Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 108 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

Request Network Docs

Loading...

Request Network API

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

General

Loading...

Loading...

Loading...

Loading...

Loading...

Advanced

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Request Network SDK

Request Client

Share an encrypted request

The content of an encrypted request can be shared to additional third parties using the addStakeholder feature.

Calling request.addStakeholder() allows any stakeholder of a request to add the public key of a third party as a stakeholder on a request. The third party can now read the content of the request.

Feature exists. Docs coming soon...

Detect a payment

Request payments can be detected easily, thanks to the integration of The Graph.

Our payment-subgraphs indexes Request's proxy smart contracts and allow you to query payment data easily.

Other methods are available to detect a payment by simply watching the Proxy Smart Contract used for payment. Payment transactions include a paymentReference that links them to the request.

Conversion Payment

A "conversion" request is one that is denominated in one currency but paid in another currency. This is facilitated by on-chain price feeds provided by oracles. The typical use case is to denominate a request in fiat like USD and pay the request in stablecoins like USDC or DAI.

Native Payment

Get Started

Encryption and Decryption

Configuring Payment Fees

Payment

SDK Guides

Request Network Docs

Request Network is a protocol for creating payment requests and reconciling payments.

Talk to an expert

Learn how Request Network can streamline Web3 invoicing and payments for your app - book a call with us.

What you can do with Request Network

  • Easily add invoicing features to your app

  • Enable crypto payments in just a few clicks

  • Reconcile invoices and payments with 100% accuracy

Start Building with Request Network

SDK Demo Apps

Examples to quickly integrate Request Network

The Request Network Templates are examples of how to integrate the Request Network Components into various web frameworks.

The Request Network Components are built using Svelte but compiled to Web Components making them usable in any web environment, regardless of the framework.

Use a Template

The Request Network Templates are distributed as "Template Repositories" in Github. The Github Docs explain how to .

Batch Payment

Functions:

Tests:

Declarative Payment

Example:

Create and Pay Requests

The Request Network API provides an interface for creating and paying requests within your application.

Talk to an expert

Discover how Request Network API can enhance your app's features - book a call with us.

Core Functionality

At its core, the Request Network API empowers you to:

  • Create Requests: Define payment requests with information such as payee, payer (optional), amount, currency, and recurrence (optional).

  • Facilitate Payments: Return transaction calldata, ready to be signed by end-users and sent to the blockchain for secure and transparent value transfer.

  • Deliver Webhook Notifications: Receive instant updates on payment status changes, enabling your application to react dynamically to completed transactions.

Supported Chains and Currencies

See

For detailed information on all available endpoints and their parameters, please refer to the full

Create and Pay Request Workflow

The following diagram illustrates the typical flow for creating and paying requests using the Request Network API:

Swap-to-Conversion Payment

A "swap-to-conversion" payment is where the request is denominated in currency A, the payer sends currency B and the payee receives currency C.

Payment Processor Functions:

Payment Processor Test:

SDK Injector

A CLI tool to help inject Request Network functionality into projects

The Request Network Injector CLI simplifies the integration process by automatically injecting pre-built, customizable functions into your project. This tool allows new and experienced builders to quickly integrate the Request Network Protocol into their applications with minimal setup.

Features

Configure the Request Client

Configure the client

The following command creates a new Request Client instance and configures it to :

  • Connect to the Gnosis Request Node Gateway maintained by the Request Network Foundation.

Request Node Gateways

The Request Network Foundation operates several Request Node "Gateways" that are free for anyone to use. These gateways offer endpoints for creating and retrieving requests. They also pay the protocol fee for creating requests, about USD $0.10 per request, paid in XDAI.

Gateway
real/test
URL

Supported Chains and Currencies

The Request Network API supports 500+ currencies across 10 EVM chains.

Talk to an expert

Discover how Request Network API can enhance your app's features - with us.

Request Network API Supported Chains and Currencies

Support a new currency

Request Network allows you to support any currency. Head out to the package to see how we identify and manage currencies in the CurrencyManager. You don't need to add new currencies in this repository.

Instead, when instanciating the CurrencyManager, you can feed it with a list of supported currencies for your dapp:

To implement new types of currencies (aside fiat, BTC, ETH, ERC20), .

Compute a Request ID without creating the request

Updating a Request

After a request is created, it can be updated:

Name
Description
Role Authorized

Swap-to-Pay Payment

A "swap-to-pay" payment is where the payment sender sends one currency but the payment recipient receives a different currency.

Functions:

Tests:

Pay from Safe Multisig

Instructions to pay a request from a Safe Multisig

The Request Network Templates and Example Apps support paying a request from a Safe multisig wallet.

  • When connecting your wallet, select WalletConnect.

  • Copy the WalletConnect Pairing Code.

Cover

Request Network API

  • Fast

  • Easy to Integrate

Cover

API Demo App: EasyInvoice

  • Try out Request Network

  • Create and Pay Invoices

Create a new repository from a template repository

Components

Web Components for integrating Request Network. Usable in any framework.

increaseExpectedAmount

increase the expected amount

payer

addStakeholders

grant 1 or more third parties access to view an encrypted request

payee, payer, third party

Feature exists. More docs coming soon...

accept

accept a request, indicating that it will be paid

payer

cancel

cancel a request

payee, payer

reduceExpectedAmount

reduce the expected amount

payee

SDK Reference

const list: CurrencyInput[] = [
    { type: RequestLogicTypes.CURRENCY.ETH, decimals: 18, network: 'anything', symbol: 'ANY' },
];
const currencyManager = new CurrencyManager(list);
currency
head towards payment networks
/** Create request parameters */
export interface ICreateRequestParameters {
  requestInfo: RequestLogic.ICreateParameters | IRequestInfo;
  signer: Identity.IIdentity;
  paymentNetwork?: Payment.PaymentNetworkCreateParameters;
  topics?: any[];
  contentData?: any;
  disablePaymentDetection?: boolean;
  disableEvents?: boolean;
}
  
  /**
   * Gets the ID of a request without creating it.
   *
   * @param requestParameters Parameters to create a request
   * @returns The requestId
   */
  public async computeRequestId(
    parameters: Types.ICreateRequestParameters,
  ): Promise<RequestLogicTypes.RequestId>

Use the web3-signature package to create requests using a web3 wallet like Metamask.

Mock Storage

To create mock storage requests, where the request is stored in memory on the local machine and cleared as soon as the script is finished running, set the useMockStorage argument to true when instantiating the RequestNetworkobject.

const web3SignatureProvider = new Web3SignatureProvider(provider);
const requestClient = new RequestNetwork({
  nodeConnectionConfig: { 
    baseURL: 'https://xdai.gateway.request.network/' 
  },
  signatureProvider: web3SignatureProvider,
});
const requestClient = new RequestNetwork({
  useMockStorage: true,
});
Fee Collection: When paying a request, you can specify a fee percentage (between 0 and 100) and a fee address, which will add the fee on top of the payment amount - meaning the payer will pay the original amount plus the fee percentage, with the fee portion being sent to the specified fee address.
  • Partial Payment Support: Pay a portion of a request instead of the full amount at once. This unlocks powerful use cases such as:

    • Split payment: split a payment 50% USDC on Base and 50% with USDT on Optimism.

    • Gradual payment plans: Allow users to pay large invoices in smaller chunks.

    • Risk mitigation: Test with small amounts before completing large payments.

    The API automatically tracks payment progress, showing partially_paid status until the request is fully paid, and prevents overpayment by capping amounts to the remaining balance.

  • Request Network API Reference
    Supported Chains and Currencies
    Automatic injection of essential Request Network functions
  • Support for both TypeScript and JavaScript projects

  • Customizable function selection

  • Automatic package installation

  • Support for various package managers (npm, yarn, pnpm, bun)

  • How it works

    1. The CLI analyzes your project structure

    2. You select the functions you want to inject

    3. You choose your preferred language (TypeScript or JavaScript)

    4. The tool injects the selected functions into your project

    5. Necessary dependencies are automatically installed

    Demo Video

    Request Injector in Terminal

    📦 View on NPM

    ▶️ Demo Video

    ℹ️ View Source

    Gnosis Gateway

    real

    https://gnosis.gateway.request.network

    Gnosis Gateway (deprecated alias)

    real

    https://xdai.gateway.request.network

    Sepolia Gateway

    test

    Overall, the Request Network API supports 500+ currencies across 10 EVM chains.

    ERC20, Native, and Conversion Payments Supported Chains

    10 EVM Chains:

    • Ethereum

    • Arbitrum One

    • OP Mainnet

    • Base

    • Polygon

    • BSC

    • Avalanche

    • Fantom

    • zkSync Era

    • Sepolia

    ERC20 and Native Payments Supported Currencies

    For ERC20 and Native Payments, the Request Network API supports 500+ tokens, mostly on Ethereum. See Request Network Token List for the full list.

    The Request Network Token List is a super set, which contains some currencies on non-supported chains. Cross-reference with the Supported Chains and Currencies

    Conversion Payments Supported Currencies

    For Conversion Payments, the Request Network API supports the following invoice currencies:

    • USD

    • EUR

    • CNY

    • GBP

    • JPY

    For Conversion Payments, the Request Network API supports the following payment currencies:

    • USDC

    • USDT

    • DAI

    • FAU on Sepolia

    To get a list of supported payment currencies for a given invoice currency, use the Supported Chains and Currencies endpoint.

    Crosschain Payments Supported Currencies

    See Crosschain Payments Supported Chains and Currencies

    Crypto-to-fiat Payments Supported Currencies

    See

    Currencies API Endpoint

    The currencies API endpoints provide access to the complete Request Network token list, enabling you to discover and filter available tokens across multiple blockchain networks.

    Key Features

    • Payment Request Integration: Get the exact currency IDs needed for creating payment requests

    • Payment Integration: Get accurate token information for payment processing

    • Currency Validation: Verify supported tokens before creating payment requests

    • Multi-Chain Support: Access tokens across Ethereum, Polygon, Arbitrum, and more

    • Developer-Friendly: Simple filtering options for easy integration

    Currency Information Structure

    Each token in the response includes:

    • id: Unique Request Network token identifier (e.g., USDC-mainnet)

    • name: Human-readable token name (e.g., USD Coin)

    • symbol: Token symbol (e.g., USDC)

    • decimals: Number of decimal places for the token

    • address: Contract address on the specified network

    • network: Blockchain network name

    • type: Token type (ERC20, ETH, etc.)

    • hash: Contract address hash

    • chainId: Blockchain chain ID

    Endpoints

    book a call
    Click the WalletConnect button on the Safe top-navigation bar and paste the Pairing Code into the resulting dialog.

    Crosschain Payments

    Pay requests using stablecoins from any supported network, without manual bridging or token swaps.

    Talk to an expert Discover how Request Network API can enhance your app's features - book a call with us.

    Crosschain payments allow users to pay a request using a stablecoin from a different blockchain network than the one specified on the request. For example, a payer can pay a request for USDC on Base using USDT from their Optimism wallet.

    Benefits

    • Flexibility: Payers can pay with their preferred currency.

    • Cost-Effective: Automated routing balances cost and speed.

    • Time-Saving: Payers don't need to swap or bridge tokens manually.

    • Simplified UX: Payment settlement requires only 1 or 2 signatures from the Payer.

    Crosschain Payments Supported Chains and Currencies

    For Crosschain (and Samechain) Payments, the Request Network API supports 12 stablecoins: USDC/USDT/DAI on 4 chains (Ethereum, Arbitrum One, Base, OP Mainnet).

    Crosschain Payments Supported Chains

    Crosschain payments are supported on the following blockchain networks:

    • Ethereum

    • Arbitrum One

    • Base

    • OP Mainnet

    Crosschain Payments Supported Currencies

    Warning: Crosschain payments work only with mainnet funds (real money). Test networks are not supported.

    The following stablecoins are supported for crosschain payments on both the sending and receiving networks.

    • USDC

    • USDT

    • DAI

    How it works

    1. Request creation

    To enable crosschain payments, the request must be created with the following parameters:

    • paymentCurrency included in the and .

    • amount greater than 1 - executing crosschain payments under 1 stablecoins is not allowed, even though creating requests has no restrictions on amount .

    For more details about creating requests, please see the endpoint.

    2. Payment route fetching

    To display a list of possible routes for a given request and payer address, use the endpoint. It returns all of the possible routes based on the payer's token balances.

    Route Ranking

    The API automatically ranks available payment routes based on the following factors:

    • Transaction fees

    • Processing speed

    Routes that offer a balanced combination of lower fees and faster processing times are ranked higher in the results.

    Fee breakdown

    When fetching payment routes, each route displays the total estimated fees in the payment currency. This fee represents the combined costs associated with processing the transaction, including:

    1. Gas Fees:

      The total fee includes all gas costs incurred by the payment processor wallet for processing the transaction. This covers:

      - Transferring tokens from the payer's wallet.

      - Approving the payment execution smart contract.

      - Executing the crosschain payment transaction.

      For tokens supporting EIP-2612:

      - The payment processor wallet also covers for the onchain permit transaction.

      For tokens that do not support EIP-2612:

      - The payer must perform an onchain approval transaction and pay for the gas fee directly. This fee is

    Samechain routes

    The API may return samechain routes if the payer address has supported currencies on the same chain as the paymentCurrency .

    • Example: paymentCurrency is USDC on Base, and the payer has USDT on Base

    • Gasless transactions - the transaction fees are added on top of the request amount

    • No native token (ETH, etc..) needed for gas

    3. Getting payment calldata

    Once the route is selected, the payer needs to fetch the unsigned payment calldata or intents. If the selected route is a crosschain payment, the endpoint returns an unsigned payment intent. It will also return an unsigned approval permit or unsigned approval calldata, depending on whether the paymentCurrency supports . For crosschain payments, this endpoint is NOT approval aware - it will return an approval permit or approval calldata even if approval has already been granted.

    If the selected route is a direct payment, the returns an unsigned payment calldata. It may also return an approval calldata. For direct payments, this endpoint IS approval aware - it will omit the approval calldata if sufficient approval has already been granted.

    4. Signing the payment intent

    The intents and calldata returned by the endpoint in the previous step must be signed by the payer's wallet to authorize the crosschain payment. The process for signing the approval varies depending on whether the paymentCurrency supports , indicated by the metadata response parameter.

    If the token does not support EIP-2612 Permit, the payer must sign and submit a standard ERC20 approval transaction.

    5. Sending the signed data

    Finally, the signed payment intent (and possibly the signed approval permit) are sent back to execute the crosschain payment via the endpoint. It will handle all the necessary steps to complete the payment. A payment.complete event will be sent to the platform's webhooks when the payment is completed.

    Custom fee configuration

    It will be possible in the future to add a custom fee to the payment, this is currently under development.

    Request Network Token List

    The Request Network Token List is a curated list of tokens supported by Request Network products. The token list follows a standardized format and includes essential information about each token, such as address, symbol, name, decimals, and chainId.

    Usage

    The token list is available at: https://requestnetwork.github.io/request-token-list/latest.json

    You can fetch the token list directly in your application:

    Token List Structure

    Each token in the list contains the following information:

    Adding a New Token

    We welcome community contributions! To add a new token to the list:

    1. Fork the repository on Github

    2. Add your token information to tokens/token-list.json

    3. Make sure your token meets our requirements (see )

    4. Run tests locally: npm test

    Add Stakeholder

    A dialog box for granting third-party access to an encrypted invoice created via Request Finance

    The add-stakeholder component allows Builders to quickly integrate the Request Finance Add Stakeholder widget into their applications.

    The Request Finance Add Stakeholder widget provides a dialog box for end-users to grant third-party access to one of their encrypted payment requests created via Request Finance. Then, under the hood, Request Finance calls request.addStakeholders() to add the third party as a stakeholder to the encrypted payment request.

    Access is granted on a per-request basis. Once we prove market demand for this feature, we hope that Request Finance will improve its widget to allow granting access to multiple requests at a time.

    The template comes in the form of a and a native component, provided by the package. The Web Component can be used anywhere including, but not limited to, React, Next.js, Vue, Svelte, or as a browser script.

    Installation

    Usage

    Web Component in React, Next.js, or Vue

    Native Svelte component

    Web Component in Svelte

    Web Component in Browser

    Payment Detection

    A high level overview on how our API does payment detection

    Payment Detection in Request API

    Overview

    The Request API uses a reference-based payment detection system that automatically monitors blockchain transactions to detect when payments are made to your requests. This system works across multiple blockchains and handles various payment scenarios.

    How It Works

    1. Payment Reference Generation

    When you create a payment request, the API automatically generates a unique, a 16-character identifier that acts as a fingerprint for your request. This reference is what connects blockchain transactions back to your specific request.

    2. Blockchain Monitoring

    The API continuously monitors supported blockchains using subgraphs that scan for transactions containing payment references. This happens automatically in the background, no action required from you.

    3. Automatic Detection

    When someone makes a payment and includes the payment reference in their transaction, our system:

    • Detects the transaction within minutes

    • Validates the payment details (amount, currency, recipient)

    • Updates the request status (partially paid, fully paid, etc.)

    • Triggers your configured webhooks

    4. Real-time Updates

    Once a payment is detected, your request status is immediately updated and you can get the latest information via:

    • GET requests to check payment status using the request's id

    • Automatically receive updates to your

    Crosschain Support

    All crosschain payments done using the use our as the last leg of payment, so payment detection works out of the box.

    Webhook Notifications

    For real-time integration, you can configure webhooks to be notified for the following events:

    • Payment Confirmed: Full payment received

    • Payment Partial: Partial payment received

    • Payment Failed: Transaction failed

    • Payment Refunded: Payment was refunded

    This allows your application to react immediately to payment events without constantly polling the API.

    Integration Benefits

    • Zero Configuration: Payment detection happens automatically

    • Multi-blockchain: Works across all supported networks

    • Real-time: Fast detection and status updates

    • Flexible: Handles various payment scenarios

    The system is designed to be completely transparent to your application, simply create requests and let the API handle all the complexity of monitoring blockchains for payments.

    Payment Reference

    In the Reference-based Payment Networks, Payments are linked to Requests via a paymentReference which is derived from the requestId and payment recipient address.

    This paymentReference consists of the last 8 bytes of a salted hash of the requestId and payment recipient address, concatenated :

    last8Bytes(hash(lowercase(requestId + salt + address)))
    • requestId is the id of the request

    • salt is a random number with at least 8 bytes of randomness. It must be unique to each request

    • address is the payment address for payments, the refund address for refunds

    • lowercase() transforms all characters to lowercase

    • hash() is a keccak256 hash function

    • last8Bytes() take the last 8 bytes

    Use the to calculate the payment reference.

    Request Scan

    An explorer app for viewing requests, payments, and addresses in Request Network.

    Request Scan is an explorer for viewing requests and payments in Request Network. It enables users to explore and scrutinize requests, payments, and addresses within the Request Network ecosystem.

    Intended Audience

    Request Scan caters to a broad audience:

    Lifecycle of a Request

    The typical lifecycle of a request is as follows:

    Create a request

    • The payer or payee signs the request which contains the payee, payer, currency, amount, payment details, and arbitrary content data.

    API Portal: Manage API Keys and Webhooks

    An app for managing Request Network API keys and webhooks.

    Overview

    The Request Network API Portal provides app developers with a platform to securely manage their API keys and webhook endpoints.

    Transferable Receivable Payment

    Functions:

    Tests:

    Meta Payments

    Tests:

    waitForConfirmation()

    Description

    Wait for a request to be persisted and indexed

    Parameters

    const tokenList = await fetch(
      "https://requestnetwork.github.io/request-token-list/latest.json"
    ).then((res) => res.json());
    None

    Returns

    Promise<IRequestDataWithEvents>

    Reliable: Built on proven blockchain indexing infrastructure

    payment reference
    webhooks
    Request Network API
    ERC 20 Fee proxy contract
    PaymentReferenceCalculator
    https://sepolia.gateway.request.network

    The request can be optionally encrypted such that only the payee, payer, and approved 3rd parties can view the request contents.

  • The request is persisted in IPFS.

  • The IPFS Content-addressable ID (CID) is stored in a smart contract on Gnosis chain

  • Requests are created by storing their CIDs on Gnosis, but this doesn't mean payment must occur on Gnosis. Payment can occur on any of the supported chains including 20+ EVM-compatible chains or NEAR.

    Update a request

    • The payee can optionally cancel the request or increase/decrease the expected amount.

    • The payer can optionally accept the request, indicating that they intend to pay it.

    • Both payee and payer can add 3rd party stakeholders if the request is encrypted.

    Pay a request

    • The payer derives a paymentReference from the request contents.

    • The payer calls a function on the payment network smart contract, passing in the token address, to address, amount, and paymentReference.

    • An event is emitted containing the token address, to address, amount, and paymentReference.

    Most requests are "reference-based" meaning that a paymentReference derived from the request contents is logged on-chain via a smart contract that emits an event. Nothing gets written back to IPFS when paying a "reference-based" request.

    The exception is when paying a "declarative" request, in which case, data is written back to IPFS. This includes when the payer declares that the payment was sent and the payee declares that the payment was received.

    Retrieve a request / Detect a payment

    • The event is indexed by the payments subgraph

    • An app can retrieve the request contents from IPFS and calculate the balance based on events from the payments subgraph.

    The request balance is calculated by adding up all the on-chain payment events with the same paymentReference. Partial payments are possible.

    All of these steps are facilitated by the Request Network JavaScript SDK such that the developer needs only make a few function calls. See the Quickstart to learn more.

    Typical Lifecycle of a Request

    Protocol Overview

  • Create a Pull Request

  • request-token-list
    CONTRIBUTING.md
    Web Component
    Svelte
    @requestnetwork/add-stakeholder
    Key Features

    API Key Management

    • Create and Manage API Keys: Users can create new API keys for authentication.

    • Toggle and Delete API Keys: API keys can be toggled on and off, or deleted if no longer needed, enhancing control over API access.

    • Security Guidelines: API keys are sensitive and should never be shared publicly. In case of compromise, users are advised to create a new key, update their code, and delete the compromised key.

    • Multiple Keys: Allows the creation of multiple API keys for different environments or applications.

    Webhook Management

    • Create and Manage Webhooks: App developers can configure webhook endpoints to receive real-time notifications for payment events.

    • Security Guidelines: Each webhook request includes a signature in the `x-request-network-signature` header to ensure authenticity.

    • Signature Verification: The signature is a SHA-256 HMAC of the request body, signed using the webhook secret.

    Example Verification Code:

    Usage

    Creating API Keys

    • Navigate to the "API Keys" section.

    • Click on "Create new key."

    • Store the key securely and never share it publicly.

    Configuring Webhooks

    • Navigate to the "Webhooks" section.

    • Click on "Add webhook."

    • Enter the endpoint URL and ensure the endpoint is secure and can handle incoming JSON payloads.

    Security Considerations

    • Keep API keys and webhook secrets secure. Never expose them in public repositories or client-side code.

    • Verify all webhook signatures to ensure authenticity and integrity.

    • Use HTTPS for all endpoints to encrypt communication.

    🕹️ Try it out

    {
      "id": "TKN-mainnet"
      "name": "Token Name",
      "address": "0x...",
      "symbol": "TKN",
      "decimals": 18,
      "chainId": 1,
      "logoURI": "https://..."
    }
    npm install @requestnetwork/add-stakeholder
    import '@requestnetwork/add-stakeholder'
    
    export default function App() {
        return (
            <add-stakeholder builderKey="..." webhookUrl="..."/>
        )
    }
    import { AddStakeholder } from '@requestnetwork/add-stakeholder'
    <AddStakeholder builderKey="..." webhookUrl=".."/> 
    import '@requestnetwork/add-stakeholder'
    <add-stakeholder builderKey="..." webhookUrl="..."/>
    <script src="./node_modules/add-stakeholder/dist/web-component.umd.cjs" defer></script>
    <!-- or -->
    <script src="//unpkg.com/@requestnetwork/add-stakeholder" defer></script>
    
    <add-stakeholder builderKey="..." webhookUrl="..."/>
    import express from 'express';
    import crypto from 'node:crypto';
    
    const app = express();
    const WEBHOOK_SECRET = 'your_webhook_secret';
    
    app.post('/payment', async (req, res) => {
      const signature = req.headers['x-request-network-signature'];
      const expectedSignature = crypto
        .createHmac('sha256', WEBHOOK_SECRET)
        .update(JSON.stringify(req.body))
        .digest('hex');
    
      if (signature !== expectedSignature) {
        return res.status(401).json({
          success: false,
          message: 'Invalid signature'
        });
      }
    
      // Business logic here
      return res.status(200).json({ success: true });
    });
    not
    included in the total fee shown for the route.
  • Service Fees:

    The total fees also include any service fees charged by the crosschain infrastructure for facilitating transfers or swaps between different blockchains.

  • EIP-2612 Permit
    EIP-2612 Permit
    Crosschain Payments
    Crosschain Payments
    POST /v2/request
    GET /v2/request/{requestId}/routes
    GET /v2/request/{requestId}/pay
    GET /v2/request/{requestId}/pay
    GET /v2/request/{requestId}/pay
    POST /v2/request/payment-intents/{paymentIntentId}

    Accountants: Audit and verify financial data on the request network.

  • Developers: Easily access Request Network data for troubleshooting your applications.

  • Analysts: Gain deep insights into network activity and trends.

  • Researchers: Conduct in-depth studies on blockchain data.

  • Enthusiasts: Stay informed about the latest happenings on the Request Network.

  • Usage

    User Interface

    • Search Bar: Located at the top, allows you to search for specific requests or addresses.

    • Dashboard: Provides an overview of network statistics and recent activity.

    • Requests: View a list of recent requests with details like payee, payer, amount, and timestamp.

    • Payments: View a list of recent payments with details like blockchain transactions, amounts, fees, and timestamps.

    • Address: View information about individual addresses, including their requests and payments.

    • Request: View information about individual requests, including their details and table with actions and payments.

    Searching for Data

    • Request: Enter a request ID in the search bar to view its details.

    • Address: Enter an address to see its requests and payment history.

    Demo Video

    Request Scan Landing Page

    🕹️ Try it out

    ▶️ Demo Video

    ℹ️ View Source

    Request Invoicing

    An Invoicing Template for creating, viewing, and paying requests in Request Network. Built using the Invoice Dashboard and Create Invoice Form Web Components.

    Framework
    Deployment
    Source

    Next.js

    Request Invoicing Demo Video

    Features

    Feature
    Status

    Chains and Currencies

    Payment Currencies

    Chain
    Currencies

    Invoice Currencies

    Invoices can be denominated in the following fiat currencies, in addition to the listed above.

    • USD

    • EUR

    • CNY

    • GBP

    Request Invoicing Integration Video

    In-Memory Requests

    Overview

    In-memory requests allow for creating and managing requests without immediately persisting them to storage. This enables faster payment workflows and deferred persistence.

    Key benefits:

    • Faster payment flow: In-memory requests are helpful when payment is the priority, such as in e-commerce cases. In this scenario, the request is a receipt rather than an invoice.

    • Deferred Persistence: With in-memory requests, a request can be created on the front end with a user's signature and passed on to the backend for persistence.

    How it works:

    The flow of creating and paying an in-memory request is similar to a regular request with the following key differences:

    • Create an in-memory request by passing the argument skipPeristence: true when instantiating the RequestNetwork instance.

    • An in-memory request is not persisted immediately like normal requests. Instead, it is stored in memory on the device where it was created. It can be persisted at a later time using the persistTransaction()function.

    • An in-memory request has the inMemoryInfo

    1

    Install necessary dependencies

    To create in-memory requests, it is necessary to install the following package:

    Along with the following package for payments:

    2

    fromRequestId()

    Description

    Retrieve a request from a request ID

    Parameters

    Name
    Type
    Required?
    Description

    Returns

    Promise<>

    Types and Interfaces

    options

    Name
    Type
    Required?
    Description

    getData()

    Description

    Unwrap the request contents

    Parameters

    None

    Returns

    Promise<>

    refresh()

    Description

    Refresh the request data and balance

    Parameters

    Name
    Type
    Required?
    Description

    Returns

    Promise<>

    web3-signature

    Install

    npm install @requestnetwork/web3-signature

    Exports

    Name
    Type
    Description

    epk-signature

    Install

    npm install @requestnetwork/epk-signature

    Exports

    Name
    Type
    Description

    payment-processor

    Install

    npm install @requestnetwork/payment-processor

    Exports

    Name
    Type
    Description

    EasyInvoice: API Demo App

    An app for creating and paying requests using the Request Network API.

    EasyInvoice is a web application built with Next.js that allows users to create and manage invoices, and accept crypto payments via the Request Network API. It mimics Web2 apps in its functionalities, providing a user-friendly experience with Google login and real-time updates.

    Talk to an expert

    Discover how your app can have its own EasyInvoice features - with us.

    Request Checkout

    The playground for adding crypto payments to any website, build using .

    Framework
    Deployment
    Source

    Recurring payments

    Create subscriptions that would automatically be paid through out a timeframe.

    Talk to an expert

    Discover how Request Network API can enhance your app's features - with us.

    Overview

    Invoice Dashboard

    A dashboard for viewing and paying invoices in Request Network

    The Invoice Dashboard component allows end-users to view and pay an invoice in Request Network. It is built using but compiled to a , making it usable in any web environment, regardless of the framework.

    Installation

    To install the component, use npm:

    Single Request Forwarder

    Overview

    The Single Request Forwarder is a smart contract solution that enables integration with Request Network's payment system without modifying existing smart contracts.

    The Single Request Forwarder Factory contact addresses can be found :

    Streaming Payment

    Pay a series of requests with a stream of ERC777 Super Tokens from Superfluid.

    The first request of a series is very similar to , it defines the salt, paymentAddress and requestId to compute the paymentReference used for the whole series.

    Other requests must define a previousRequestId and cannot redefine any of the payment properties (paymentAddress, feeAddress or salt).

    Multiple requests can be paid with the same stream, typically recurring requests of fixed amounts paid continuously. A group of requests payable with the same stream are called a request series, they must all have the same currency.

    Pay through a proxy-contract with a multisig

    The imports you will need:

    In this example, we will use the . Here is its partial abi:

    Pay ETH request

    Mobile using Expo

    Introduction

    This guide demonstrates how to integrate Request Network into a React Native application using Expo. Due to the differences between Node.js and React Native environments, several polyfills and configurations are necessary to make Request Network work properly. Following this guide will set up your Expo project to use Request Network, handle polyfills for missing modules, and ensure smooth integration. A full Github repository with Request Network support can be found .

    Escrow Payment

    Escrow

    The Request Network Escrow isn't a separate payment network. Rather, it builds on top of the ERC20_FEE_PROXY_CONTRACT payment network.

    request-client.js

    Install

    Exports

    Name

    computeRequestId()

    Description

    Compute a request ID without actually creating a request

    Parameters

    Request

    Description

    An object used to interact with requests. Returned by and _createEncryptedRequest()

    Properties

    fromTopic()

    Description

    Retrieve an array of requests from a topic

    Parameters

    cancel()

    Description

    Cancel a request

    Parameters

    IIdentity

    Description

    An identity object that is used to uniquely identify a stakeholder in a request. Today, a stakeholder's IIdentity is expressed as an Ethereum address, but it is conceivable that future implementations may include alternative identity formats, perhaps W3C DIDs.

    Examples of IIdentity are: payee, payer, signer, creator, and any 3rd party that can view an encrypted request's contents.

    reduceExpectedAmountRequest()

    Description

    Reduce the expected amount

    Parameters

    epk-decryption

    Install

        "metadata": {
            "supportsEIP2612": true
        }
    import { ethers } from "ethers";
    
    const ethersProvider = new ethers.providers.Web3Provider(
      // Connected wallet provider
      walletProvider as ethers.providers.ExternalProvider,
    );
    const signer = await ethersProvider.getSigner();
    
    // Response from the `GET /request/{requestId}/pay` endpoint
    const response = ...
    
    const paymentIntent = JSON.parse(paymentData.paymentIntent);
    const supportsEIP2612 = paymentData.metadata.supportsEIP2612;
    let approvalSignature = undefined;
    let approval = undefined;
    
    if (supportsEIP2612) {
      approval = JSON.parse(paymentData.approvalPermitPayload);
    
      approvalSignature = await signer._signTypedData(
        approval.domain,
        approval.types,
        approval.values,
      );
    } else {
      const tx = await signer.sendTransaction(paymentData.approvalCalldata);
      await tx.wait();
    }
    
    const paymentIntentSignature = await signer._signTypedData(
      paymentIntent.domain,
      paymentIntent.types,
      paymentIntent.values,
    );
    
    const signedData = {
      signedPaymentIntent: {
        signature: paymentIntentSignature,
        nonce: paymentIntent.values.nonce.toString(),
        deadline: paymentIntent.values.deadline.toString(),
      },
      signedApprovalPermit: approvalSignature
        ? {
          signature: approvalSignature,
          nonce: approval.values.nonce.toString(),
          deadline: approval?.values?.deadline
            ? approval.values.deadline.toString()
            : approval.values.expiry.toString(),
        }
        : undefined,
    };
    Typical Workflow
    1. Using the request-client.js package, the payer creates a request with the ERC20_FEE_PROXY_CONTRACT payment network.

    2. Using the payment-processor package, payer:

      1. Approves the escrow contract using approveErc20ForEscrow()

      2. Pays the escrow contract using payEscrow()

      3. Waits until the work is complete

      4. Pays the payee from the Escrow contract using payRequestFromEscrow()

    These steps are shown by our unit tests:

    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/payment-processor/test/payment/erc20-escrow-payment.test.ts#L200-L339

    Web3SignatureProvider

    Constructor

    Sign using a private key inside of a wallet

    EthereumPrivateKeySignatureProvider

    Constructor

    payRequest()

    Function

    Pay a request

    requestId

    string

    The ID of the request

    options

    Object

    Options

    disablePaymentDetection

    boolean

    Disable payment detection

    disableEvents

    boolean

    Disable events

    Request
    IRequestDataWithEvents

    requestAndMeta

    IReturnGetRequestFromId

    optional

    The value returned by getRequestFromId()

    IRequestDataWithEvents
    Request Injector Demo Video
    npm install @requestnetwork/epk-decryption
    property.
  • Avoid calling getData() on an in-memory request because it will fail silently by returning an empty EventEmitter object.

  • Retrieving an in-memory request with requestClient.fromRequestId() will fail because the request has not been persisted yet so it is not possible to read it from the Request Node.

  • Create an in-memory request

    Create an in-memory request by passing the argument skipPeristence: true when instantiating the RequestNetwork instance.

    3

    Pay an in-memory request

    To pay an in-memory request, pass the inMemoryInfo.requestData property to the payment function.

    4

    Persist in-memory request

    In-memory requests need to be persisted using a new RequestNetwork client that does not use the skipPersistence property.

    Pay ERC20 request

    Approve ERC20 spending

    Pay ERC20 request

    import { Contract, ContractTransaction, Signer } from 'ethers';
    import {
      encodeApproveErc20,
      encodePayErc20Request,
    } from '@requestnetwork/payment-processor/dist/payment/erc20-proxy';
    import { getRequestPaymentValues } from '@requestnetwork/payment-processor/dist/payment/utils';
    import { ClientTypes } from '@requestnetwork/types';
    const multisigAbi = [
      'function submitTransaction(address _destination, uint _value, bytes _data) returns (uint)',
    ];
    Gnosis multisig
    import {
      payRequest
    } from "@requestnetwork/payment-processor";
    
    const paymentTx = await payRequest(
        inMemoryRequest.inMemoryInfo.requestData,
        signer
      );
      
    await paymentTx.wait(confirmationBlocks);
    
    const persistingRequestNetwork = new RequestNetwork({
        nodeConnectionConfig: {
          baseURL: "https://gnosis.gateway.request.network",
        },
      });
    
    await persistingRequestNetwork.persistRequest(inMemoryRequest);
    npm install @requestnetwork/request-client.js
    npm install @requestnetwork/payment-processor
    // Request parameters 
    const requestParameters = {...}
    
    
    const web3SignatureProvider = new Web3SignatureProvider(
        ethersProvider!.provider
      );
    
     const inMemoryRequestNetwork = new RequestNetwork({
        nodeConnectionConfig: {
          baseURL: "https://gnosis.gateway.request.network",
        },
        signatureProvider: web3SignatureProvider,
        skipPersistence: true,
      });
    
     let inMemoryRequest =
        await inMemoryRequestNetwork.createRequest(requestParameters);
    
    export const payEthWithMultisig = async (
      request: ClientTypes.IRequestData,
      multisigAddress: string,
      signer: Signer,
    ): Promise<ContractTransaction> => {
      const multisigContract = new Contract(multisigAddress, multisigAbi, signer);
      const { paymentAddress, paymentReference } = getRequestPaymentValues(request);
      return multisigContract.submitTransaction(paymentAddress, 0, paymentReference);
    };
    export const approveErc20WithMultisig = async (
      request: ClientTypes.IRequestData,
      multisigAddress: string,
      signer: Signer,
    ): Promise<ContractTransaction> => {
      const multisigContract = new Contract(multisigAddress, multisigAbi, signer);
      const tokenAddress = request.currencyInfo.value;
      return multisigContract.submitTransaction(tokenAddress, 0, encodeApproveErc20(request, signer));
    };
    import { erc20FeeProxyArtifact } from '@requestnetwork/smart-contracts';
    
    export const payErc20WithMultisig = async (
      request: ClientTypes.IRequestData,
      multisigAddress: string,
      signer: Signer,
    ): Promise<ContractTransaction> => {
      const multisigContract = new Contract(multisigAddress, multisigAbi, signer);
      const proxyAddress = erc20FeeProxyArtifact.getAddress(request.currencyInfo.network);
      return multisigContract.submitTransaction(
        proxyAddress,
        0,
        encodePayErc20Request(request, signer),
      );
    };

    ✅

    Sepolia

    ETH, USDC, USDT, FAU

    JPY

    ERC20 Payments

    ✅

    Native Token Payments

    ✅

    Conversion Payments

    ✅

    rnf_invoice format 0.3.0

    ✅

    Configure Logo and Colors

    ✅

    Inject your own custom currency list

    ✅

    Ethereum

    ETH, USDC, USDT, DAI, REQ

    Polygon

    MATIC, USDC, USDT, DAI

    BNB Chain

    DAI, BUSD

    Gnosis

    USDC

    Optimism

    ETH, USDC, USDT, DAI

    Base

    ETH, USDC

    https://invoicing.request.network
    https://github.com/RequestNetwork/invoicing-template
    Currency Selectors from the Request Invoicing app
    Payment Currencies

    Download Invoice as PDF

    Key Features

    Overall Supported Currencies and Chains

    15 stablecoins: USDC/USDT/DAI on 5 chains (Ethereum, Polygon, Arbitrum One, Base, OP Mainnet) + 4 testnet tokens on Sepolia + USD fiat for Conversion and Crypto-to-fiat payments.

    Invoice Creation

    • Invoice Creation: A simple form to create invoices.

      • Client name and email fields.

      • Items, amounts, and notes fields.

      • Invoice currency and payment currency options, supporting currency conversion via the Request Network API.

    • Currency Conversion: uses on-chain price feeds to calculate the exact payment currency amount based on the invoice currency at the moment of payment so you always receive the correct amount.

    EasyInvoice Create Invoice Page

    Dashboard

    • Dashboard: View key metrics and a table of your invoices.

    EasyInvoice Dashboard

    Invoice Payment

    • Invoice Payment:

      • View invoice details and initiate payment using transaction calldata provided by the Request Network API.

      • Compatible with 80+ different crypto wallets via Reown AppKit

    • Real-time Updates: The app receives webhooks from the Request Network API to update the invoice status in real-time.

    EasyInvoice Invoice Payment Page
    EasyInvoice supports 80+ wallets via Reown AppKit

    Invoice Crosschain Payment

    Crosschain Payment Supported Currencies

    For Crosschain (and Samechain) Payments, EasyInvoice supports 12 stablecoins: USDC/USDT/DAI on 4 chains (Ethereum, Arbitrum One, Base, OP Mainnet)

    Crypto-to-fiat Payment

    Crypto-to-fiat Payment Supported Currencies

    For Crypto-to-fiat Payments, EasyInvoice supports USDC on Sepolia.

    Batch Pay Invoices

    Recurring Invoices

    • Recurring Invoice: Automatically create new invoices based on the selected start date and frequency

    Create New Invoice page - Recurring Invoice Enabled
    Invoice Dashboard - Recurring Invoice

    Payout

    • Payout: Send a payment without having to create a request first.

    EasyInvoice Direct Payment page

    Batch Payout

    InvoiceMe Link

    • InvoiceMe Link: Prompt clients to send you an invoice prefilled with your name and email address.

    Create InvoiceMe Link page
    Create Invoice via InvoiceMe Link

    Subscriptions

    Login

    • Google Login: Securely log in to your account using Google OAuth.

    EasyInvioce Login Page
    book a call

    🕹️ Try it out

    ℹ️ View Source

    Payment Widget Demo Video

    Features

    Feature
    Status

    Demo Page

    ✅

    Playground Page

    ✅

    ERC20 Payments

    ✅

    Native Token Payments

    ✅

    Fiat Price Conversion in the UI

    ✅

    0.3.0

    ✅

    Chains and Currencies

    Chain
    Currencies

    Ethereum

    USDC, USDT, DAI, AXS, AUDIO, RAI, SYLO, LDO, UST, MNT, MIR, INJ, OCEAN, ANKR, RLY, REQ, ETH

    Polygon

    USDC, USDT, DAI, MATIC

    Sepolia

    FAU, ETH, USDT, USDC

    BNB Smart Chain

    DAI, BUSD

    Gnosis

    USDC

    Avalanche

    USDC, USDT, AVAX

    Payment Widget Integration Video

    Next.js

    https://checkout.request.network

    https://github.com/RequestNetwork/rn-checkout

    Payment Widget
    The Recurring Payments feature allows you to create and manage subscription-like payments on the blockchain. The API handles the scheduling and triggering of these payments, providing a reliable way to automate regular transfers.

    Core functionality

    • Create Recurring Schedules: Define a payment schedule with a start date, frequency (daily, weekly, monthly, yearly), and total number of payments. The system will generate a payment permit that encapsulates all the payment details.

    • Payer Authorization: To authorize the payment series, the payer signs the payment permit with an EIP-712 signature. This single authorization allows the system to trigger all subsequent payments in the schedule without further interaction from the payer. For the first payment, the payer may also need to approve a token allowance for the recurring payment contract if they haven't already.

    • Automated Payments: Once the payer has authorized the schedule, the Request Network API backend systems automatically trigger the payments at the specified intervals. You can rely on the API to handle the entire lifecycle of the recurring payments.

    • Status Tracking and Webhooks: You can monitor the status of each recurring payment, including processed payments, failures, and completion status (e.g., active, paused, completed). Webhook notifications will be sent for key events like payment.confirmed and payment.failed, allowing your application to react in real time.

    • Flexible Management: The API provides the ability to manage the lifecycle of a recurring payment. You can cancel a recurring payment schedule to stop future payments. If a payment fails (e.g., due to insufficient funds), the schedule will be paused, and you can unpause it once the issue is resolved. Unpausing a recurring payment once issues are resolved would allow the subscription to catch up on any missed payments.

    Security & Trust

    Our recurring payments feature is built on a non-custodial smart contract that enforces several security measures to protect payers' funds and ensure predictable behavior. The core principle is that all payment parameters are defined upfront and cryptographically signed by the payer, preventing any unauthorized changes.

    Here are some key security features provided by the smart contract:

    • Signature-Protected Payments: Payments cannot be triggered without a valid EIP-712 signature from the payer. The smart contract verifies the signature for every payment attempt.

    • Immutable Recipient: The recipient's address is part of the signed data. Funds can only be sent to this specified address, which cannot be altered after the schedule is authorized.

    • Fixed Payment Amount: The amount for each payment is fixed in the signed permit. The smart contract will only transfer this exact amount, preventing any over- or under-payments.

    • Strict Payment Timing: Payments cannot be triggered before their scheduled time. The contract calculates the due date for each payment and will reject any attempts to trigger it prematurely.

    • No-Repeat Payments: The contract tracks payments, making it impossible to process the same payment more than once.

    • Enforced Payment Limit: The total number of payments is defined in the signed permit. The smart contract enforces this limit and will not allow any extra payments beyond the agreed-upon total.

    • Sequential Payments: Payments must be triggered in a strict, sequential order (e.g., payment #1, then #2, then #3). Any out-of-order attempt will fail, preventing missed or skipped payments from disrupting the schedule.

    • Signature Expiration: Each recurring payment schedule has a deadline. If the signature expires, no further payments can be triggered, providing a hard stop for the agreement.

    Recurring payment workflow

    The following diagram illustrates the typical flow for creating and managing recurring payments:

    Supported Networks

    Recurring payments are supported on the following Blockchain networks:

    • Ethereum

    • Polygon

    • Arbitrum

    • Gnosis

    • Base

    • Binance Smart Chain

    • Sepolia

    Supported currencies

    The recurring payments support all ERC20 currencies available in the supported networks.

    See the full list here Supported Chains and Currencies

    How it works

    1. Create a recurring payment

    To enable recurring payments, a schedule must be created with the following endpoint:

    The response includes a payment permit payload (EIP-712 typed data) for signature, and, if required, transactions for token allowance approval.

    2. Payer authorization

    The payer must:

    • Approve the recurring payment contract to spend the required amount of tokens (if not already approved)

    • Sign the payment permit using EIP-712 compatible wallet

    Example

    3. Recurring payment activation

    To activate the recurring payment, the resulting signature must be submitted to the following endpoint:

    A successful response confirms activation. The schedule is now active and payments will be executed automatically.

    4. Status monitoring

    The status, processed payments, next payment date, and other details can be retrieved at any time.

    5. Recurring payment management

    Recurring payments can be cancelled or unpaused.

    • Cancel: stops all future payments

    • Unpause: resume the recurring payment after it fails three times (due to insufficient balance or allowance)

    book a call
    Usage

    Usage in React and Next.js

    Follow the instructions below to add the Invoice Dashboard to a React or Next.js app.

    invoice-dashboard.tsx

    Configure the invoice dashboard web component by creating a reference to it, setting its properties, and passing the reference as a prop.

    wagmiConfig.ts

    The web component supports any wallet connector module built on top of wagmi. This provides the flexibility to use any wagmi-compatible wallet connector, such as RainbowKit.

    initializeRN.ts

    Initialize the RequestNetwork object using an Ethers Signer or Viem WalletClient.

    config.ts

    Use the config object to pass additional configuration options. Please replace the builderId with your own, arbitrarily chosen ID. This is used to track how many invoices your application creates.

    context.tsx

    Use a context provider to reinitialize the Request Network instance when the wallet changes.

    currencies.ts

    A list of custom currencies to extend the default currency list.

    types.d.ts

    Specify types to avoid TypeScript errors.

    Props

    Prop
    Type
    Description

    config

    IConfig

    Additional configuration parameters

    config.builderId

    string

    Unique builder ID, arbitrarily chosen, used for metrics

    config.dashboardLink

    string

    Path to dashboard page

    config.logo

    string

    Path to logo file

    Svelte
    Web Component
    Screenshot of @requestnetwork/invoice-dashboard 0.3.0

    🕹️ Try it out

    ▶️ Demo Video

    🏗️ Integration Video

    📦 View on NPM

    ℹ️ View Source

    The contract name is SingleRequestProxyFactory

    Key Benefits:

    • Universal Compatibility: Works with any system that can make standard crypto transfers.

    • No Code Changes: Integrate with Request Network without modifying existing smart contracts.

    • Exchange Friendly: Enable payments from centralized exchanges.

    How it works:

    1. Request: Create a request in the Request Network protocol

    2. Deploy: Deploy a unique Single Request Forwarder for your request

    3. Pay: The Payer sends funds to the Single Request Forwarder

    4. Complete: The Single Request Forwarder forwards the payment to the Payee and emits an event to enable payment detection.

    Integration Guide

    Create a request

    For a complete guide on request creation, see

    Deploy Single Request Forwarder

    To deploy a Single Request Forwarder, call deploySingleRequestForwarder() which takes in the following arguments:

    • requestData: the data of the created request

    • signer: An Ethers v5 Signer to sign the deployment transaction

    The deploySingleRequestForwarder() function automatically deploys the correct type of Single Request Forwarder based on the Request data passed into the function; either an Ethereum Single Request Forwarder or ERC20 Single Request Forwarder

    It returns

    • Single Request Forwarder Address

    Pay through a Single Request Forwarder

    Pay through a Single Request Forwarder using the RN SDK

    To pay a request through a Single Request Forwarder using the Request Network SDK, call payRequestWithSingleRequestForwarder() which takes in the following arguments:

    • singleRequestForwarderAddress: the address of the SRP deployed in the previous step.

    • signer: A wallet signer who is making the transfer of funds.

    • amount: Amount of funds that need to be transferred.

    Pay through a Single Request Forwarder using a Direct Transfer

    Once we have the Single Request Forwarder address, we can pay by directly transferring the money to the address itself. The Single Request Forwarder will automatically process the payment. For ERC20 payments, the process of paying with a Single Request Forwarder happens in two steps:

    • Transferring the tokens to the Single Request Forwarder

    • Make a zero-value transaction to the Single Request Forwarder (i.e. Send 0 ETH to the contract)

    Design Features:

    • Single Use: Each Single Request Forwarder deployment processes payments for a specific request.

    • Immutable Parameters: Payment details cannot be modified after deployment.

    • Fund Recovery: Built-in mechanisms to send stuck funds to the payment receiver.

    Smart Contract Addresses
    Single Request Forwarder Payment Flow

    For additional details, see the payment-network-erc777-stream-0.1.0 specification

    Create a Streaming Request

    To create a streaming request, like normal, but set the paymentNetwork parameter to the ERC777_STREAM payment network.

    Create the first request in a series

    Create subsequent requests in a series

    Tests

    See Github for tests showing usage.

    • https://github.com/RequestNetwork/requestNetwork/blob/master/packages/advanced-logic/test/extensions/payment-network/erc777/stream.test.ts

    • https://github.com/RequestNetwork/requestNetwork/tree/master/packages/payment-detection/test/erc777

    payment-network-erc20-fee-proxy
    Installation

    After creating a new Expo project, install the necessary dependencies:

    Setup

    1- Create a new file named index.js in the root of your project

    2- Add the following content to index.js

    3- Create a file named cryptoPolyfill.js in the root of your project

    4- Add the following content to cryptoPolyfill.js

    5- Create / Update metro.config.js to use the custom polyfills

    6- Update package.json to set the main entry point to index.js

    7- Ensure that app.json file includes the correct entry point

    Polyfills and Configurations

    Crypto Polyfills

    We've created a custom crypto polyfill (cryptoPolyfill.js) to provide the necessary cryptographic functions. This polyfill uses tweetnacl and node-forge libraries to implement various cryptographic operations.

    Why tweetnacl and node-forge?

    1. tweetnacl: It's a fast, secure, and easy-to-use cryptography library. It provides a pure JavaScript implementation of the NaCl cryptography library, which is particularly useful for generating random bytes, essential for cryptographic operations.

    2. node-forge: It provides a comprehensive set of cryptographic tools and utilities. It implements various cryptographic algorithms and protocols that are not natively available in React Native. It's used in our polyfill for operations like hashing, cipher creation, and PBKDF2 key derivation.

    Using these libraries allows us to implement a more complete set of cryptographic functions that closely mimic the Node.js crypto module, which is not available in React Native.

    Important Notes

    1. Ensure you're using compatible versions of React Native and Expo.

    2. The crypto polyfill may not cover all use cases. Test thoroughly and adjust as needed.

    3. Be cautious when handling private keys. Never expose them in your code or version control.

    4. The example code uses environment variables for private keys. Ensure you set these up correctly and securely.

    here
    Type
    Description

    Constructor

    The Request Network client

    npm install @requestnetwork/request-client.js
    Name
    Type
    Required?
    Description

    parameters

    Object used to create a new request

    signerIdentity

    Identity of the signer

    Returns

    string

    requestId

    string

    The ID of the request. Hash derived from the request contents. Stored alongside the request contents so can be used to look up a request.

    Instance Methods

    Name
    Description

    Wait for request to be persisted and indexed

    Unwrap the request contents

    Refresh the request data and balance

    Cancel a request

    Accept a request

    Increase the expected amount

    createRequest()
    Name
    Type
    Required?
    Description

    topic

    string

    Topic string

    updatedBetween

    Start time and end time

    Object

    Options

    Returns

    Promise<Request[]>

    Types and Interfaces

    Types.ITimestampBoundaries

    Name
    Type
    Required?
    Description

    from

    number (Unix timestamp)

    Start time

    to

    number (Unix timestamp)

    End time

    options

    Name
    Type
    Required?
    Description

    disablePaymentDetection

    boolean

    Disables payment detection

    disableEvents

    boolean

    Disabled events

    Name
    Type
    Required?
    Description

    signerIdentity

    required

    The value returned by getRequestFromId()

    refundInformation

    any

    optional

    Depends on the payment network

    Returns

    Promise<IRequestDataWithEvents>

    The payee identity and payer identity are NOT necessarily the same as the payment recipient (paymentAddress in the PaymentNetworkCreateParameters) or the payment sender (from address in the payment-subgraph). Conceptually, the payee and payer identities are used only for notifications and access control, NOT payment routing.

    Name
    Type
    Required
    Description

    type

    Identity type

    value

    string

    Identity address

    Types.Identity.TYPE

    Name
    Value
    Description

    ETHEREUM_ADDRESS

    'ethereumAddress'

    Externally owned account (EOA)

    ETHEREUM_SMART_CONTRACT

    'ethereumSmartContract'

    Smart contract account. Don't use this. It is not possible to create or update a request using an IIdentity of this type. This type was added to support creating/updating requests using multisig wallets via EIP-1271 but this feature has not yet been implemented.

    Name
    Type
    Required?
    Description

    deltaAmount

    number | string

    required

    The amount by which to reduce the expected amount

    signerIdentity

    required

    The value returned by getRequestFromId()

    paymentInformation

    any

    optional

    Depends on the payment network

    Returns

    Promise<IRequestDataWithEvents>

    Encrypt with an Ethereum private key

    Manipulating private keys must be done with care. Losing them can lead to a loss of data, privacy or non-repudiation safety!

    For an introduction to Encryption and Decryption in Request Network, see Private Requests using Encryption

    A request can be encrypted to make its details private to selected stakeholders. In this guide, we won't explain how encryption is managed under the hood. We will mention encryption or decryption of requests with payers and payees keys. Although in practice, we will use an intermediate symmetric key. See more details on .

    The transaction layer manages the encryption, .

    To manipulate encrypted requests you need a CipherProvider (recommended) or DecryptionProvider (deprecated). Both of them require direct access to the private key. They're best suited for backends.

    • EthereumPrivateKeyCipherProvider: Provides both encryption and decryption utilities.

    • EthereumPrivateKeyDecryptionProvider (deprecated) provides only decryption utilities.

    Create an encrypted request

    EthereumPrivateKeyCipherProvider

    See on .

    Then you can create an encrypted request:

    Note: You must give at least one encryption key you can decrypt with the decryption provider. Otherwise, an error will be triggered after the creation.

    EthereumPrivateKeyDecryptionProvider

    is deprecated in favor of

    Get invoice information from its request ID

    Let's step back for a second: the requester sent a request that he encrypted with the payer's public key, as well as with his own, to retrieve it later. This is an essential and typical example, but a request can be encrypted with many keys to give access to its status and details.

    If the decryption provider knows a private key matching one of the keys used at the creation, it can decrypt it. Like a clear request you can get it from its request id.

    Accepting/canceling an invoice information

    Like a clear request, you can update it if the decryption provider is instantiated with a matching private key.

    Enabling/Disabling Decryption

    Checking Capabilities

    Create Invoice Form

    A form for creating invoices in Request Network

    Screenshot of @requestnetwork/create-invoice-form 0.2.0

    The Create Invoice Form allows end-users to create an invoice using the Request Network. It is built using Svelte but compiled to a Web Component, making it usable in any web environment, regardless of the framework.

    Installation

    To install the component, use npm:

    Usage

    Usage in React and Next.js

    Follow the instructions below to add the Create Invoice Form to a React or Next.js app. For a video explaining how to integrate, see the

    Configure the Create Invoice Form web component by creating a reference to it, setting its properties, and passing the reference as a prop.

    The web component supports any wallet connector module built on top of . This provides the flexibility to use any wagmi-compatible wallet connector, such as .

    Initialize the RequestNetwork object using an Ethers Signer or Viem WalletClient.

    Use the config object to pass additional configuration options. Please replace the builderId with your own, arbitrarily chosen ID. This is used to track how many invoices your application creates.

    Use a context provider to reinitialize the Request Network instance when the wallet changes.

    A list of custom currencies to extend the default currency list.

    Specify types to avoid TypeScript errors.

    Props

    Prop
    Type
    Description

    Next Steps

    Payment Widget

    A widget that allows builders to accept crypto payments.

    Screenshot of @requestnetwork/[email protected]

    The Payment Widget allows builders to accept crypto payments on their websites with minimal integration. It is built using Svelte but complied into a Web Component, making it usable in any web environment, regardless of the framework.

    Installation

    Usage

    Usage in React and Next.js

    Props

    Prop
    Type
    Description

    SDK and Request Node Overview

    Request Network SDK

    The Request Network SDK is the set of software packages for interacting with the Request Network. The packages can be installed via npm and allow developers to:

    • Create new requests

    • Update requests

    • Pay requests

    • Retrieve requests

    • Detecting payments

    Request Node

    The Request Node is a software bundle that provides a gateway to the various layers of the Request Network Protocol:

    • persists the request content

    • Smart contracts persist the unique IPFS on-chain

    • indexes smart contract events

    This Request Node acts as a relay server which helps reduce friction and costs for the end user. The user signs the request contents, but the node funds the fees required to persist the contents in the various protocol layers.

    The Request Network Foundation operates several which builders can use to quickly test the protocol. However, for production, we advise you to run your own node which can be installed via either or .

    Interaction between SDK and Request Node

    Shown below is a diagram that depicts how the SDK and Node interact with the protocol.

    fromIdentity()

    Description

    Retrieve an array of requests from an Identity

    Parameters

    Name
    Type
    Required?
    Description

    Returns

    Promise<[]>

    Types and Interfaces

    Types.ITimestampBoundaries

    Name
    Type
    Required?
    Description

    options

    Name
    Type
    Required?
    Description

    PaymentReferenceCalculator

    Description

    Compute the payment reference, the last 8 bytes of a salted hash of the request ID.

    The payment reference is the parameter that ties the request to events emitted by on-chain payments via Request Network payment smart contracts.

    Usage

    Static method: calculate()

    Parameters

    Name
    Type
    Required
    Description

    Returns

    string

    Implementation

    accept()

    Description

    Accept a request

    Parameters

    Name
    Type
    Required?
    Description

    Returns

    Promise<>

    increaseExpectedAmountRequest()

    Description

    Increase the expected amount

    Parameters

    Name
    Type
    Required?
    Description

    Returns

    Promise<>

    Web3SignatureProvider

    Description

    Sign using a private key inside of a wallet

    Usage

    import { Web3SignatureProvider } from "@requestnetwork/web3-signature";

    Constructor Paramters

    Name
    Type
    Required?
    Description

    Private Requests using Encryption

    The Request Network protocol persists request contents on IPFS. When privacy is required, the SDK supports the creation and subsequent reading of encrypted requests.

    This is achieved by combining public key (asymmetric) & AES (symmetric) encryption.

    Encryption

    Request encryption overview

    The "request contents" are encrypted with a random AES key. This key is then encrypted with the public key of each stakeholder of the request. The stakeholders are the users and platforms who require access to the request contents. The encrypted request contents and the encrypted AES keys are then persisted to IPFS.

    Decryption

    The user retrieves the encrypted request content and the encrypted AES keys. They decrypt the AES key using their private key. Then they decrypt the request content using the AES key.

    Similarity to HTTPS

    The privacy and encryption mechanism used by Request Network has many similarities to HTTPS. If you're not familiar with HTTPS, we recommend reading

    Internal SDK Architecture

    Request is an open database for any type of payment request - from business invoices to reimbursements between friends. It aims to support products at any scale from startups to large organizations, from the private to the public sector.

    The Request Protocol is the core of Request. It's the bottom layer that defines and handles the data of a request and persists it to a distributed ledger to make Request open, trustless, secure, and resilient.

    This section is aimed at helping you understand how the protocol is structured, how it works and meets its requirements. It is particularly useful if you want to propose changes or implement them yourself.

    Overview

    The Request Protocol has one fundamental purpose: to persist, on a distributed ledger, data representing requests and to be able to retrieve these data efficiently.

    To achieve this, the Request Protocol follows the layered architecture pattern. Each layer is responsible for a specific task and a specific level of abstraction. This layered architecture is highly extensible and hopefully easy to understand.

    The protocol is composed of four layers:

    • Request logic

    • Transaction

    • Data Access

    • Storage

    This layered architecture allows package reusability and makes the protocol more upgradeable. For example, our current implementation uses Ethereum and IPFS. Still, suppose Arweave turns out to be a better solution for storing data in a decentralized database than IPFS. In that case, we can create a new storage layer that uses Arweave and make the data-access layer using this new package instead.

    Interface vs implementation

    The protocol follows a defined interface; each layer has to implement a specific interface. The interfaces for each layer can be found in the Types package of Request Network repository: .

    The following pages present the first implementation of the protocol used for the released version of Request V2 on mainnet.

    Batch Payments

    Process multiple payment requests or payouts efficiently in a single blockchain transaction

    Talk to an expert

    Discover how Request Network API can enhance your app's features - with us.

    Overview

    Installation

    The best way to access Request Network is using the Request Network SDK with a Request Node. In this way, the Request Node operator pays the protocol fee for creating requests and reduces the number of transactions signed by the end user resulting in a better user experience.

    The Request Network SDK is split into multiple packages so that Builders can pick and choose the features they need.

    SDK Packages

    These are the packages that we think would be most commonly used by Builders to build applications.

    Package

    Supported Chains

    Payments

    The payment proxy smart contracts enable the various payment types.

    The most widely deployed payment proxy is the ERC20FeeProxy. The most frequently used payment proxy is the ERC20ConversionProxy.

    For an explanation of what each smart contract does, see

    EthereumPrivateKeySignatureProvider

    Description

    Sign using a private key outside of a wallet

    Usage

    EthereumPrivateKeyDecryptionProvider

    Description

    Decrypt using a private key outside of a wallet

    Usage

    Request Logic

    This layer is responsible for the business logic of Request. This is where we define the data structure of a request.

    This layer has three responsibilities:

    • It defines the properties of the requests and the actions performed on them.

    • It's responsible for the signature of the actions performed to ensure the request stakeholder identities.

    import { Wallet, providers } from "ethers";
    
    const privateKey = 'WALLET_PRIVATE_KEY'
    const provider = const provider = new providers.JsonRpcProvider(
    	"RPC_URL",
    );
    
    const wallet = new Wallet(privateKey, provider);
    
    const recurringPaymentPermit = ... // from API response
    const signature = await wallet._signTypedData(
      recurringPaymentPermit.domain,
      recurringPaymentPermit.types,
      recurringPaymentPermit.values
    );
    
    npm install @requestnetwork/invoice-dashboard
    const request = await requestClient.createRequest(requestCreateParameters);
    
    const requestData = request.getData() 
    
    // In case of in-memory request
     const requestData = request.inMemoryInfo.requestData
    import { deploySingleRequestForwarder } from "@requestnetwork/payment-processor"
    
     const forwarderAddress = await deploySingleRequestForwarder(
          requestData,
          signer
        );
    
    console.log(`Single Request Forwarder Deployed At: ${forwarderAddress}`)
    // Single Request Forwarder Deployed At : 0x1234567890123456789012345678901234567890
    import { payRequestWithSingleRequestForwarder } from "@requestnetwork/payment-processor"
    import { utils } from "ethers"
    const paymentAmount = utils.parseUnits("1" , 18)
    await payRequestWithSingleRequestForwarder(forwarderAddress , signer, paymentAmount)
      // The paymentNetwork is the method of payment and related details.
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ERC777_STREAM,
        parameters: {
          expectedFlowRate: expectedFlowRate // number, Expected amount of request currency per second
          expectedStartDate: expectedStartDate // timestamp, Expected start of stream	
          paymentAddress: payeeIdentity,
        },
      },
      // The paymentNetwork is the method of payment and related details.
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ERC777_STREAM,
        parameters: {
          originalRequestId: 'abcd', // first reqeust in the series
          previousRequestId: 'abcd', // previous request in the series
          recurrenceNumber: 1, // 1 if previous request is original, 2+ otherwise
        },
      },
    npm install @requestnetwork/request-client.js @requestnetwork/types @requestnetwork/payment-processor @requestnetwork/epk-signature buffer eventemitter3 stream-browserify http-browserify https-browserify react-native-get-random-values tweetnacl node-forge [email protected]
    touch index.js
    touch cryptoPolyfill.js
    {
      "expo": {
        "entryPoint": "./index.js",
        ...
      }
    }
    npm install @requestnetwork/create-invoice-form
    npm install @requestnetwork/payment-widget
    last8Bytes(hash(lowercase(requestId + salt + address)))

    web3Provider

    ethers.Provider

    An ethers v5 Provider or viem WalletClient

    identity

    IIdentity

    Identity type and value

    updatedBetween

    ITimestampBoundaries

    Start time and end time

    options

    Object

    Options

    from

    number (Unix timestamp)

    Start time

    to

    number (Unix timestamp)

    End time

    disablePaymentDetection

    boolean

    Disable payment detection

    disableEvents

    boolean

    Disable events

    Request

    signerIdentity

    IIdentity

    required

    The value returned by getRequestFromId()

    refundInformation

    any

    optional

    Depends on the payment network

    IRequestDataWithEvents

    deltaAmount

    number | string

    required

    The amount by which to increase the expected amount

    signerIdentity

    IIdentity

    required

    The value returned by getRequestFromId()

    refundInformation

    any

    optional

    Depends on the payment network

    IRequestDataWithEvents
    RequestNetwork
    ICreateRequestParameters
    IIdentity

    reduceExpectedAmountRequest()

    Reduce the expected amount

    Others...

    Other features exist. Docs coming soon...

    waitForConfirmation()
    getData()
    refresh()
    cancel()
    accept()
    increaseExpectedAmountRequest()
    iTimestampBoundaries
    options
    IIdentity
    Types.Identity.TYPE
    IIdentity
    IPFS
    CID
    The Graph
    Request Nodes
    npm
    docker
    Interacting with Request Network via a Request Node
    https://github.com/RequestNetwork/requestNetwork/tree/master/packages/types
    Layers of the Request Protocol, each layer is described in the next section.
    HTTPS explained with carrier pigeons
    Request decryption overview
    Request Scan Demo Video
    github
    see more details on the Request Protocol section
    Github
    EthereumPrivateKeyDecryptionProvider
    EthereumPrivateKeyCipherProvider

    sellerInfo

    SellerInfo

    (Optional) Information about the seller

    sellerInfo.name

    string

    (Optional) Seller name

    productInfo

    ProductInfo

    (Optional) Information about the product

    productInfo.name

    string

    (Optional) Name of the product

    productInfo.description

    string

    (Optional) Description of the product

    productInfo.image

    string

    (Optional) Product image

    persistRequest

    boolean

    (Optional) Defaults to true, when set to false the request data is not persisted to the blockchain

    showRNBranding

    boolean

    (Optional) Defaults to true, when set to false the "Powered by Request Network" banner is hidden

    builderId

    string

    (Optional) An ID added to request to identify request created by builder

    onPaymentSuccess

    (request) => void

    (Optional) Event that returns the Request data once the payment is successful.

    onError

    (error) => void

    (Optional) Event that returns the error when something goes wrong.

    amountInUSD

    number

    The total of the purchase in US Dollars

    sellerAddress

    string

    Address that would accept the payments

    supportedCurrencies

    string[]

    An array of currency IDS that are supported by the seller

    sellerInfo.logo

    string

    (Optional) Seller logo

    🕹️ Try it out

    ▶️ Demo Video

    🏗️ Integration Video

    📦 View on NPM

    ℹ️ View Source

    import { EthereumPrivateKeyCipherProvider } from '@requestnetwork/epk-cipher';
    
    const cipherProvider = new EthereumPrivateKeyCipherProvider({
      # Warning: private keys should never be stored in clear, this is a basic tutorial
      key: '0x4025da5692759add08f98f4b056c41c71916a671cedc7584a80d73adc7fb43c0',
      method: RequestNetwork.Types.Encryption.METHOD.ECIES,
    });
    
    const requestNetwork = new RequestNetwork({
      cipherProvider,
      signatureProvider,
      useMockStorage: true,
    });
    const payeeEncryptionPublicKey = {
      key: 'cf4a1d0bbef8bf0e3fa479a9def565af1b22ea6266294061bfb430701b54a83699e3d47bf52e9f0224dcc29a02721810f1f624f1f70ea3cc5f1fb752cfed379d',
      method: RequestNetwork.Types.Encryption.METHOD.ECIES,
    };
    const payerEncryptionPublicKey = {
      key: '299708c07399c9b28e9870c4e643742f65c94683f35d1b3fc05d0478344ee0cc5a6a5e23f78b5ff8c93a04254232b32350c8672d2873677060d5095184dad422',
      method: RequestNetwork.Types.Encryption.METHOD.ECIES,
    };
    
    const invoice = await requestNetwork._createEncryptedRequest(
      {
        requestParameters,
        signer: requestParameters.payee,
        paymentNetwork,
      },
      [payeeEncryptionPublicKey, payerEncryptionPublicKey],
    );
    const invoiceFromRequestID = await requestNetwork.fromRequestId(requestId);
    
    const requestData = invoiceFromRequestID.getData();
    
    console.log(requestData);
    
    /* { 
     requestId,
     currency,
     expectedAmount,
     payee,
     payer,
     timestamp,
     extensions,
     version,
     events,
     state,
     creator,
     meta,
     balance,
     contentData,
    } */
    //Accept
    await request.accept(payerIdentity);
    
    //Cancel
    await request.cancel(payeeIdentity);
    
    //Increase the expected amount
    await request.decreaseExpectedAmountRequest(amount, payeeIdentity);
    
    //Decrease the expected amount
    await request.increaseExpectedAmountRequest(amount, payerIdentity);
    // Disable decryption
    cipherProvider.enableDecryption(false);
    // Check if decryption is enabled
    const isEnabled = cipherProvider.isDecryptionEnabled();
    // Re-enable decryption
    cipherProvider.enableDecryption(true);
    // Check if encryption is available
    const canEncrypt = cipherProvider.isEncryptionAvailable();
    // Check if decryption is available
    const canDecrypt = cipherProvider.isDecryptionAvailable();
    // Check if an identity is registered
    const isRegistered = await cipherProvider.isIdentityRegistered({
    type: 'ethereum_address',
    value: '0x123...'
    });// Some code
    import PaymentWidget from "@requestnetwork/payment-widget/react";
    
    export default function PaymentPage() {
      return (
        <PaymentWidget
          sellerInfo={{
            logo: "https://example.com/logo.png",
            name: "Example Store",
          }}
          productInfo={{
            name: "Digital Art Collection",
            description: "A curated collection of digital artworks.",
            image: "https://example.com/product-image.jpg",
          }}
          amountInUSD={1.5}
          sellerAddress="0x1234567890123456789012345678901234567890"
          supportedCurrencies={["REQ-mainnet","ETH-sepolia-sepolia","USDC-mainnet"]}
          persistRequest={true}
          onPaymentSuccess={(request) => {
            console.log(request);
          }}
          onError={(error) => {
            console.error(error);
          }}
        />
      );
    }

    config.colors.main

    string

    Hex color code for primary buttons and labels

    config.colors.secondary

    string

    Hex color code for for borders and accents

    requestNetwork

    The RequestNetwork instance

    wagmiConfig

    WagmiConfig

    Wallet connector config

    currencies

    Currency[]

    A list of custom currencies

    config

    IConfig

    Additional configuration parameters

    config.builderId

    string

    Unique builder ID, arbitrarily chosen, used for metrics

    config.dashboardLink

    string

    Path to dashboard page

    config.logo

    string

    Path to logo file

    🕹️ Try it out

    ▶️ Demo Video

    🏗️ Integration Video

    📦 View on NPM

    ℹ️ View Source

    create-invoice.tsx
    wagmiConfig.ts
    wagmi
    RainbowKit
    initializeRN.ts
    config.ts
    context.tsx
    currencies.ts
    types.d.ts
    Quickstart - Browser
    SDK Demo Apps
    Batch payments enable you to process multiple payment requests efficiently in a single blockchain transaction, reducing gas costs and simplifying multi-recipient workflows.

    Two Types of Batch Payments:

    Batch Pay Invoices

    Process previously created requests using their request IDs and receive payment calldata that can be executed on-chain to pay multiple requests simultaneously.

    Batch Payouts

    Submit new payment requests that are immediately processed, creating requests and returning payment calldata in a single API call for instant multi-recipient payments.

    Key Benefits

    • Gas Efficiency: Significantly reduce transaction costs by batching multiple payments

    • Simplified UX: Process up to 200 payments in a single transaction

    • Mixed Payment Types: Support ERC20, native tokens, and conversion payments in the same batch

    • Atomic Execution: All payments succeed or fail together, ensuring consistency

    Single Network Limitation: All requests in a batch must be on the same blockchain network. Need multi-network batch payments? Book a call to discuss this feature.

    Batch Processing Limits

    The theoretical limit for batch payments is 100-200 payments per transaction, depending on:

    • Payment complexity (ERC20 vs native tokens vs conversions)

    • Available block gas limit on the target network

    • Smart contract computational requirements

    For optimal performance, we recommend starting with smaller batches (10-50 payments) and scaling based on your network conditions.

    Batch Payment Workflow

    Endpoints

    Implementation Examples

    The following examples demonstrate how to implement batch payment calldata execution in your application. Note: The API returns unsigned transaction calldata - your application must handle sending these transactions to the blockchain.

    Batch Pay Invoices Example

    Batch Payouts Example

    Supported Payment Types

    Batch payments support mixing different payment types in a single transaction:

    • ERC20 Token Payments: Standard token transfers

    • Native Token Payments: ETH, MATIC, etc.

    • Conversion Payments: Requests denominated in one currency but paid in another (e.g., USD invoices paid with USDC)

    Key Implementation Notes

    Your Responsibility

    • API Call: Your application calls the Request Network API to get transaction data

    • Blockchain Execution: Your application executes the returned transaction data on the blockchain

    • Error Handling: Your application handles transaction failures and retries

    Best Practices

    1. Validate Addresses: Always validate recipient addresses before submitting batch payments

    2. Test on Testnets: Start with small batches on test networks before production deployment

    3. Handle Failures Gracefully: Implement proper error handling for transaction failures

    4. Gas Estimation: Consider gas costs when determining optimal batch sizes

    5. User Experience: Provide clear progress indicators for multi-step approval processes

    Error Handling

    Common error scenarios and their solutions:

    • Network Mismatch: Ensure all requests use the same blockchain network

    • Insufficient Funds: Verify payer has sufficient balance for all payments plus gas

    • Invalid Addresses: Validate all payee addresses before batch submission

    • Gas Limit Exceeded: Reduce batch size if hitting network gas limits

    Demo Application

    See the batch payment feature in action in our EasyInvoice demo application:

    EasyInvoice Batch Pay Invoices

    EasyInvoice: Batch Payouts

    For detailed information on all available endpoints and their parameters, please refer to the full Request Network API Reference.

    For more implementation details, explore the EasyInvoice source code.

    book a call
    Description

    Create, update, and retrieve requests.

    Sign requests using web3 wallets like Metamask

    Sign requests using Ethereum private keys

    Standards for data stored on Request, like invoice format

    Decrypt encrypted requests using Ethereum private keys

    Pay a request using a web3 wallet

    Web server that allows easy access to the Request system

    Tools for managing currency definitions

    For a list of internal SDK packages, see Internal SDK Packages.

    Component Packages

    These packages offer pre-built components for quickly integrating certain Request Network features.

    Package
    Description

    A form for creating invoices in Request Network

    A dashboard for viewing and paying invoices in Request Network

    A dialog box for granting third-party access to an encrypted invoice created via Request Finance

    Import the packages

    The Request Client library can be imported as ES6 or CommonJS modules.

    Internal SDK Packages

    These packages are published publicly but contain functions that are considered internal to the SDK Packages. It is less likely that a Builder would need to use these packages.

    Package
    Description

    Extensions to the protocol

    Indexing and batching of transactions

    Storage of Request data on Ethereum and IPFS, with custom indexing

    Serialize and deserialize object in the Request Network protocol

    Payment detection, to compute the balance.

    The Request business logic: properties and actions of requests

    Make sure to scroll horizontally to see all the payment proxy types! 😉

    Chain
    Chain Name
    Chain ID
    ERC20FeeProxy
    EthereumFeeProxy
    Erc20ConversionProxy
    EthConversionProxy
    BatchConversionPayments
    ERC20SwapToPay
    Erc20SwapToConversion
    ERC20TransferableReceivable
    ERC20EscrowToPay
    ERC20Proxy
    EthereumProxy
    BatchPayments (deprecated)

    Storage

    These smart contracts facilitate storing IPFS content addressable hashes (CIDs) on-chain.

    Chain
    Chain ID
    RequestHashStorage
    RequestOpenHashSubmitter

    Gnosis

    100

    Sepolia

    11155111

    Ethereum Mainnet (deprecated)

    1

    REQ Token and Burn Mechanism

    The REQ Token is on Ethereum Mainnet. The burn contracts facilitate locking xDAI on Gnosis, bridging xDAI to Ethereum, swapping xDAI for REQ, and burning the REQ.

    Chain
    Chain ID
    lockForREQBurn
    DaiBasedREQBurner
    RequestToken

    Ethereum Mainnet

    1

    Gnosis

    100

    Smart Contracts Overview
    Constructor Paramters
    Name
    Type
    Required?
    Description

    signatureParameter

    Signing method and private key

    Types and Interfaces

    ISignatureParameters

    Name
    Type
    Required?
    Description

    method

    Signing method

    privateKey

    string

    Private key

    Types.Signature.METHOD

    Name
    Value

    ECDSA

    'ecdsa'

    "Vanilla" ECDSA

    ECDSA_ETHEREUM

    'ecdsa-ethereum'

    Ethereum ECDSA with the prefix "\x19Ethereum Signed Message:\n"

    Constructor Parameters
    Name
    Type
    Required?
    Description

    decryptionParameters

    Decryption method and private key

    Types and Interfaces

    IDecryptionParameters

    Name
    Type
    Required?
    Description

    method

    Decryption method

    key

    string

    Private key

    Types.Decryption.METHOD

    Name
    Value

    ECIES

    'ecies'

    Elliptic Curve Integrated Encryption Scheme (ECIES). An asymmetric key cipher.

    AES256_CBC

    'aes256-cbc'

    Advanced Encryption Standard (AES). A symmetric key cipher with keys of length 256 in CBC mode.

    AES256_GCM

    'aes256-gcm'

    Advanced Encryption Standard (AES). A symmetric key cipher with keys of length 256 in GCM mode.

    It manages extensions that can be created to extend the features of the Request Protocol through the Advanced Logic package.

    https://github.com/RequestNetwork/requestNetwork/tree/master/packages/request-logic

    Actions

    Actions are the essential elements that compose a request. From this layer's point of view, a request is simply a list of different actions.

    Example of a request in Request Logic represented by a list of actions
    • The payee creates the request requesting 1 ETH to the payer

    • The payer accepts the request

    • The payer increases the expected amount of the request by 1 ETH (the expected amount of the request can only be increased by the payer and decreased by the payee)

    Given the list of these actions, we can interpret the state of the request. The example above describes a request that has been accepted by the payer where he will have to pay 2 ETH to the payee.

    Note that the hash of the action determines the request Id. Therefore, this action doesn't specify the request Id since it doesn't exist yet. The update actions (accept and increaseExpectedAmount) specify the request Id in their data.

    There are two kinds of action:

    • Create: This action is not related to an existing request, it will create a new one

    • Update: All other actions, it will update the state of an existing request

    Signature

    In addition to providing the structure to form an action composing a request, the request logic layer is also responsible for signing the action.

    To abstract the signing process from the layer (and eventually be able to use it in other packages), the signing process is done through external packages named signature providers.

    The protocol repository currently contains two signature provider packages:

    • epk-signature

    • web3-signature

    Both packages use the Elliptic Curve Digital Signature Algorithm (ECDSA) used in Ethereum. web3-signature will connect to Metamask to ask users to sign requests. epk-signature uses private keys that are stored in the clear and managed manually.

    The web3-signature provider should be used to create a fully-decentralized solution where the users manage their own private keys. The epk-signature provider is used to manage the private keys on behalf of the users. It's never a good idea to let users handle plain private keys.

    Configure Logo and Colors

    ✅

    Inject your own custom currency list

    ✅

    Download Receipt as PDF

    ✅

    Optimism

    USDC, USDT, DAI, ETH

    Moonbeam

    USDC (multichain), USDC (wormhole)

    Fantom

    FTM

    zkSync Era

    ETH

    Base

    ETH

    rnf_invoice format

    config.colors.main

    string

    Color used for primary buttons and labels

    config.colors.secondary

    string

    Color used for borders and accents

    requestNetwork

    RequestNetwork

    The RequestNetwork instance

    wagmiConfig

    WagmiConfig

    Wallet connector config

    currencies

    Currency[]

    A list of custom currencies

    requestId

    string

    The ID of the request

    salt

    string

    The salt of the request

    address

    string

    Payment recipient address

    Migrate to V2

    A comprehensive guide to help you transition from V1 to V2 of the Request Network API

    The Request Network API V2 introduces significant improvements while maintaining backward compatibility. This guide provides a comprehensive overview of the breaking changes between V1 and V2, along with a step-by-step migration guide.

    V1 API Deprecation Notice

    V1 of the Request Network API is deprecated and in security-fixes-only mode. This means:

    • Deprecated: We strongly recommend upgrading to V2

    • Security-fixes-only: V1 will only receive critical security patches, no new features, enhancements, or non-security bug fixes

    Please migrate to V2 as soon as possible to ensure continued support and access to the latest features.

    Important: V2 is designed to coexist with V1. You can migrate incrementally and don't need to migrate all endpoints at once. See the for documentation of the V1 endpoints.

    Breaking Changes from V1 to V2

    Core Architectural Changes

    Path Parameter Changes

    • V1: Uses paymentReference as the path parameter

    • V2: Uses requestId as the path parameter

    Response Schema Standardization

    • V1: Returns requestID (uppercase D) in create responses

    • V2: Returns requestId (lowercase d) in create responses

    Enhanced Validation

    • V2: Stricter type checking and validation schemas

    • V1: More permissive validation

    API Interface Differences

    Endpoint Structure Changes

    Request Endpoints

    Feature
    V1 Endpoint
    V2 Endpoint

    Payment Endpoints

    Feature
    V1 Endpoint
    V2 Endpoint

    Request Schema Differences

    Create Request Schema

    V1 Schema:

    V2 Schema:

    Create Response Schema

    V1 Response:

    V2 Response:

    Payment Query Parameter Differences

    Pay Request Query Parameters

    V1 Query Parameters:

    V2 Query Parameters:

    Step-by-Step Migration Guide

    Phase 1: Assessment and Planning

    1. Audit Your Current Integration

      • List all V1 endpoints you're currently using

      • Identify which features you need (basic payments vs advanced features)

    2. Choose Migration Strategy

    Phase 2: Update Path Parameters

    Before (V1):

    After (V2):

    Phase 3: Update Response Handling

    Before (V1):

    After (V2):

    Phase 4: Update Error Handling

    V2 has enhanced error responses with more specific error codes:

    Phase 5: Testing and Validation

    1. Test Core Functionality

      • Create requests using V2 endpoints

      • Verify payment flows work correctly

      • Check response formats match expectations

    Support and Resources

    • Migration Support: with our team for migration assistance

    • GitHub Examples: Check the easy-invoice repository for V2 implementation examples

    Backward Compatibility

    V1 endpoints will continue to work during the migration period. However, we recommend migrating to V2 to access improvements and future features:

    • Enhanced security and validation

    • Better error handling and debugging

    • Improved webhook events

    • Access to new features as they are released

    V2 is the foundation for all future Request Network API features and improvements.

    Hinkal Private Payments

    The Request Network SDK supports Hinkal Private Payments using ERC-20 tokens. Hinkal is a middleware and suite of smart contracts on EVM-compatible chains that leverage zero-knowledge proofs and private addresses to facilitate compliant and private transactions.

    Each public address has exactly one Hinkal private address.

    The @requestnetwork/payment-processor package provides functions to:

    • Pay a request from a Hinkal private address to a public address: such that the payment sender's public address never appears on-chain.

    • Deposit to a Hinkal private address from a public address: such that the payment recipient's public address never appears on-chain. Callers can choose to deposit to their own private address or someone else's private address.

    Paying a request where the payment recipient address is a Hinkal private address is not supported because the Request Network payment proxy smart contracts can only send funds to public addresses. Consider using instead.

    Benefits

    • Privacy: Obfuscates payer address when paying a request.

    • Compliance: Ensures transactions adhere to regulatory requirements. See for details

    Supported Chains

    See for a list of chains on which Hinkal Private Payments are supported.

    Installation

    To use Hinkal Private Payments, install the necessary package:

    Usage

    Pay a request from a Hinkal private address

    To pay a request from a Hinkal private address to a public address, where only the payment sender's address is obfuscated, use the `payErc20FeeProxyRequestFromHinkalShieldedAddress()` function. Ensure the payment sender's Hinkal private address has a positive balance using

    Strongly consider using to keep the request contents private, including the payer and payee identity addresses, when paying requests from a Hinkal private address. Revealing the payer and payee identity addresses increases the likelihood of un-shielding the payment sender's address via on-chain analysis.

    See for how to instantiate a RequestNetwork and Signer

    Deposit to a Hinkal private address

    To deposit funds to a Hinkal private address from a public address, where only the payment recipient's address is obfuscated, use the sendToHinkalShieldedAddressFromPublic() function.

    • Deposit to own Hinkal shielded address: omit the recipientInfo argument

    • Deposit to someone else's Hinkal shielded address: set recipientInfo to the shielded address of the payment recipient.

    Hinkal private addresses must be shared out-of-band. This SDK doesn't offer functions for sharing Hinkal private addresses.

    See for how to instantiate a Signer

    Content Security Policy

    The Hinkal SDK depends on , a powerful library that enables local zero-knowledge proving in browser and Node.js environments. Snarkjs leverages to perform complex cryptographic computations efficiently.

    As a result, any client-side application integrating the Hinkal SDK must adjust its Content-Security-Policy to allow the wasm-unsafe-eval directive under the script-src setting. This configuration ensures that the cryptographic processes can execute properly.

    See for more details.

    Details

    For more details about Hinkal Private Payments, refer to on GitHub.

    payRequest()

    Usage

    Parameters

    Name
    Type
    Required?
    Description

    Returns

    Promise<ethers.ContractTransaction>

    This is what ethers returns after submitting a transaction.

    Types and Interfaces

    IConversionPaymentSettings

    Name
    Type
    Required?
    Description

    Encrypt with a wallet signature using Lit Protocol

    This document outlines how to encrypt and decrypt requests using Lit Protocol. Encryption and decryption are performed using the end-user's wallet signatures, ensuring only they can access the data. Neither Request Network nor Lit Protocol can access the data without consent from the user.

    This allows the end-user to own their data without requiring them to know about or manage their public key, as is the case when they .

    Encryption with Lit Protocol supports the Add Stakeholder feature for adding view access to a 3rd party other than the payee or payer.

    The LitCipherProvider is suitable for both frontend and backend use.

    Introduction

    This implementation utilizes a two-step encryption process to secure sensitive data within requests:

    import { ethers } from 'ethers';
    
    // Get unsigned calldata to pay existing requests by their IDs
    const batchPayResponse = await fetch('https://api.request.network/v2/payouts/batch', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your-api-key',
        'x-platform-id': 'your-platform-id'
      },
      body: JSON.stringify({
        requestIds: [
          "01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb",
          "02f384fdd39e5c627e04b1f2e6fd60593783b8863c3c789197f5bd381527b8ecd"
        ],
        payer: "0x2e2E5C79F571ef1658d4C2d3684a1FE97DD30570"
      })
    });
    
    const { batchPaymentTransaction, ERC20ApprovalTransactions } = await batchPayResponse.json();
    
    // Your app must implement sending these transactions to the blockchain
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    const signer = provider.getSigner();
    
    // 1. Handle ERC20 approvals if needed
    for (const approval of ERC20ApprovalTransactions) {
      const tx = await signer.sendTransaction(approval);
      await tx.wait();
    }
    
    // 2. Send the batch payment transaction
    const batchTx = await signer.sendTransaction(batchPaymentTransaction);
    await batchTx.wait();
    // Create new requests and process them immediately
    const batchPayResponse = await fetch('https://api.request.network/v2/payouts/batch', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your-api-key',
        'x-platform-id': 'your-platform-id'
      },
      body: JSON.stringify({
        requests: [
          {
            payee: "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
            amount: "10",
            invoiceCurrency: "USD",
            paymentCurrency: "USDC-sepolia"
          },
          {
            payee: "0xb07D2398d2004378cad234DA0EF14f1c94A530e4",
            amount: "25.50",
            invoiceCurrency: "EUR", 
            paymentCurrency: "DAI-sepolia"
          }
        ],
        payer: "0x2e2E5C79F571ef1658d4C2d3684a1FE97DD30570"
      })
    });
    
    const { batchPaymentTransaction, ERC20ApprovalTransactions } = await batchPayResponse.json();
    
    // Your app must implement the blockchain transaction execution
    // (same pattern as Batch Pay Invoices example above)
    import { RequestNetwork } from '@requestnetwork/request-client.js';
    import { Web3SignatureProvider } from '@requestnetwork/web3-signature';
    import { payRequest } from '@requestnetwork/payment-processor';
    const { RequestNetwork } = require('@requestnetwork/request-client.js');
    const { Web3SignatureProvider } = require('@requestnetwork/web3-signature');
    const { payRequest } = require("@requestnetwork/payment-processor");
    import { EthereumPrivateKeySignatureProvider } from "@requestnetwork/epk-signature";
    import { EthereumPrivateKeyDecryptionProvider } from "@requestnetwork/epk-decryption";
    import { PaymentReferenceCalculator } from "@requestnetwork/request-client.js";
    import { payRequest } from "@requestnetwork/payment-processor";

    @requestnetwork/smart-contracts

    Sources and artifacts of the smart contracts

    @requestnetwork/thegraph-data-access

    Storage of Request data on Ethereum and IPFS, indexed by TheGraph

    @requestnetwork/transaction-manager

    Creates transactions to be sent to Data Access, managing encryption

    @requestnetwork/types

    Typescript types shared across @requestnetwork packages

    @requestnetwork/utils

    Collection of tools shared between the @requestnetwork packages

    @requestnetwork/request-client.js
    @requestnetwork/web3-signature
    @requestnetwork/epk-signature
    @requestnetwork/data-format
    @requestnetwork/epk-decryption
    @requestnetwork/payment-processor
    @requestnetwork/request-node
    @requestnetwork/currency
    @requestnetwork/create-invoice-form
    @requestnetwork/invoice-dashboard
    @requestnetwork/add-stakeholder
    @requestnetwork/advanced-logic
    @requestnetwork/data-access
    @requestnetwork/ethereum-storage
    @requestnetwork/multi-format
    @requestnetwork/payment-detection
    @requestnetwork/request-logic
    ISignatureParameters
    Types.Signature.METHOD
    IDecryptionParameters
    Types.Decryption.METHOD
    A video showing the Request Invoicing template where you can create, pay, and list all your requests.
    A video showing how to integrate the Request Invoicing web components into a Next.js application.

    Goerli (deprecated)

    goerli

    5

    Optimism

    optimism

    10

    Arbitrum One

    arbitrum-one

    42161

    Base

    base

    8453

    zkSync Era

    zksyncera

    324

    zkSync Goerli (deprecated)

    zksynceratestnet

    280

    Gnosis

    xdai

    100

    Polygon

    matic

    137

    Mumbai

    mumbai

    80001

    BSC

    bsc

    56

    BSC Testnet

    bsctest

    97

    Celo

    celo

    42220

    Alfajores

    alfajores

    44787

    Fantom

    fantom

    250

    Tombchain

    tombchain

    6969

    Core

    core

    1116

    Avalanche

    avalanche

    43114

    Fuse

    fuse

    122

    Moonbeam

    moonbeam

    1284

    Ronin

    ronin

    2020

    Mantle

    mantle

    5000

    Mantle Testnet

    mantle-testnet

    5001

    NEAR

    N/A

    N/A

    NEAR Testnet

    N/A

    N/A

    Ethereum Mainnet

    mainnet

    1

    Sepolia

    sepolia

    11155111

    Goerli (deprecated)

    5

    A video showing the Request Checkout Payment Widget .
    A video showing the Request Checkout Payment Widget integration.

    Incremental: Migrate endpoints one by one (recommended)

  • Full Migration: Switch all endpoints at once

  • Parallel: Run V1 and V2 side by side

  • Enhanced Validation Testing

    • Test stricter type checking

    • Verify improved error responses

  • Performance Testing

    • Compare response times between V1 and V2

    • Test with realistic data volumes

  • Create Request

    POST /v1/request

    POST /v2/request

    Get Request

    GET /v1/request/{paymentReference}

    GET /v2/request/{requestId}

    Get Request Status

    GET /v1/request/{paymentReference}/status

    GET /v2/request/{requestId}/status

    Get Payment Calldata

    GET /v1/request/{paymentReference}/pay

    GET /v2/request/{requestId}/pay

    Get Payment Routes

    GET /v1/request/{paymentReference}/routes

    GET /v2/request/{requestId}/routes

    Full API Reference
    Book a call
    Declarative Payment
    Hinkal Compliance
    Hinkal Supported Chains
    Encryption and Decryption
    Quickstart - Browser
    Quickstart - Browser
    snarkjs
    WebAssembly
    Hinkal SDK Integration
    Pull Request #1482
    Deposit to a Hinkal private address
    {
      "amount": "string",
      "payee": "string",
      "invoiceCurrency": "string",
      "paymentCurrency": "string"
    }
    {
      "amount": "string",
      "payee": "string",
      "invoiceCurrency": "string",
      "paymentCurrency": "string"
      // V2 accepts additional optional fields for extended functionality
    }
    {
      "requestID": "string",  // Note: uppercase D
      "paymentReference": "string"
    }
    {
      "requestId": "string",  // Note: lowercase d
      "paymentReference": "string"
    }
    interface PayRequestQueryV1 {
      payerAddress?: string;
      routeId?: string;
    }
    interface PayRequestQueryV2 {
      payerAddress?: string;
      routeId?: string;
      // Enhanced validation with stricter type checking
    }
    // Get request status
    const response = await fetch(`/v1/request/${paymentReference}/status`);
    
    // Get payment calldata
    const payData = await fetch(`/v1/request/${paymentReference}/pay?payerAddress=${address}`);
    // Get request status
    const response = await fetch(`/v2/request/${requestId}/status`);
    
    // Get payment calldata
    const payData = await fetch(`/v2/request/${requestId}/pay?payerAddress=${address}`);
    const createResponse = await fetch('/v1/request', {
      method: 'POST',
      body: JSON.stringify(requestData)
    });
    
    const { requestID, paymentReference } = await createResponse.json();
    // Note: requestID with uppercase D
    const createResponse = await fetch('/v2/request', {
      method: 'POST',
      body: JSON.stringify(requestData)
    });
    
    const { requestId, paymentReference } = await createResponse.json();
    // Note: requestId with lowercase d
    try {
      const response = await fetch('/v2/request', {
        method: 'POST',
        body: JSON.stringify(requestData)
      });
    
      if (!response.ok) {
        const error = await response.json();
    
        // V2 provides more detailed error information
        console.error('Error:', error.message);
        console.error('Status Code:', error.statusCode);
        console.error('Error Code:', error.error);
      }
    } catch (error) {
      console.error('Request failed:', error);
    }
    npm install @requestnetwork/payment-processor
    import { 
      payErc20FeeProxyRequestFromHinkalShieldedAddress,
    } from '@requestnetwork/payment-processor';
    
    // Instantiation of `RequestNetwork` and `Signer` omitted for brevity
    
    const request = await requestClient.fromRequestId('insert request id');
    const requestData = request.getData();
    
    const relayerTx = await payErc20FeeProxyRequestFromHinkalShieldedAddress(
      requestData,
      signer,
    );
    import { 
      sendToHinkalShieldedAddressFromPublic,
    } from '@requestnetwork/payment-processor';
    
    // Instantiation of `Signer` omitted for brevity
    
    const recipientShieldedAddress = '142590100039484718476239190022599206250779986428210948946438848754146776167,0x096d6d5d8b2292aa52e57123a58fc4d5f3d66171acd895f22ce1a5b16ac51b9e,0xc025ccc6ef46399da52763a866a3a10d2eade509af27eb8411c5d251eb8cd34d'
    const tx = await sendToHinkalShieldedAddressFromPublic({
        signerOrProvider: paymentSender,
        tokenAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC on Base
        amount: '1000000', // 1 USDC
        recipientInfo: recipientShieldedAddress, // omit to deposit to own Hinkal shielded address
    })

    Settings for conversion payments

    request

    IRequestData

    The request object

    signerOrProvider

    ethers.providers.Web3Provider | ethers.Signer = getProvider()

    An ethers v5 Provider. See for explanation how to wrap a viem WalletClient to look like an ethers v5 Provider.

    amount

    ethers.BigNumberish

    The amount to pay. Defaults to the expected amount of the request.

    overrides

    Omit<ethers.providers.TransactionRequest, 'to' | 'data' | 'value'>

    Override transaction settings like baseFee and maxPriorityFee

    paymentSettings

    currency

    ICurrency

    The currency in which the payment is made, not the currency in which the request is denominated.

    maxToSpend

    ethers.BigNumberish

    The maximum input currency to spend on conversion payments. Protects the user from rapidly changing exchange rates.

    currencyManager

    ICurrencyManager

    A Currency manager handles a list of currencies and provides utility to retrieve and change format

    1. Symmetric Encryption: The data is first encrypted using a randomly generated symmetric key (e.g., AES-256). This provides efficient encryption for larger data payloads.

    2. Asymmetric Encryption with Lit Protocol: The symmetric key is then encrypted using Lit Protocol's decentralized key management network. Only authorized parties (payer and payee) can access the symmetric key and decrypt the data.

    For a deeper introduction to Encryption and Decryption in Request Network, see Private Requests using Encryption

    Benefits

    • Ease-of-use: Encrypt using a signature instead of a public key.

    • Efficiency: Symmetric encryption is efficient for large data, while Lit Protocol secures the key.

    • Decentralized Access Control: Lit Protocol ensures that only authorized parties can decrypt the data.

    Architecture

    The system consists of three main components:

    • Request Network: Handles the creation, storage, and lifecycle of payment requests on the blockchain.

    • Lit Protocol: Provides a decentralized key management network and encryption capabilities.

    • Wallet Addresses: Used as the primary identifiers for access control in Lit Protocol.

    Workflow

    Encryption Process

    1. Request Creation: The payer creates a request object using the Request Network SDK.

    2. Symmetric Key Generation: A unique symmetric key is randomly generated.

    3. Data Encryption: The payee and payer encrypt the sensitive data within the request using the generated symmetric key.

    4. Encrypt Symmetric Key with Lit:

      • Define Access Control Conditions: The payee and payer define access control conditions using Lit Actions, specifying that only the Ethereum addresses of the payer and payee can decrypt the symmetric key.

      • Encrypt with Lit: The payee and payer encrypt the symmetric key using Lit's encryptString function, leveraging their wallet to sign the encryption.

    5. Store Encrypted Data: The payee and payer store the following on the Request Network:

      • Encrypted request data

      • Lit access control conditions

      • Encrypted symmetric key

    Decryption Process

    1. Retrieve Request: The payer and payee retrieve the following request data from the Request Network:

      1. Encrypted request data

      2. Lit access control conditions

      3. Encrypted symmetric key

    2. Decrypt Symmetric Key with Lit: The payer and payee use Lit's decryptString function with their wallet to decrypt the encrypted symmetric key. Lit Protocol verifies the payer's and payee's addresses against access control conditions. If authorized, the symmetric key is decrypted.

    3. Decrypt Data: The payer and payee use the decrypted symmetric key to decrypt the sensitive data.

    Installation

    Usage

    Creating Encrypted Requests

    Decrypting Requests

    Disable Decryption

    Decryption Requirements

    1. The wallet address must be included in the original encryption parameters

    2. Session signatures must be valid

    3. Decryption must be enabled

    4. The Lit Protocol client must be connected

    Cleanup

    ICipherProvider Interface

    Encrypt with an Ethereum private key
    RequestNetwork

    Smart Contracts Overview

    This page is missing the RequestToken, DAIbasedREQBurner, lockForREQBurn, ChainlinkConversionPath contracts

    Contracts Overview

    Request Network smart contracts are available .

    Contracts type

    There are three types of contracts

    • Storage - These store for Requests stored in IPFS.

    • Payments - These process various payment types, also known as , and are deployed across many .

    • REQ Token and Burn Mechanism - These lock, bridge, and burn REQ tokens each time a Request is stored.

    Storage

    Declares data hashes and collects the fees.

    After a request has been sent to IPFS, the hash is declared to the whole request network system through the RequestHashStorage.

    Anyone can submit hashes.

    Manages the fees for the creation of a request.

    This contract is the entry point to retrieve all the hashes of the request network system.

    Payments

    Performs an ERC20 token transfer with a payment reference and a transfer to a second address for the payment of a fee.

    Performs an ERC20 token transfer with a payment reference and a transfer to a second address.

    This contract performs an Ethereum transfer with a Fee sent to a third address and stores a reference.

    This contract performs an Ethereum transfer sent to a third address and stores a reference.

    This contract allows users to lock funds in an escrow and perform payments in ERC20. It contains a refund and emergency feature to unlock funds if needed.

    This contract makes multiple conversion payments with a payment references, in one transaction.

    This contract makes multiple payments with payment references, in one transaction.

    This contract makes multiple payments with references, in one transaction, without conversion.

    This contract swaps ERC20 tokens before paying a request such that the payer sends currency A, but payee receives currency B.

    Swap-to-Pay is different from Conversion. For details see

    This contract uses a chainlink price feed to pay a request denominated in one currency (usually a fiat currency like USD) but paid in an on-chain currency. This variant supports ERC20 payments.

    This contract uses a chainlink price feed to pay a request denominated in one currency (usually a fiat currency like USD) but paid in an on-chain currency. This variant supports native currency payments.

    This contract combines "conversion" and "swap-to-pay". It executes an ERC20 swap before paying a request denominated in one currency (usually a fiat currency like USD) but paid in an on-chain currency. This variant supports ERC20 payments.

    This contract allows minting requests as NFTs thus allowing them to be transferred. The owner of the request NFT receives the payment.

    A contract that allows payment through the without having to make a function call.

    A contract that allows payment through without having to make a function call.

    A factory smart contract responsible for deploying and contracts.

    Crypto-to-fiat Payments

    A Guide to Crypto-to-fiat Payments, Compliance, and Webhooks with the Request Network API

    Crypto-to-fiat payments allow a Payer to pay a Request in cryptocurrency, while the Payee receives fiat currency directly in their bank account. This is achieved by combining the Request Network crypto payment with offramp infrastructure. This requires prerequisite compliance (KYC/Agreement) and bank account registration (payment detail) flows.

    includes a reference implementation for this flow.

    Crypto-to-fiat payments are only available in version 2 of the Request Network API.

    Crypto-to-fiat Supported Chains and Currencies

    Use your own signature mechanism

    In a previous chapter, we used the signature providers @requestnetwork/web3-signature and @requestnetwork/epk-signature (this one is made for test purpose). But, if you are not using web3, you need to inject your own signature mechanism to the request client. This is fairly simple, you need to implement a class following this interface: (see on )

    Example 1

    For example, your own package to sign needs an ethereum address and return the signature as a hexadecimal string:

    Your signature provider would look like:

    npm install @requestnetwork/lit-protocol-cipher @requestnetwork/request-client.js [email protected]
    import { LitProtocolCipherProvider } from '@requestnetwork/lit-protocol-cipher';
    import { RequestNetwork, Types } from '@requestnetwork/request-client.js';
    import { LitNodeClient } from '@lit-protocol/lit-node-client';
    
    // Node connection configuration
    const nodeConnectionConfig = {
      baseURL: 'https://req-node.request.network',
      connectionTimeout: 10000,
      retry: {
        retries: 3
      }
    };
    
    // Initialize Lit Node Client
    const litClient = new LitNodeClient({
      litNetwork: 'datil',
      debug: false
    });
    
    // Initialize the Lit Provider
    const litProvider = new LitProtocolCipherProvider(
      litClient,
      nodeConnectionConfig,
      'ethereum' // optional chain parameter
    );
    
    // Connect to Lit Network
    await litProvider.initializeClient();
    
    // Initialize wallet and get session signatures
    const wallet = new Wallet('your-private-key');
    const address = await wallet.getAddress();
    
    // Get session signatures
    await litProvider.getSessionSignatures(wallet, address);
    
    // Enable decryption
    litProvider.enableDecryption(true);
    
    // Initialize Request Network
    const requestNetwork = new RequestNetwork({
      cipherProvider: litProvider,
      signatureProvider: new Web3SignatureProvider(wallet),
      nodeConnectionConfig
    });
    const payeeIdentity = {
      type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
      value: 'payee-ethereum-address'
    };
    
    const payerIdentity = {
      type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
      value: 'payer-ethereum-address'
    };
    
    // Define encryption parameters
    const encryptionParams = [
      {
        key: payeeIdentity.value,
        method: Types.Encryption.METHOD.KMS
      },
      {
        key: payerIdentity.value,
        method: Types.Encryption.METHOD.KMS
      }
    ];
    
    // Create request parameters
    const requestCreateParameters = {
      requestInfo: {
        currency: {
          type: Types.RequestLogic.CURRENCY.ERC20,
          value: '0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C',
          network: 'sepolia',
        },
        expectedAmount: '1000000000000000000',
        payee: payeeIdentity,
        payer: payerIdentity,
        timestamp: Utils.getCurrentTimestampInSecond(),
      },
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
        parameters: {
          paymentNetworkName: 'sepolia',
          paymentAddress: payeeIdentity.value,
          feeAddress: '0x0000000000000000000000000000000000000000',
          feeAmount: '0',
        },
      },
      contentData: {
        reason: '🍕',
        dueDate: '2023.06.16',
      },
      signer: payeeIdentity,
    };
    
    // Create the encrypted request
    const request = await requestNetwork._createEncryptedRequest({
      requestParameters: requestCreateParameters,
      encryptionParams
    });
    // Fetch an existing request
    const requestId = "request_id_here";
    const request = await requestNetwork.fromRequestId(requestId);
    
    // If you have the correct permissions (wallet address in encryption params),
    // and decryption is enabled, the data will be automatically decrypted
    const requestData = await request.getData();
    
    // The decrypted data will include:
    console.log({
      requestInfo: requestData.requestInfo,
      paymentNetwork: requestData.paymentNetwork,
      contentData: requestData.contentData,
      state: requestData.state
    });
    // Disable decryption
    litProvider.enableDecryption(false)
    // Proper cleanup sequence
    try {
      // First disconnect the wallet
      await litProvider.disconnectWallet();
      
      // Then disconnect the client
      await litProvider.disconnectClient();
    } catch (error) {
      console.error('Cleanup error:', error);
    }
    interface ICipherProvider {
      encrypt(data: any, options: any): Promise<any>;
      decrypt(encryptedData: any, options: any): Promise<any>;
      isEncryptionAvailable(): boolean;
      isDecryptionAvailable(): boolean;
      enableDecryption(option: boolean): void;
      isDecryptionEnabled(): boolean;
    }
    Create a request

    Now you can inject it into the request client:

    ## Example 2

    For example, your own package to sign needs an internal identifier and return the signature as a Buffer:

    Your signature provider would look like:

    Now you can inject it into the request client:

    export interface ISignatureProvider {
      supportedMethods: Signature.METHOD[];
      supportedIdentityTypes: Identity.TYPE[];
    
      sign: (data: any, signer: Identity.IIdentity) => Promise<Signature.ISignedData>;
    }
    class mySignaturePackage {
      /**
       * Sign data
       *
       * @param data the data to sign
       * @param address the address to sign with
       * @returns a promise resolving the signature
       */
      public async sign(data: any, address: string): Promise<string>;
    }
    github
    import { IdentityTypes, SignatureProviderTypes, SignatureTypes } from '@requestnetwork/types';
    import Utils from '@requestnetwork/utils';
    
    // Your package
    import mySignaturePackage from 'mySignaturePackage';
    
    /**
     * Implementation of the signature provider for my wallet
     */
    export default class MySignatureProvider implements SignatureProviderTypes.ISignatureProvider {
      /** list of supported signing methods */
      public supportedMethods: SignatureTypes.METHOD[] = [SignatureTypes.METHOD.ECDSA];
      /** list of supported identity types */
      public supportedIdentityTypes: IdentityTypes.TYPE[] = [IdentityTypes.TYPE.ETHEREUM_ADDRESS];
    
      /**
       * Signs data
       *
       * @param string data the data to sign
       * @returns IIdentity the identity to sign with. If not given, the default signer will be used
       *
       * @returns string the signature
       */
      public async sign(
        data: any,
        signer: IdentityTypes.IIdentity,
      ): Promise<SignatureTypes.ISignedData> {
        if (!this.supportedIdentityTypes.includes(signer.type)) {
          throw Error(`Identity type not supported ${signer.type}`);
        }
    
        // Hash the normalized data (e.g. avoid case sensitivity)
        const hashData = Utils.crypto.normalizeKeccak256Hash(data).value;
    
        // use your signature package
        const signatureValue = mySignaturePackage.sign(hashData, signer.value);
    
        return {
          data,
          signature: {
            method: SignatureTypes.METHOD.ECDSA,
            value: signatureValue,
          },
        };
      }
    }
    import MySignatureProvider from 'mySignatureProvider';
    
    const mySignatureProvider = new MySignatureProvider();
    
    // We can initialize the RequestNetwork class with the signature provider
    const requestNetwork = new RequestNetwork.RequestNetwork({
      signatureProvider: mySignatureProvider,
    });
    class mySignaturePackage {
      /**
       * Sign a Buffer
       *
       * @param data the data to sign
       * @param walletId a way to get the right wallet to sign with
       * @returns a promise resolving the signature
       */
      public async sign(data: Buffer, walletId: number): Promise<Buffer>;
    }
    import { IdentityTypes, SignatureProviderTypes, SignatureTypes } from '@requestnetwork/types';
    import Utils from '@requestnetwork/utils';
    
    // Your package
    import mySignaturePackage from 'mySignaturePackage';
    
    /** Type of the dictionary of wallet id indexed by address */
    type IWalletIdDictionary = Map<string, number>;
    
    /**
     * Implementation of the signature provider for my wallet
     */
    export default class MySignatureProvider implements SignatureProviderTypes.ISignatureProvider {
      /** list of supported signing method */
      public supportedMethods: SignatureTypes.METHOD[] = [SignatureTypes.METHOD.ECDSA];
      /** list of supported identity types */
      public supportedIdentityTypes: IdentityTypes.TYPE[] = [IdentityTypes.TYPE.ETHEREUM_ADDRESS];
    
      /** Dictionary containing all the private keys indexed by address */
      private walletIdDictionary: IWalletIdDictionary;
    
      constructor(identity?: ?IdentityTypes.IIdentity, walletId?: number) {
        this.walletIdDictionary = new Map<string, number>();
    
        if (identity && walletId) {
          this.addSignatureParameters(identity, walletId);
        }
      }
    
      /**
       * Signs data
       *
       * @param string data the data to sign
       * @returns IIdentity the identity to sign with. If not given, the default signer will be used
       *
       * @returns string the signature
       */
      public async sign(
        data: any,
        signer: IdentityTypes.IIdentity,
      ): Promise<SignatureTypes.ISignedData> {
        if (!this.supportedIdentityTypes.includes(signer.type)) {
          throw Error(`Identity type not supported ${signer.type}`);
        }
    
        // toLowerCase to avoid mismatch because of case
        const walletId: number | undefined = this.walletIdDictionary.get(signer.value.toLowerCase());
    
        if (!walletId) {
          throw Error(`Identity unknown: ${signer.type}, ${signer.value}`);
        }
    
        // Hash the normalized data (e.g. avoid case sensitivity)
        const hashData = Utils.crypto.normalizeKeccak256Hash(data).value;
    
        // convert the hash from a string '0x...' to a Buffer
        const hashDataBuffer = Buffer.from(hashData.slice(2), 'hex');
    
        // use your signature package
        const signatureValueBuffer = mySignaturePackage.sign(hashDataBuffer, walletId);
    
        // convert the signature to a string '0x...'
        const signatureValue = `0x${signatureValueBuffer.toString('hex')}`;
    
        return {
          data,
          signature: {
            method: SignatureTypes.METHOD.ECDSA,
            value: signatureValue,
          },
        };
      }
    
      /**
       * Function to add a new identity in the provider
       *
       * @param identity the new identity
       * @param walletId the wallet id matching the identity
       */
      public addIdentity(identity: IdentityTypes.IIdentity, walletId: number): void {
        if (!this.supportedIdentityTypes.includes(identity.type)) {
          throw Error(`Identity type not supported ${identity.type}`);
        }
    
        this.walletIdDictionary.set(identity.value.toLowerCase(), walletId);
      }
    }
    import MySignatureProvider from 'mySignatureProvider';
    
    const mySignatureProvider = new MySignatureProvider(anIdentity, aWalletId);
    
    // We can initialize the RequestNetwork class with the signature provider
    const requestNetwork = new RequestNetwork.RequestNetwork({
      signatureProvider: mySignatureProvider,
    });
    
    // later on, you can even add more supported identities
    mySignatureProvider.addIdentity(anotherIdentity, anotherWalletId);
    Getting Started with Crypto-to-fiat Payments

    Sandbox Access - Get Started Today

    All developers can immediately access the Crypto-to-fiat Sandbox to build and test their integration:

    1. Create an account on the Request Network API Portal

    2. Generate a Sandbox API key with crypto-to-fiat sandbox access

    3. Start building with Sepolia testnet USDC, simulated KYC, and mock bank accounts

    The sandbox provides a complete testing environment where you can:

    • Test the full crypto-to-fiat flow without real funds

    • Simulate payer KYC verification using mock documents

    • Work with mock bank account data and fiat payment status

    Important: Other Payment Types Use Real Funds

    The "Crypto-to-fiat Sandbox" setting for API keys only affects crypto-to-fiat payments. Other payment types can process real funds with any API key, even Sandbox API keys.

    Production Access - Launch When Ready

    When you're ready to go live with real transactions:

    1. Book a call to request production access

    2. Discuss your use case with our team to ensure the best integration approach

    3. Complete the approval process - we'll work with you to get everything set up

    4. Generate Production API keys once approved

    Production access includes:

    • Real USDC transactions on mainnet

    • Actual KYC verification for payers

    • Live bank account validation

    • Fiat deposits to real bank accounts

    Crypto-to-fiat Supported Chains and Currencies

    For Crypto-to-fiat Payments, the Request Network API supports USDC on Ethereum, Polygon, Arbitrum One, and Sepolia.

    Crypto-to-fiat Payment with non-USDC currencies:

    While crypto-to-fiat requests must be created in USDC, users can pay in alternative currencies through a four-step process, using Crosschain Payments:

    1. Create Crosschain Request: Create a request to swap the payer's currency (e.g., ETH on Ethereum) to USDC on the target chain (e.g., USDC on Polygon). Note: Don't specify a payer address during request creation to allow flexibility in who can pay.

    2. Pay Crosschain Request: Execute the crosschain payment to fulfill the first request

    3. Create Crypto-to-Fiat Request: Create a separate request for the USDC offramp to fiat and bank deposit

    4. Pay Crypto-to-Fiat Request: Execute the crypto-to-fiat payment to complete the flow

    This four-step approach is required due to current API limitations - more streamlined flows are not yet implemented.

    Understanding clientUserId

    Many /payer endpoints in the Request Network API require a clientUserId as a path parameter. This value is an arbitrary identifier chosen by your platform to represent a user (the payer) in your own system.

    • You control the format: The clientUserId can be any unique string that makes sense for your application. It can be a UUID, email address, database ID, or anything unique per user on your platform.

    • EasyInvoice Example: In the EasyInvoice demo app, clientUserId is set to the user's email address.

    • Why is this useful? This approach allows you to integrate the Request Network API without having to change your existing user management logic. You simply pass your own identifier to the API, and all payer-related compliance, agreement, and payment detail records will be associated with that value.

    Example usage:

    In each case, replace {clientUserId} with your chosen identifier for the user.

    Compliance & Payer Onboarding

    Before a payer can use crypto-to-fiat, they must complete compliance steps:

    • KYC: The payer must submit a KYC application.

    • Agreement: The payer must sign a compliance agreement (via an iframe flow).

    • Bank Account: The payee's bank account must be associated with a payer for compliance reasons, even though the payee owns the account.

    Compliance Flow Diagram

    Flow Explanation

    1. Submit KYC: The platform collects KYC information from the payer and submits it to the API.

    2. KYC Review: The platform receives webhook updates as the KYC is processed (compliance.updated with kycStatus).

    3. Agreement Signature: The platform displays an iframe for the payer to sign the compliance agreement. Once signed, the platform calls the API to update the agreement status.

    4. Agreement Confirmation: The platform receives a webhook update when the agreement is completed (compliance.updated with agreementStatus).

    Relevant Endpoints

    • POST /payer: Submit KYC application.

    • GET /payer/{clientUserId}: Get compliance status for a payer.

    • PATCH /payer/{clientUserId}: Update agreement status after signature.

    Setting Up a Crypto-to-Fiat Request (Payee Flow)

    Before a payer can pay in crypto and the payee can receive fiat, the platform must:

    • Submit the payee’s bank account details (associated with a payer for compliance).

    • Wait for approval of those payment details (usually less than 60 seconds, confirmed via webhook).

    • Create a new request with isCryptoToFiatAllowed = true.

    Payment Details Flow Diagram

    Flow Explanation

    1. Submit Bank Account: The platform submits the payee’s bank account details, associating them with a payer. The Request Network API forwards these details to the offramp provider (Request Tech).

    2. Approval: The platform receives a webhook (payment_detail.updated) indicating if the payment details are approved, failed, or pending.

    3. Create Request: Once approved, the platform creates a new request as usual, but with the isCryptoToFiatAllowed flag set to true. This signals that the request is eligible for crypto-to-fiat payment.

    Design Rationale & UX Constraints

    While it is technically possible to create a crypto-to-fiat request before the payer has completed KYC, EasyInvoice intentionally requires the payer to complete KYC first. This decision is based on several practical and UX considerations:

    • Bank Account Association: The payee’s bank account ("payment details") must be linked to a specific payer, which can only be done after the payer completes KYC. This ensures compliance and accurate association of payment details.

    • Validation Complexity: Although the payee could submit their bank account details in advance, the platform cannot validate or approve these details until the payer’s KYC is complete. This would introduce additional communication steps and potential confusion.

    • UI Simplicity: EasyInvoice integrates payee bank account registration directly into the Create Invoice form. If the user clicks "Create" before the bank account is approved, a loading indicator appears until approval is granted. This avoids the need for a separate bank account management page and keeps the user experience straightforward.

    • Protocol Fit: The crypto-to-fiat feature is integrated at the API level, not at the Request Network protocol level. Creating a request on the protocol does not require bank account details, because the protocol itself only handles crypto payments. The additional bank account and offramp logic is layered on top via the API, which transfers crypto to Request Tech, who then executes the offramp and sends fiat to the payee’s bank account. This separation adds some complexity, so the UI is intentionally kept simple.

    • Future Flexibility: While some clients may want to allow payees to create requests before payer KYC, this is not currently supported in EasyInvoice to avoid increased complexity. We may revisit this if there is sufficient market demand.

    This approach ensures a smooth, compliant, and user-friendly experience, even if it means some technical possibilities are not exposed in the current UI.

    Relevant Endpoints

    • POST /payer/{clientUserId}/payment-details: Create payment details (register bank account) for a payee.

    • GET /payer/{clientUserId}/payment-details: Get payment details (bank accounts) for a payee.

    • POST /v2/request with isCryptoToFiatAllowed = true: Create a new crypto-to-fiat request

    Paying a Crypto-to-Fiat Request

    The payer pays in crypto; Request Tech handles offramping and fiat payout.

    Payment Flow Diagram

    Flow Explanation

    1. Get Payment Calldata: The platform fetches payment calldata for the request.

    2. User Pays: The payer signs and submits the transaction, sending crypto to Request Tech.

    3. Offramp Processing: Request Tech receives the crypto and begins the offramp process.

    4. Status Updates: The platform receives webhook events as the offramp progresses (payment.processing, payment.failed), with indicating the current offramp stage.

    5. Fiat Delivered: When the offramp is complete, the platform receives a final webhook ( with ), and then a event.

    Crypto-to-fiat Webhook Event Reference

    Event

    Description

    subStatus values (if any)

    compliance.updated

    KYC/Agreement status updates

    kycStatus: initiated, pending, approved, rejected, failed agreementStatus: not_started, pending, completed, rejected, failed

    payment_detail.updated

    Payment detail (bank account) status

    approved, failed, pending

    payment.processing

    Offramp in progress

    initiated, pending_internal_assessment, ongoing_checks, sending_fiat, fiat_sent, bounced, retry_required

    payment.failed

    Offramp or payment failed

    failed, bounced

    Request Tech
    EasyInvoice
    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/payment-processor/src/payment/swap-conversion-erc20.ts

    📫 Smart Contract Addresses

    ℹ️ Smart Contract Source

    here
    Content Identifiers (CIDs)
    Payment Networks
    Supported Chains
    RequestOpenHashSubmitter
    StorageFeeCollector
    RequestHashStorage
    ERC20FeeProxy
    ERC20Proxy
    EthereumFeeProxy
    EthereumProxy
    ERC20EscrowToPay
    BatchConversionPayments
    BatchNoConversionPayments
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    ERC20SingleRequestProxy
    EthereumSingleRequestProxy
    SingleRequestProxyFactory
    ERC20FeeProxy
    EthereumFeeProxy
    ERC20SingleRequestProxy
    EthereumSingleRequestProxy
    ConversionPaymentSettings

    Quickstart - Node.js

    This page will introduce the primary operations provided by Request Network’s SDK while using the EthereumPrivateKeySignatureProvider to sign requests with a private key that is managed outside of a wallet.

    This approach works well for Node.js environments without access to a Web3 wallet.

    You will learn:

    • How to create a request

    • How to update a request (coming soon...)

    • How to pay a request

    • How to detect a payment

    • How to retrieve a user’s requests

    Repository

    All of the following examples can be found in this repository

    Create a request

    To create an unencrypted ERC-20 request, first construct an EthereumPrivateKeySignatureProvider with a private key.

    Then, first construct a RequestNetwork, passing in the:

    • Request Node URL. In this example, we use the Sepolia Request Node Gateway.

    • EthereumPrivateKeySignatureProvider constructed in the previous step.

    Prepare the Request creation parameters:

    Then, call createRequest() to create the request and waitForConfirmation() to wait until the request is persisted in IPFS and the CID hash is stored on-chain.

    Altogether it looks like this:

    Pay a request / Detect a payment

    First, construct a RequestNetwork object and connect it to a Request Node. In this example, we use the Sepolia Request Node Gateway:

    Then, retrieve the request and get the request data. Take note of the current request balance, to be used later for payment detection.

    Then, construct an ethers v5 Provider and Wallet using a private key. These allow you to read and write to the chain, respectively.

    Unfortunately, the Request Network SDK does not yet support ethers v6.

    Coming soon. Probably involves publicClientToProvider() and walletClientToSigner().

    Then, check that the payer has sufficient funds using hasSufficientFunds()

    Then, in the case of an ERC-20 request, check that the payer has granted sufficient approval using hasErc20Approval(). If not, submit an approval transaction using approveErc20. Wait for an appropriate number of block confirmations. On Sepolia or Ethereum, 2 block confirmations should suffice. Other chains may require more.

    Finally, pay the request using payRequest()

    Detect that the payment was successful by polling the request and waiting until the request balance is greater than or equal to the expected amount.

    Altogether it looks like this:

    Retrieve a user's requests

    First, construct a RequestNetwork object and connect it to a Request Node. In this example, we use the Sepolia Request Node Gateway:

    Then, call fromIdentity() to get an array of Request objects or fromRequestId() to get a single Request object. This function retrieves the Requests stored in IPFS and queries on-chain events to determine the balances paid so far. Finally, call getData() on each Request to get the request contents.

    Altogether it looks like this:

    RequestNetwork

    Description

    The Request Network client.

    Usage

    Constructor Paramters

    Name
    Type
    Required?
    Description

    Types and Interfaces

    AxiosRequestConfig Properties

    Name
    Type
    Required?
    Description

    ISignatureProvider Implementations

    Type
    Description

    IDecryptionProvider Implementations

    Type
    Description

    IHttpDataAccessConfig Properties

    Name
    Type
    Required?
    Description

    PaymentNetworkOptions Properties

    Name
    Type
    Required?
    Description

    Instance Methods

    Name
    Description
    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/payment-processor/src/payment/batch-conversion-proxy.ts
    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/payment-processor/src/payment/swap-erc20-fee-proxy.ts
    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/payment-processor/test/payment/erc20-transferable-receivable.test.ts
    https://github.com/RequestNetwork/quickstart-node-js/blob/main/src/declarePaymentSentAndReceived.js
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/config.ts
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/context.tsx
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/config.ts
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/context.tsx
    GET /v2/payer/{clientUserId}
    PATCH /v2/payer/{clientUserId}
    POST /v2/payer/{clientUserId}/payment-details
    GET  /v2/payer/{clientUserId}/payment-details
    import { ContractTransaction, providers, Signer, BigNumberish, BigNumber } from 'ethers';
    
    import { erc20SwapConversionArtifact } from '@requestnetwork/smart-contracts';
    import { ClientTypes, ExtensionTypes } from '@requestnetwork/types';
    
    import { ITransactionOverrides } from './transaction-overrides';
    import { getProvider, getSigner } from './utils';
    import { checkErc20Allowance, encodeApproveAnyErc20 } from './erc20';
    import { IPreparedTransaction } from './prepared-transaction';
    
    /**
     * Processes the approval transaction of a given payment ERC20 to be spent by the swap router,
     * if the current approval is missing or not sufficient.
     * @param request request to pay, used to know the network
     * @param ownerAddress address of the payer
     * @param paymentTokenAddress ERC20 currency used for the swap
     * @param signerOrProvider the web3 provider. Defaults to Etherscan.
     * @param minAmount ensures the approved amount is sufficient to pay this amount
     * @param overrides optionally, override default transaction values, like gas.
     */
    export async function approveErc20ForSwapWithConversionIfNeeded(
      request: ClientTypes.IRequestData,
      ownerAddress: string,
      paymentTokenAddress: string,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      minAmount: BigNumberish,
      overrides?: ITransactionOverrides,
    ): Promise<ContractTransaction | void> {
      if (
        !(await hasErc20ApprovalForSwapWithConversion(
          request,
          ownerAddress,
          paymentTokenAddress,
          signerOrProvider,
          minAmount,
        ))
      ) {
        return approveErc20ForSwapWithConversionToPay(
          request,
          paymentTokenAddress,
          signerOrProvider,
          overrides,
        );
      }
    }
    
    /**
     * Verify if a given payment ERC20 to be spent by the swap router
     * @param request request to pay, used to know the network
     * @param ownerAddress address of the payer
     * @param paymentTokenAddress ERC20 currency used for the swap
     * @param signerOrProvider the web3 provider. Defaults to Etherscan.
     * @param minAmount ensures the approved amount is sufficient to pay this amount
     * @param overrides optionally, override default transaction values, like gas.
     */
    export async function hasErc20ApprovalForSwapWithConversion(
      request: ClientTypes.IRequestData,
      ownerAddress: string,
      paymentTokenAddress: string,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      minAmount: BigNumberish,
    ): Promise<boolean> {
      if (!request.extensions[ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]) {
        throw new Error(`The request must have the payment network any-to-erc20-proxy`);
      }
      const network =
        request.extensions[ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY].values.network;
      return await checkErc20Allowance(
        ownerAddress,
        erc20SwapConversionArtifact.getAddress(network),
        signerOrProvider,
        paymentTokenAddress,
        minAmount,
      );
    }
    
    /**
     * Processes the approval transaction of the payment ERC20 to be spent by the swap router.
     * @param request request to pay, used to know the network
     * @param paymentTokenAddress picked currency for the swap to pay
     * @param signerOrProvider the web3 provider. Defaults to Etherscan.
     * @param overrides optionally, override default transaction values, like gas.
     */
    export async function approveErc20ForSwapWithConversionToPay(
      request: ClientTypes.IRequestData,
      paymentTokenAddress: string,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      overrides?: ITransactionOverrides,
    ): Promise<ContractTransaction> {
      const network =
        request.extensions[ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY].values.network;
      if (!network) {
        throw new Error(`Payment network currency must have a network`);
      }
    
      const preparedTx = prepareApprovalErc20ForSwapWithConversionToPay(
        request,
        paymentTokenAddress,
        signerOrProvider,
        overrides,
      );
      const signer = getSigner(signerOrProvider);
      const tx = await signer.sendTransaction(preparedTx);
      return tx;
    }
    
    /**
     * Prepare the approval transaction of the payment ERC20 to be spent by the swap router.
     * @param request request to pay, used to know the network
     * @param paymentTokenAddress picked currency for the swap to pay
     * @param signerOrProvider the web3 provider. Defaults to Etherscan.
     * @param overrides optionally, override default transaction values, like gas.
     */
    export function prepareApprovalErc20ForSwapWithConversionToPay(
      request: ClientTypes.IRequestData,
      paymentTokenAddress: string,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      overrides?: ITransactionOverrides,
      amount?: BigNumber,
    ): IPreparedTransaction {
      const network =
        request.extensions[ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY].values.network;
      if (!network) {
        throw new Error(`Payment network currency must have a network`);
      }
    
      const encodedTx = encodeApproveAnyErc20(
        paymentTokenAddress,
        erc20SwapConversionArtifact.getAddress(network),
        signerOrProvider,
        amount,
      );
      return {
        data: encodedTx,
        to: paymentTokenAddress,
        value: 0,
        ...overrides,
      };
    }
    
    import { RequestNetwork } from "@requestnetwork/request-client.js";
    import { IConfig } from "./types";
    
    export const config: IConfig = {
      builderId: "request-network", // Replace with your builder ID, arbitrarily chosen, used to identify your app
      dashboardLink: "/",
      logo: "/assets/logo-sm.svg",
      colors: {
        main: "#0BB489",
        secondary: "#58E1A5",
      },
    };
    
    import { IConfig } from "./types";
    
    export const config: IConfig = {
      builderId: "request-network", // Replace with your builder ID, arbitrarily chosen, used to identify your app
      dashboardLink: "/",
      logo: "/assets/logo-sm.svg",
      colors: {
        main: "#0BB489",
        secondary: "#58E1A5",
      },
    };
    
    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/payment-processor/test/payment/swap-any-to-erc20.test.ts
    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/payment-processor/test/payment/swap-erc20-fee-proxy.test.ts
    import { Wallet, providers, BigNumber } from 'ethers';
    
    import {
      ClientTypes,
      ExtensionTypes,
      IdentityTypes,
      RequestLogicTypes,
    } from '@requestnetwork/types';
    import { deepCopy } from '@requestnetwork/utils';
    
    import { approveErc20ForSwapWithConversionIfNeeded } from '../../src/payment/swap-conversion-erc20';
    import { ERC20, ERC20__factory } from '@requestnetwork/smart-contracts/types';
    import { swapToPayAnyToErc20Request } from '../../src/payment/swap-any-to-erc20';
    import { IConversionSettings } from '../../src/types';
    
    import { currencyManager } from './shared';
    import { UnsupportedCurrencyError } from '@requestnetwork/currency';
    
    /* eslint-disable no-magic-numbers */
    /* eslint-disable @typescript-eslint/no-unused-expressions */
    
    const paymentTokenAddress = '0x9FBDa871d559710256a2502A2517b794B482Db40';
    const acceptedTokenAddress = '0x38cF23C52Bb4B13F051Aec09580a2dE845a7FA35';
    let paymentToken: ERC20;
    let acceptedToken: ERC20;
    
    const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat';
    const paymentAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732';
    const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef';
    const provider = new providers.JsonRpcProvider('http://localhost:8545');
    const wallet = Wallet.fromMnemonic(mnemonic).connect(provider);
    
    const validRequest: ClientTypes.IRequestData = {
      balance: {
        balance: '0',
        events: [],
      },
      contentData: {},
      creator: {
        type: IdentityTypes.TYPE.ETHEREUM_ADDRESS,
        value: wallet.address,
      },
      currency: 'USD',
      currencyInfo: {
        type: RequestLogicTypes.CURRENCY.ISO4217,
        value: 'USD',
      },
    
      events: [],
      expectedAmount: '100',
      extensions: {
        [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: {
          events: [],
          id: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
          type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
          values: {
            feeAddress,
            feeAmount: '2',
            paymentAddress,
            salt: 'salt',
            acceptedTokens: [acceptedTokenAddress],
            network: 'private',
          },
          version: '0.1.0',
        },
      },
      extensionsData: [],
      meta: {
        transactionManagerMeta: {},
      },
      pending: null,
      requestId: 'abcd',
      state: RequestLogicTypes.STATE.CREATED,
      timestamp: 0,
      version: '1.0',
    };
    
    const validSwapSettings = {
      deadline: 2599732187000, // This test will fail in 2052
      maxInputAmount: '3000000000000000000',
      path: [paymentTokenAddress, acceptedTokenAddress],
    };
    const validConversionSettings: IConversionSettings = {
      currency: {
        type: 'ERC20' as any,
        value: acceptedTokenAddress,
        network: 'private',
      },
      currencyManager,
    };
    
    beforeAll(async () => {
      paymentToken = await ERC20__factory.connect(paymentTokenAddress, provider);
      acceptedToken = await ERC20__factory.connect(acceptedTokenAddress, provider);
    });
    
    describe('swap-any-to-erc20', () => {
      describe('swapErc20FeeProxyRequest', () => {
        it('should throw an error if the settings are missing', async () => {
          await expect(
            swapToPayAnyToErc20Request(validRequest, wallet, {
              conversion: validConversionSettings,
            }),
          ).rejects.toThrowError('Swap Settings are required');
    
          await expect(
            swapToPayAnyToErc20Request(validRequest, wallet, {
              swap: validSwapSettings,
            }),
          ).rejects.toThrowError('Conversion Settings are required');
        });
    
        it('should throw an error if the payment network is wrong', async () => {
          const request = deepCopy(validRequest);
          delete request.extensions[ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY];
    
          await expect(
            swapToPayAnyToErc20Request(request, wallet, {
              conversion: validConversionSettings,
              swap: validSwapSettings,
            }),
          ).rejects.toThrowError('The request must have the payment network any-to-erc20-proxy');
        });
    
        it('should throw an error if the conversion path is impossible', async () => {
          const request = deepCopy(validRequest);
          (request.currencyInfo = {
            type: RequestLogicTypes.CURRENCY.ISO4217,
            value: 'XXX',
          }),
            await expect(
              swapToPayAnyToErc20Request(request, wallet, {
                conversion: validConversionSettings,
                swap: validSwapSettings,
              }),
            ).rejects.toThrowError(
              /Impossible to find a conversion path between from XXX \(0x.*\) to ERC20_1 \(0x.*\)/,
            );
        });
    
        it('should throw an error if the conversion currency is not an acceptedTokens', async () => {
          const wrongCurrency: RequestLogicTypes.ICurrency = {
            type: 'ERC20' as any,
            value: '0x17b4158805772ced11225e77339f90beb5aae968',
            network: 'private',
          };
          await expect(
            swapToPayAnyToErc20Request(validRequest, wallet, {
              conversion: {
                currency: wrongCurrency,
                currencyManager,
              },
              swap: {
                deadline: 2599732187000, // This test will fail in 2052
                maxInputAmount: '3000000000000000000',
                path: [paymentTokenAddress, wrongCurrency.value],
              },
            }),
          ).rejects.toThrowError(new UnsupportedCurrencyError(wrongCurrency));
        });
    
        it('should swap and pay with an ERC20 request with fees', async () => {
          // first approve the SwapToPay contract to spend tokens
          const approvalTx = await approveErc20ForSwapWithConversionIfNeeded(
            validRequest,
            wallet.address,
            paymentTokenAddress,
            wallet.provider,
            BigNumber.from(204).mul(BigNumber.from(10).pow(18)),
          );
          if (approvalTx) {
            await approvalTx.wait(1);
          }
    
          // get the balances to compare after payment
          const initialPayerBalance = await paymentToken.balanceOf(wallet.address);
          const initialPayeeBalance = await acceptedToken.balanceOf(paymentAddress);
          const initialBuilderBalance = await acceptedToken.balanceOf(feeAddress);
    
          // Swap and pay
          const tx = await swapToPayAnyToErc20Request(validRequest, wallet, {
            swap: validSwapSettings,
            conversion: validConversionSettings,
          });
    
          const confirmedTx = await tx.wait(1);
    
          expect(confirmedTx.status).toEqual(1);
          expect(tx.hash).toBeDefined();
    
          // Get the new balances
          const finalPayerBalance = await paymentToken.balanceOf(wallet.address);
          const finalPayeeBalance = await acceptedToken.balanceOf(paymentAddress);
          const finalBuilderBalance = await acceptedToken.balanceOf(feeAddress);
    
          // Check each balance
    
          //   expectedAmount:      100000000
          //   feeAmount:        +    2000000
          //                     =  102000000 (8 decimals)
          //   AggDaiUsd.sol     /  101000000
          //                     =  1009900990099009900
          //   Swap fees         *                1.005
          //                     =  1014950495049504949 (18 decimals)
          //   Swapper           *  2
          //                     =  2029900990099009898 (18 decimals) paid by payer in erc20BeforeSwap
          expect(finalPayerBalance.toString()).toEqual(
            initialPayerBalance.sub('2029900990099009898').toString(),
          );
    
          //   expectedAmount:      100000000 (8 decimals)
          //   AggDaiUsd.sol     /  101000000
          //                     =  990099009900990099 (18 decimals) received by payee in erc20AfterConversion
          expect(finalPayeeBalance.toString()).toEqual(
            initialPayeeBalance.add('990099009900990099').toString(),
          );
    
          //   feeAmount:           2000000 (8 decimals)
          //   AggDaiUsd.sol     /  101000000
          //                     =  19801980198019801 (18 decimals) received by fee address in erc20AfterConversion
          //      +
          //
          //   Swap fees            100000000
          //   feeAmount         +    2000000
          //                     =  102000000 (8 decimals)
          //   AggDaiUsd.sol     /  101000000
          //                     =  1009900990099009900
          //   Swap fees         *                0.005
          //                     =     5049504950495049
          //   Total fees        =    24851485148514850
          expect(finalBuilderBalance.toString()).toEqual(
            initialBuilderBalance.add('24851485148514850').toString(),
          );
        });
      });
    });
    
    import { BigNumber, providers, Wallet } from 'ethers';
    
    import {
      ClientTypes,
      CurrencyTypes,
      ExtensionTypes,
      IdentityTypes,
      RequestLogicTypes,
    } from '@requestnetwork/types';
    import { deepCopy } from '@requestnetwork/utils';
    
    import {
      approveErc20ForSwapToPayIfNeeded,
      getErc20Balance,
      ISwapSettings,
      swapErc20FeeProxyRequest,
    } from '../../src';
    import { ERC20__factory } from '@requestnetwork/smart-contracts/types';
    import { erc20SwapToPayArtifact } from '@requestnetwork/smart-contracts';
    import { revokeErc20Approval } from '../../src/payment/utils';
    
    /* eslint-disable no-magic-numbers */
    /* eslint-disable @typescript-eslint/no-unused-expressions */
    
    const erc20ContractAddress = '0x9FBDa871d559710256a2502A2517b794B482Db40';
    const alphaErc20Address = '0x38cF23C52Bb4B13F051Aec09580a2dE845a7FA35';
    
    const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat';
    const paymentAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732';
    const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef';
    const provider = new providers.JsonRpcProvider('http://localhost:8545');
    const wallet = Wallet.fromMnemonic(mnemonic).connect(provider);
    
    const validRequest: ClientTypes.IRequestData = {
      balance: {
        balance: '0',
        events: [],
      },
      contentData: {},
      creator: {
        type: IdentityTypes.TYPE.ETHEREUM_ADDRESS,
        value: wallet.address,
      },
      currency: 'DAI',
      currencyInfo: {
        network: 'private',
        type: RequestLogicTypes.CURRENCY.ERC20,
        value: erc20ContractAddress,
      },
    
      events: [],
      expectedAmount: '100',
      extensions: {
        [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT]: {
          events: [],
          id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
          type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
          values: {
            feeAddress,
            feeAmount: '2',
            paymentAddress,
            salt: 'salt',
          },
          version: '0.1.0',
        },
      },
      extensionsData: [],
      meta: {
        transactionManagerMeta: {},
      },
      pending: null,
      requestId: 'abcd',
      state: RequestLogicTypes.STATE.CREATED,
      timestamp: 0,
      version: '1.0',
    };
    
    const validSwapSettings: ISwapSettings = {
      deadline: 2599732187000, // This test will fail in 2052
      maxInputAmount: 204,
      path: [alphaErc20Address, erc20ContractAddress],
    };
    
    describe('swap-erc20-fee-proxy', () => {
      beforeAll(async () => {
        // revoke erc20SwapToPay approval
        await revokeErc20Approval(
          erc20SwapToPayArtifact.getAddress(
            validRequest.currencyInfo.network! as CurrencyTypes.EvmChainName,
          ),
          alphaErc20Address,
          wallet.provider,
        );
      });
      describe('encodeSwapErc20FeeRequest', () => {
        beforeAll(async () => {
          // revoke erc20SwapToPay approval
          await revokeErc20Approval(
            erc20SwapToPayArtifact.getAddress(
              validRequest.currencyInfo.network! as CurrencyTypes.EvmChainName,
            ),
            alphaErc20Address,
            wallet.provider,
          );
        });
        it('should throw an error if the request is not erc20', async () => {
          const request = deepCopy(validRequest) as ClientTypes.IRequestData;
          request.currencyInfo.type = RequestLogicTypes.CURRENCY.ETH;
    
          await expect(
            swapErc20FeeProxyRequest(request, wallet, validSwapSettings),
          ).rejects.toThrowError(
            'request cannot be processed, or is not an pn-erc20-fee-proxy-contract request',
          );
        });
    
        it('should throw an error if the currencyInfo has no value', async () => {
          const request = deepCopy(validRequest);
          request.currencyInfo.value = '';
          await expect(
            swapErc20FeeProxyRequest(request, wallet, validSwapSettings),
          ).rejects.toThrowError(
            'request cannot be processed, or is not an pn-erc20-fee-proxy-contract request',
          );
        });
    
        it('should throw an error if currencyInfo has no network', async () => {
          const request = deepCopy(validRequest);
          request.currencyInfo.network = '' as CurrencyTypes.EvmChainName;
          await expect(
            swapErc20FeeProxyRequest(request, wallet, validSwapSettings),
          ).rejects.toThrowError('Unsupported chain ');
        });
    
        it('should throw an error if request has no extension', async () => {
          const request = deepCopy(validRequest);
          request.extensions = [] as any;
    
          await expect(
            swapErc20FeeProxyRequest(request, wallet, validSwapSettings),
          ).rejects.toThrowError('no payment network found');
        });
      });
    
      describe('swapErc20FeeProxyRequest', () => {
        it('should consider override parameters', async () => {
          const spy = jest.fn();
          const originalSendTransaction = wallet.sendTransaction.bind(wallet);
          wallet.sendTransaction = spy;
          await swapErc20FeeProxyRequest(
            validRequest,
            wallet,
            {
              deadline: 2599732187000, // This test will fail in 2052
              maxInputAmount: 206,
              path: [alphaErc20Address, erc20ContractAddress],
            },
            {
              overrides: { gasPrice: '20000000000' },
            },
          );
          expect(spy).toHaveBeenCalledWith({
            data: '0x5f2993bf00000000000000000000000075c35c980c0d37ef46df04d31a140b65503c0eed000000000000000000000000f17f52151ebef6c7334fad080c5704d77216b732000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000ce000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c5fdf4076b8f3a5357c5e395ab970b5b54098fef000000000000000000000000000000000000000000000000000000009af4c3db000000000000000000000000000000000000000000000000000000000000000200000000000000000000000038cf23c52bb4b13f051aec09580a2de845a7fa350000000000000000000000009fbda871d559710256a2502a2517b794b482db40000000000000000000000000000000000000000000000000000000000000000886dfbccad783599a000000000000000000000000000000000000000000000000',
            gasPrice: '20000000000',
            to: '0xA4392264a2d8c998901D10C154C91725b1BF0158',
            value: 0,
          });
          wallet.sendTransaction = originalSendTransaction;
        });
    
        it('should swap and pay with an ERC20 request with fees', async () => {
          // first approve the SwapToPay contract to spend ALPHA tokens
          const approvalTx = await approveErc20ForSwapToPayIfNeeded(
            validRequest,
            wallet.address,
            alphaErc20Address,
            wallet.provider,
            BigNumber.from(204).mul(BigNumber.from(10).pow(18)),
          );
          expect(approvalTx).toBeDefined();
          if (approvalTx) {
            await approvalTx.wait(1);
          }
    
          // get the balances to compare after payment
          const balanceEthBefore = await wallet.getBalance();
          const balanceAlphaBefore = await ERC20__factory.connect(
            alphaErc20Address,
            provider,
          ).balanceOf(wallet.address);
          const issuerBalanceErc20Before = await getErc20Balance(
            validRequest,
            paymentAddress,
            provider,
          );
          const feeBalanceErc20Before = await getErc20Balance(validRequest, feeAddress, provider);
    
          // Swap and pay
          const tx = await swapErc20FeeProxyRequest(validRequest, wallet, {
            deadline: Date.now() + 1000000,
            maxInputAmount: 206,
            path: [alphaErc20Address, erc20ContractAddress],
          });
          const confirmedTx = await tx.wait(1);
    
          expect(confirmedTx.status).toEqual(1);
          expect(tx.hash).toBeDefined();
    
          // Get the new balances
          const balanceEthAfter = await wallet.getBalance();
          const balanceAlphaAfter = await ERC20__factory.connect(alphaErc20Address, provider).balanceOf(
            wallet.address,
          );
          const issuerBalanceErc20After = await getErc20Balance(validRequest, paymentAddress, provider);
          const feeBalanceErc20After = await getErc20Balance(validRequest, feeAddress, provider);
    
          // Check each balance
          expect(BigNumber.from(balanceEthBefore).sub(balanceEthAfter).toNumber()).toBeGreaterThan(0);
          expect(BigNumber.from(balanceAlphaAfter).toString()).toEqual(
            BigNumber.from(balanceAlphaBefore).sub(204).toString(),
          );
          expect(BigNumber.from(issuerBalanceErc20After).toString()).toEqual(
            BigNumber.from(issuerBalanceErc20Before).add(100).toString(),
          );
          expect(BigNumber.from(feeBalanceErc20After).toString()).toEqual(
            BigNumber.from(feeBalanceErc20Before).add(2).toString(),
          );
        });
      });
    });
    
    import { constants, ContractTransaction, Signer, BigNumber, BigNumberish, providers } from 'ethers';
    
    import { erc20FeeProxyArtifact, erc20SwapToPayArtifact } from '@requestnetwork/smart-contracts';
    import { ERC20SwapToPay__factory } from '@requestnetwork/smart-contracts/types';
    import { ClientTypes } from '@requestnetwork/types';
    
    import { ITransactionOverrides } from './transaction-overrides';
    import {
      getAmountToPay,
      getProvider,
      getProxyAddress,
      getRequestPaymentValues,
      getSigner,
      validateErc20FeeProxyRequest,
    } from './utils';
    import { IPreparedTransaction } from './prepared-transaction';
    import { Erc20PaymentNetwork } from '@requestnetwork/payment-detection';
    import { EvmChains } from '@requestnetwork/currency';
    
    /**
     * Details required for a token swap:
     *
     *  - maxInputAmount: maximum number of ERC20 allowed for the swap before payment, considering both amount and fees
     *  - path: array of token addresses to be used for the "swap path".
     *    ['0xPaymentCurrency', '0xIntermediate1', ..., '0xRequestCurrency']
     *  - deadline: time in milliseconds since UNIX epoch, after which the swap should not be executed.
     */
    export interface ISwapSettings {
      deadline: number;
      maxInputAmount: BigNumberish;
      path: string[];
    }
    
    /**
     * Details required for a request payment transaction
     * @member overrides custom swap transaction parameters
     */
    export interface ISwapTransactionOptions extends IRequestPaymentOptions {
      overrides?: ITransactionOverrides;
    }
    
    /**
     * Details required for a proxy payment:
     * @member {BigNumberish} amount custom request amount to pay
     * @member {BigNumberish} feeAmount custom fee amount to pay for the proxy
     */
    export interface IRequestPaymentOptions {
      amount?: BigNumberish;
      feeAmount?: BigNumberish;
    }
    
    /**
     * Processes a transaction to swap tokens and pay an ERC20 Request through a proxy with fees.
     * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
     * @param swapSettings settings for the swap: swap path, max amount to swap, deadline
     * @param options to override amount, feeAmount and transaction parameters
     */
    export async function swapErc20FeeProxyRequest(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      swapSettings: ISwapSettings,
      options?: ISwapTransactionOptions,
    ): Promise<ContractTransaction> {
      const preparedTx = prepareSwapToPayErc20FeeRequest(
        request,
        signerOrProvider,
        swapSettings,
        options,
      );
      const signer = getSigner(signerOrProvider);
      const tx = await signer.sendTransaction(preparedTx);
      return tx;
    }
    
    /**
     * Prepare a transaction to swap tokens and pay an ERC20 Request through a proxy with fees.
     * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
     * @param swapSettings settings for the swap: swap path, max amount to swap, deadline
     * @param options to override amount, feeAmount and transaction parameters
     */
    export function prepareSwapToPayErc20FeeRequest(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      swapSettings: ISwapSettings,
      options?: ISwapTransactionOptions,
    ): IPreparedTransaction {
      const { network } = request.currencyInfo;
      EvmChains.assertChainSupported(network!);
      const encodedTx = encodeSwapToPayErc20FeeRequest(
        request,
        signerOrProvider,
        swapSettings,
        options,
      );
      const proxyAddress = erc20SwapToPayArtifact.getAddress(network);
      return {
        data: encodedTx,
        to: proxyAddress,
        value: 0,
        ...options?.overrides,
      };
    }
    
    /**
     * Encodes the call to pay a request through the ERC20 fee proxy contract, can be used with a Multisig contract.
     * @param request request to pay
     * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum
     * @param swapSettings settings for the swap
     * @param options to override amount, feeAmount and transaction parameters
     */
    export function encodeSwapToPayErc20FeeRequest(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      swapSettings: ISwapSettings,
      options?: IRequestPaymentOptions,
    ): string {
      const { paymentReference, paymentAddress, feeAddress, feeAmount, network } =
        getRequestPaymentValues(request);
      EvmChains.assertChainSupported(network!);
    
      validateErc20FeeProxyRequest(request, options?.amount, options?.feeAmount);
    
      const signer = getSigner(signerOrProvider);
      const tokenAddress = request.currencyInfo.value;
      const amountToPay = getAmountToPay(request, options?.amount);
      const feeToPay = BigNumber.from(options?.feeAmount || feeAmount || 0);
    
      if (
        swapSettings.path[swapSettings.path.length - 1].toLowerCase() !== tokenAddress.toLowerCase()
      ) {
        throw new Error('Last item of the path should be the request currency');
      }
      // eslint-disable-next-line no-magic-numbers
      if (Date.now() > swapSettings.deadline * 1000) {
        throw new Error('A swap with a past deadline will fail, the transaction will not be pushed');
      }
      if (!request.currencyInfo.network) {
        throw new Error('Request currency network is missing');
      }
    
      const feeProxyAddress = getProxyAddress(
        request,
        Erc20PaymentNetwork.ERC20FeeProxyPaymentDetector.getDeploymentInformation,
      );
    
      const swapToPayAddress = erc20FeeProxyArtifact.getAddress(network);
      const swapToPayContract = ERC20SwapToPay__factory.connect(swapToPayAddress, signer);
    
      return swapToPayContract.interface.encodeFunctionData('swapTransferWithReference', [
        feeProxyAddress,
        paymentAddress,
        amountToPay,
        swapSettings.maxInputAmount,
        swapSettings.path,
        `0x${paymentReference}`,
        feeToPay,
        feeAddress || constants.AddressZero,
        // eslint-disable-next-line no-magic-numbers
        Math.round(swapSettings.deadline / 1000),
      ]);
    }
    
    const waitForConfirmation = async (dataOrPromise) => {
      const data = await dataOrPromise;
      return new Promise((resolve, reject) => {
        data.on("confirmed", resolve);
        data.on("error", reject);
      });
    };
    
    (async () => {
      const {
        RequestNetwork,
        Types,
        Utils,
      } = require("@requestnetwork/request-client.js");
      const {
        EthereumPrivateKeySignatureProvider,
      } = require("@requestnetwork/epk-signature");
      const { config } = require("dotenv");
      const { Wallet } = require("ethers");
    
      // Load environment variables from .env file
      config();
    
      const payeeEpkSignatureProvider = new EthereumPrivateKeySignatureProvider({
        method: Types.Signature.METHOD.ECDSA,
        privateKey: process.env.PAYEE_PRIVATE_KEY, // Must include 0x prefix
      });
    
      const payerEpkSignatureProvider = new EthereumPrivateKeySignatureProvider({
        method: Types.Signature.METHOD.ECDSA,
        privateKey: process.env.PAYER_PRIVATE_KEY, // Must include 0x prefix
      });
    
      const payeeRequestClient = new RequestNetwork({
        nodeConnectionConfig: {
          baseURL: "https://sepolia.gateway.request.network/",
        },
        signatureProvider: payeeEpkSignatureProvider,
      });
    
      const payerRequestClient = new RequestNetwork({
        nodeConnectionConfig: {
          baseURL: "https://sepolia.gateway.request.network/",
        },
        signatureProvider: payerEpkSignatureProvider,
      });
    
      const payeeIdentityAddress = new Wallet(process.env.PAYEE_PRIVATE_KEY)
        .address;
      const payerIdentityAddress = new Wallet(process.env.PAYER_PRIVATE_KEY)
        .address;
    
      const payeeIdentity = {
        type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
        value: payeeIdentityAddress,
      };
    
      const payerIdentity = {
        type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
        value: payerIdentityAddress,
      };
    
      // In this example, the payee is also the payment recipient.
      const paymentRecipient = payeeIdentityAddress;
      const feeRecipient = "0x0000000000000000000000000000000000000000";
    
      const requestCreateParameters = {
        requestInfo: {
          currency: {
            type: Types.RequestLogic.CURRENCY.ERC20,
            value: "0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C", // FAU token address
            network: "sepolia",
          },
          expectedAmount: "1000000000000000000", // 1.0
          payee: payeeIdentity,
          payer: payerIdentity,
          timestamp: Utils.getCurrentTimestampInSecond(),
        },
        paymentNetwork: {
          // We can declare payments because ERC20 fee proxy payment network inherits from declarative payment network
          id: Types.Extension.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
          parameters: {
            paymentNetworkName: "sepolia",
            paymentAddress: paymentRecipient,
            feeAddress: feeRecipient,
            feeAmount: "0",
          },
        },
        contentData: {
          reason: "🍕",
          dueDate: "2023.06.16",
          builderId: "request-network",
          createdWith: "quickstart",
        },
        signer: payeeIdentity,
      };
    
      const payeeRequest = await payeeRequestClient.createRequest(
        requestCreateParameters,
      );
      const payeeRequestData = await payeeRequest.waitForConfirmation();
    
      const payerRequest = await payerRequestClient.fromRequestId(
        payeeRequestData.requestId,
      );
      const payerRequestData = payerRequest.getData();
    
      const payerRequestDataAfterSent = await payerRequest.declareSentPayment(
        payerRequestData.expectedAmount,
        "payment initiated from the bank",
        payerIdentity,
      );
      console.log(
        "payerRequestDataAfterSent: " +
          JSON.stringify(payerRequestDataAfterSent, null, 2),
      );
    
      const payerRequestDataAfterSentConfirmed = await waitForConfirmation(
        payerRequestDataAfterSent,
      );
      console.log(
        "payerRequestDataAfterSentConfirmed: " +
          JSON.stringify(payerRequestDataAfterSentConfirmed, null, 2),
      );
      console.log(
        "Observe extensionsData contains 3 events: paymentNetwork 'create', contentData 'create', and paymentNetwork 'declareSentPayment'",
      );
    
      const payeeRequestDataRefreshed = await payeeRequest.refresh();
    
      const payeeRequestDataAfterReceived =
        await payeeRequest.declareReceivedPayment(
          payeeRequestDataRefreshed.expectedAmount,
          "payment received from the bank",
          payeeIdentity,
        );
    
      const payeeRequestDataAfterReceivedConfirmed = await waitForConfirmation(
        payeeRequestDataAfterReceived,
      );
      console.log(
        "payeeRequestDataAfterReceivedConfirmed: " +
          JSON.stringify(payeeRequestDataAfterReceivedConfirmed, null, 2),
      );
      console.log(
        "Observe extensionsData contains 4 events: paymentNetwork 'create', contentData 'create', paymentNetwork 'declareSentPayment', and paymentNetwork 'declareReceivedPayment'",
      );
    
      console.log(
        "Request balance: " +
          payeeRequestDataAfterReceivedConfirmed.balance.balance,
      );
      console.log(
        "Request balance events: " +
          JSON.stringify(
            payeeRequestDataAfterReceivedConfirmed.balance.events,
            null,
            2,
          ),
      );
    })();
    

    httpConfig

    optional

    Options for HTTP transport (timeout, delay, retry, etc.)

    paymentOptions

    optional

    Payment detection options

    useMockStorage

    boolean

    optional

    Store ephemeral requests in local memory

    currencies

    CurrencyInput[]

    optional

    Custom currency list

    currencyManager

    ICurrencyManager

    optional

    Custom currency manager (will override currencies). A Currency manager handles a list of currencies and provides utility to retrieve and change format

    httpRequestExponentialBackoffDelay

    number

    Exponential backoff delay in ms when requests to the Node fail

    httpRequestMaxExponentialBackoffDelay

    number

    Maximum exponential backoff delay in ms when requests to the Node fail

    getConfirmationMaxRetry

    number

    Maximum number of retries to get the confirmation of a persistTransaction

    getConfirmationRetryDelay

    number

    Delay between retry in ms to get the confirmation of a persistTransaction

    getConfirmationExponentialBackoffDelay

    number

    Exponential backoff delay in ms to get the confirmation of a persistTransaction

    getConfirmationMaxExponentialBackoffDelay

    number

    Maximum exponential backoff delay in ms to get the confirmation of a persistTransaction

    getConfirmationDeferDelay

    number

    Delay to wait in ms before trying for the first time to get the confirmation of a persistTransaction

    getRpcProvider

    function(ChainName)

    Override RPC node provider

    nodeConnectionConfig

    AxiosRequestConfig

    recommended

    Axios configurations

    signatureProvider

    ISignatureProvider

    recommended

    Required to sign and create requests

    decryptionProvider

    IDecryptionProvider

    optional

    baseUrl

    string

    recommended

    Request Node URL

    Many other properties...

    optional

    Web3SignatureProvider

    Sign using a private key inside of a wallet

    EthereumPrivateKeySignatureProvider

    Sign using a private key outside of a wallet

    EthereumPrivateKeyDecryptionProvider

    Decrypt using a private key outside of a wallet

    requestClientVersionHeader

    string

    Name of the header containing the client version

    httpRequestMaxRetry

    number

    Maximum number of retries to attempt when http requests to the Node fail

    httpRequestRetryDelay

    number

    bitcoinDetectionProvider

    IBitcoinDetectionProvider

    Override default bitcoin payment detection

    explorerApiKeys

    Map<ChainName, string>

    Override explorer API keys

    getSubgraphClient

    function(ChainName)

    createRequest()

    Create an unencrypted request

    _createEncryptedRequest()

    Create an encrypted request. Docs coming soon...

    computeRequestId()

    Compute a request ID without actually creating a request

    fromRequestId()

    Retrieve a request from a requestId

    fromIdentity()

    Retrieve an array of requests from an Identity

    fromTopic()

    Retrieve an array of requests from a topic

    Required to retrieve encrypted requests

    Delay between retry in ms

    Override subgraph payment detection

    https://github.com/RequestNetwork/rn-expo-support/blob/5a771c31051ce89c4feee533692028d45e1415ab/package.json
    https://github.com/RequestNetwork/rn-expo-support/blob/5a771c31051ce89c4feee533692028d45e1415ab/index.js
    https://github.com/RequestNetwork/rn-expo-support/blob/5a771c31051ce89c4feee533692028d45e1415ab/metro.config.js

    payment.confirmed

    Payment fully settled (fiat delivered)

    subStatus
    payment.processing
    subStatus: fiat_sent
    payment.confirmed
    https://github.com/RequestNetwork/quickstart-node.js
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/types.d.ts
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/pages/index.tsx
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/currencies.ts
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/types.d.ts
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/pages/create-invoice.tsx
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/currencies.ts

    IRequestDataWithEvents

    Description

    The request contents. Returned by waitForConfirmation(), getData() and many other methods.

    Instance Methods

    Name
    Description

    Inherited Properties

    IRequestData

    Name
    Type
    Required?
    Description

    IRequest

    Name
    Type
    Required?
    Description

    Types and Interfaces

    Types.RequestLogic.STATE

    Name
    Value

    IBalanceWithEvents

    Name
    Type
    Required?
    Description

    IPaymentNetworkEvent

    Name
    Type
    Required?
    Description

    ICurrency

    Name
    Type
    Required?
    Description

    Types.RequestLogic.CURRENCY

    Name
    Value
    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/advanced-logic/test/extensions/payment-network/meta.test.ts
    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/payment-detection/src/payment-reference-calculator.ts
    {
      "name": "rn-expo-support",
      "main": "./index.js",
      "version": "1.0.0",
      "scripts": {
        "start": "expo start",
        "reset-project": "node ./scripts/reset-project.js",
        "android": "expo start --android",
        "ios": "expo start --ios",
        "web": "expo start --web",
        "test": "jest --watchAll",
        "lint": "expo lint"
      },
      "jest": {
        "preset": "jest-expo"
      },
      "dependencies": {
        "@expo/vector-icons": "^14.0.0",
        "@react-navigation/native": "^6.0.2",
        "@requestnetwork/epk-signature": "^0.6.0",
        "@requestnetwork/payment-processor": "^0.44.0",
        "@requestnetwork/request-client.js": "^0.45.0",
        "@requestnetwork/types": "^0.42.0",
        "@requestnetwork/web3-signature": "^0.5.0",
        "ethers": "^5.5.1",
        "eventemitter3": "^5.0.1",
        "expo": "~51.0.14",
        "expo-constants": "~16.0.2",
        "expo-crypto": "~13.0.2",
        "expo-font": "~12.0.7",
        "expo-linking": "~6.3.1",
        "expo-router": "~3.5.16",
        "expo-splash-screen": "~0.27.5",
        "expo-status-bar": "~1.12.1",
        "expo-system-ui": "~3.0.6",
        "expo-web-browser": "~13.0.3",
        "http-browserify": "^1.7.0",
        "https-browserify": "^1.0.0",
        "node-forge": "^1.3.1",
        "react": "18.2.0",
        "react-dom": "18.2.0",
        "react-native": "0.74.2",
        "react-native-crypto": "^2.2.0",
        "react-native-gesture-handler": "~2.16.1",
        "react-native-get-random-values": "^1.11.0",
        "react-native-quick-crypto": "^0.6.1",
        "react-native-randombytes": "^3.6.1",
        "react-native-reanimated": "~3.10.1",
        "react-native-safe-area-context": "4.10.1",
        "react-native-screens": "3.31.1",
        "react-native-web": "~0.19.10",
        "stream-browserify": "^3.0.0",
        "tweetnacl": "^1.0.3"
      },
      "devDependencies": {
        "@babel/core": "^7.20.0",
        "@types/jest": "^29.5.12",
        "@types/react": "~18.2.45",
        "@types/react-test-renderer": "^18.0.7",
        "jest": "^29.2.1",
        "jest-expo": "~51.0.1",
        "react-test-renderer": "18.2.0",
        "typescript": "~5.3.3"
      }
    }
    
    // Buffer polyfill
    import { Buffer } from "buffer";
    global.Buffer = Buffer;
    
    import "react-native-get-random-values";
    
    // Crypto Polyfill
    import cryptoPolyfill from "./cryptoPolyfill";
    if (typeof global.crypto !== "object") {
      global.crypto = {};
    }
    Object.assign(global.crypto, cryptoPolyfill);
    
    // Event Emitter polyfill
    import EventEmitter from "eventemitter3";
    global.EventEmitter = EventEmitter;
    
    // Stream Polyfill
    import { Readable, Writable } from "stream-browserify";
    global.Readable = Readable;
    global.Writable = Writable;
    
    // HTTP Polyfill
    import http from "http-browserify";
    global.http = http;
    
    // HTTPS Polyfill
    import https from "https-browserify";
    global.https = https;
    
    // Starting expo router
    import "expo-router/entry";
    
    const { getDefaultConfig } = require("expo/metro-config");
    
    const defaultConfig = getDefaultConfig(__dirname);
    
    defaultConfig.resolver.extraNodeModules = {
      ...defaultConfig.resolver.extraNodeModules,
      crypto: require.resolve("./cryptoPolyfill"),
      stream: require.resolve("stream-browserify"),
      buffer: require.resolve("buffer"),
      http: require.resolve("http-browserify"),
      https: require.resolve("https-browserify"),
    };
    
    module.exports = defaultConfig;
    
    const {
      EthereumPrivateKeySignatureProvider,
    } = require("@requestnetwork/epk-signature");
    const { Types } = require("@requestnetwork/request-client.js");
    
    const epkSignatureProvider = new EthereumPrivateKeySignatureProvider({
      method: Types.Signature.METHOD.ECDSA,
      privateKey: process.env.PAYEE_PRIVATE_KEY, // Must include 0x prefix
    });
    const { RequestNetwork } = require("@requestnetwork/request-client.js")
    
    const requestClient = new RequestNetwork({
      nodeConnectionConfig: { 
        baseURL: "https://sepolia.gateway.request.network/",
      },
      signatureProvider: epkSignatureProvider,
    });
    const { Types, Utils } = require("@requestnetwork/request-client.js");
    
    const payeeIdentity = '0x7eB023BFbAeE228de6DC5B92D0BeEB1eDb1Fd567';
    const payerIdentity = '0x519145B771a6e450461af89980e5C17Ff6Fd8A92';
    const paymentRecipient = payeeIdentity;
    const feeRecipient = '0x0000000000000000000000000000000000000000';
    
    const requestCreateParameters = {
      requestInfo: {
        
        // The currency in which the request is denominated
        currency: {
          type: Types.RequestLogic.CURRENCY.ERC20,
          value: '0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C',
          network: 'sepolia',
        },
        
        // The expected amount as a string, in parsed units, respecting `decimals`
        // Consider using `parseUnits()` from ethers or viem
        expectedAmount: '1000000000000000000',
        
        // The payee identity. Not necessarily the same as the payment recipient.
        payee: {
          type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
          value: payeeIdentity,
        },
        
        // The payer identity. If omitted, any identity can pay the request.
        payer: {
          type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
          value: payerIdentity,
        },
        
        // The request creation timestamp.
        timestamp: Utils.getCurrentTimestampInSecond(),
      },
      
      // The paymentNetwork is the method of payment and related details.
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
        parameters: {
          paymentNetworkName: 'sepolia',
          paymentAddress: payeeIdentity,
          feeAddress: feeRecipient,  
          feeAmount: '0',
        },
      },
      
      // The contentData can contain anything.
      // Consider using rnf_invoice format from @requestnetwork/data-format
      contentData: {
        reason: '🍕',
        dueDate: '2023.06.16',
      },
      
      // The identity that signs the request, either payee or payer identity.
      signer: {
        type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
        value: payeeIdentity,
      },
    };
    const request = await requestClient.createRequest(requestCreateParameters);
    const confirmedRequestData = await request.waitForConfirmation();
    const { RequestNetwork, Types } = require("@requestnetwork/request-client.js");
    
    const requestClient = new RequestNetwork({
      nodeConnectionConfig: { 
        baseURL: "https://sepolia.gateway.request.network/",
      }
    });
    const request = await requestClient.fromRequestId(
      '019830e9ec0439e53ec41fc627fd1d0293ec4bc61c2a647673ec5aaaa0e6338855',
    );
    const requestData = request.getData();
    const { providers, Wallet } = require("ethers");
    
    const provider = new providers.JsonRpcProvider(
      process.env.JSON_RPC_PROVIDER_URL,
    );
    const payerWallet = new Wallet(
      process.env.PAYER_PRIVATE_KEY, // Must include 0x prefix
      provider,
    );
    const { hasSufficientFunds } = require("@requestnetwork/payment-processor);
    
    const _hasSufficientFunds = await hasSufficientFunds(
      requestData,
      payerAddress,
      {
        provider: provider,
      },
    );
    const { 
      approveErc20,
      hasErc20Approval,
    } = require("@requestnetwork/payment-processor);
    
    const _hasErc20Approval = await hasErc20Approval(
      requestData,
      payerAddress,
      provider
    );
    if (!_hasErc20Approval) {
      const approvalTx = await approveErc20(requestData, signer);
      await approvalTx.wait(2);
    }
    const { payRequest } = require("@requestnetwork/payment-processor");
    
    const paymentTx = await payRequest(requestData, signer);
    await paymentTx.wait(2);
    const request = await requestClient.fromRequestId(requestData.requestId);
    let requestData = request.getData();
    
    while (requestData.balance?.balance < requestData.expectedAmount) {
      requestData = await request.refresh();
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
    const { RequestNetwork, Types } = require("@requestnetwork/request-client.js");
    const requestClient = new RequestNetwork({
      nodeConnectionConfig: { 
        baseURL: "https://sepolia.gateway.request.network/",
      },
    });
    const identityAddress = "0x519145B771a6e450461af89980e5C17Ff6Fd8A92";
    const requests = await requestClient.fromIdentity({
      type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
      value: identityAddress,
    });
    const requestDatas = requests.map((request) => request.getData());
    const request = await requestClient.fromRequestId(
      '019830e9ec0439e53ec41fc627fd1d0293ec4bc61c2a647673ec5aaaa0e6338855',
    );
    const requestData = request.getData();
    import { ContractTransaction, Signer, providers, BigNumber, constants } from 'ethers';
    import { batchConversionPaymentsArtifact } from '@requestnetwork/smart-contracts';
    import { BatchConversionPayments__factory } from '@requestnetwork/smart-contracts/types';
    import {
      ClientTypes,
      CurrencyTypes,
      ExtensionTypes,
      PaymentTypes,
      RequestLogicTypes,
    } from '@requestnetwork/types';
    import { ITransactionOverrides } from './transaction-overrides';
    import {
      comparePnTypeAndVersion,
      getAmountToPay,
      getPnAndNetwork,
      getProvider,
      getProxyAddress,
      getRequestPaymentValues,
      getSigner,
      MAX_ALLOWANCE,
      validateConversionFeeProxyRequest,
      validateErc20FeeProxyRequest,
    } from './utils';
    import {
      padAmountForChainlink,
      getPaymentNetworkExtension,
    } from '@requestnetwork/payment-detection';
    import { IPreparedTransaction } from './prepared-transaction';
    import { IConversionPaymentSettings } from './index';
    import { getConversionPathForErc20Request } from './any-to-erc20-proxy';
    import { checkErc20Allowance, encodeApproveAnyErc20 } from './erc20';
    import { CurrencyManager } from '@requestnetwork/currency';
    import {
      BatchPaymentNetworks,
      EnrichedRequest,
      IConversionSettings,
      IRequestPaymentOptions,
    } from '../types';
    import { validateEthFeeProxyRequest } from './eth-fee-proxy';
    import { getConversionPathForEthRequest } from './any-to-eth-proxy';
    
    const CURRENCY = RequestLogicTypes.CURRENCY;
    
    /**
     * Processes a transaction to pay a batch of requests with an ERC20 currency
     * that can be different from the request currency (eg. fiat)
     * The payment is made through ERC20 or ERC20Conversion proxies
     * It can be used with a Multisig contract
     * @param enrichedRequests List of EnrichedRequests to pay.
     * @param signerOrProvider The Web3 provider, or signer. Defaults to window.ethereum.
     * @param options It contains 3 paramaters required to do a batch payments:
     *  - conversion: It must contains the currencyManager.
     *  - skipFeeUSDLimit: It checks the value of batchFeeAmountUSDLimit of the batch proxy deployed.
     * Setting the value to true skips the USD fee limit, and reduces gas consumption.
     *  - version: The version of the batch conversion proxy.
     * @param overrides Optionally, override default transaction values, like gas.
     * @dev We only implement batchPayments using two ERC20 functions:
     *      batchMultiERC20ConversionPayments, and batchMultiERC20Payments.
     */
    export async function payBatchConversionProxyRequest(
      enrichedRequests: EnrichedRequest[],
      signerOrProvider: providers.Provider | Signer = getProvider(),
      options: IRequestPaymentOptions,
      overrides?: ITransactionOverrides,
    ): Promise<ContractTransaction> {
      const { data, to, value } = prepareBatchConversionPaymentTransaction(enrichedRequests, options);
      const signer = getSigner(signerOrProvider);
      return signer.sendTransaction({ data, to, value, ...overrides });
    }
    
    /**
     * Prepares a transaction to pay a batch of requests with an ERC20 currency
     * that can be different from the request currency (eg. fiat).
     * It can be used with a Multisig contract.
     * @param enrichedRequests List of EnrichedRequests to pay.
     * @param options It contains 3 paramaters required to prepare a batch payments:
     *  - conversion: It must contains the currencyManager.
     *  - skipFeeUSDLimit: It checks the value of batchFeeAmountUSDLimit of the batch proxy deployed.
     * Setting the value to true skips the USD fee limit, and reduces gas consumption.
     *  - version: The version of the batch conversion proxy.
     */
    export function prepareBatchConversionPaymentTransaction(
      enrichedRequests: EnrichedRequest[],
      options: IRequestPaymentOptions,
    ): IPreparedTransaction {
      const encodedTx = encodePayBatchConversionRequest(
        enrichedRequests,
        options.skipFeeUSDLimit,
        options.conversion,
      );
      const value = getBatchTxValue(enrichedRequests);
      const proxyAddress = getBatchConversionProxyAddress(enrichedRequests[0].request, options.version);
      return {
        data: encodedTx,
        to: proxyAddress,
        value,
      };
    }
    
    const mapPnToDetailsBuilder: Record<
      BatchPaymentNetworks,
      (req: EnrichedRequest, isNative: boolean) => PaymentTypes.RequestDetail
    > = {
      'pn-any-to-erc20-proxy': getRequestDetailWithConversion,
      'pn-any-to-eth-proxy': getRequestDetailWithConversion,
      'pn-erc20-fee-proxy-contract': getRequestDetailWithoutConversion,
      'pn-eth-fee-proxy-contract': getRequestDetailWithoutConversion,
    };
    
    const mapPnToAllowedCurrencies: Record<BatchPaymentNetworks, RequestLogicTypes.CURRENCY[]> = {
      'pn-any-to-erc20-proxy': [CURRENCY.ERC20, CURRENCY.ISO4217, CURRENCY.ETH],
      'pn-any-to-eth-proxy': [CURRENCY.ERC20, CURRENCY.ISO4217],
      'pn-erc20-fee-proxy-contract': [CURRENCY.ERC20],
      'pn-eth-fee-proxy-contract': [CURRENCY.ETH],
    };
    
    const mapPnToBatchId: Record<BatchPaymentNetworks, PaymentTypes.BATCH_PAYMENT_NETWORK_ID> = {
      'pn-any-to-erc20-proxy':
        PaymentTypes.BATCH_PAYMENT_NETWORK_ID.BATCH_MULTI_ERC20_CONVERSION_PAYMENTS,
      'pn-any-to-eth-proxy': PaymentTypes.BATCH_PAYMENT_NETWORK_ID.BATCH_ETH_CONVERSION_PAYMENTS,
      'pn-erc20-fee-proxy-contract': PaymentTypes.BATCH_PAYMENT_NETWORK_ID.BATCH_MULTI_ERC20_PAYMENTS,
      'pn-eth-fee-proxy-contract': PaymentTypes.BATCH_PAYMENT_NETWORK_ID.BATCH_ETH_PAYMENTS,
    };
    
    const computeRequestDetails = ({
      enrichedRequest,
      extension,
    }: {
      enrichedRequest: EnrichedRequest;
      extension: ExtensionTypes.IState<any> | undefined;
    }) => {
      const paymentNetworkId = enrichedRequest.paymentNetworkId;
      const allowedCurrencies = mapPnToAllowedCurrencies[paymentNetworkId];
      const detailsBuilder = mapPnToDetailsBuilder[paymentNetworkId];
      const isNative =
        paymentNetworkId === ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY ||
        paymentNetworkId === ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT;
    
      extension = extension ?? getPaymentNetworkExtension(enrichedRequest.request);
    
      comparePnTypeAndVersion(extension, enrichedRequest.request);
      if (!allowedCurrencies.includes(enrichedRequest.request.currencyInfo.type)) {
        throw new Error(`wrong request currencyInfo type`);
      }
    
      return {
        input: detailsBuilder(enrichedRequest, isNative),
        extension,
      };
    };
    
    /**
     * Encodes a transaction to pay a batch of requests with an ERC20 currency
     * that can be different from the request currency (eg. fiat).
     * It can be used with a Multisig contract.
     * @param enrichedRequests List of EnrichedRequests to pay.
     * @param skipFeeUSDLimit It checks the value of batchFeeAmountUSDLimit of the batch proxy deployed.
     * Setting the value to true skips the USD fee limit, and reduces gas consumption.
     */
    function encodePayBatchConversionRequest(
      enrichedRequests: EnrichedRequest[],
      skipFeeUSDLimit = false,
      conversion: IConversionSettings | undefined,
    ): string {
      if (!(conversion && conversion.currencyManager)) {
        throw 'the conversion object or the currencyManager is undefined';
      }
      const { feeAddress } = getRequestPaymentValues(enrichedRequests[0].request);
    
      const { network } = getPnAndNetwork(enrichedRequests[0].request);
    
      const requestDetails: Record<BatchPaymentNetworks, PaymentTypes.RequestDetail[]> = {
        'pn-any-to-erc20-proxy': [],
        'pn-any-to-eth-proxy': [],
        'pn-erc20-fee-proxy-contract': [],
        'pn-eth-fee-proxy-contract': [],
      };
    
      const requestExtensions: Record<BatchPaymentNetworks, ExtensionTypes.IState<any> | undefined> = {
        'pn-any-to-erc20-proxy': undefined,
        'pn-any-to-eth-proxy': undefined,
        'pn-erc20-fee-proxy-contract': undefined,
        'pn-eth-fee-proxy-contract': undefined,
      };
    
      for (const enrichedRequest of enrichedRequests) {
        const request = enrichedRequest.request;
        const { input, extension } = computeRequestDetails({
          enrichedRequest,
          extension: requestExtensions[enrichedRequest.paymentNetworkId],
        });
        requestDetails[enrichedRequest.paymentNetworkId].push(input);
        requestExtensions[enrichedRequest.paymentNetworkId] = extension;
    
        if (network !== getPnAndNetwork(request).network)
          throw new Error('All the requests must have the same network');
      }
    
      /**
       * The native with conversion payment inputs must be the last element.
       * See BatchConversionPayment batchPayments method in @requestnetwork/smart-contracts
       */
      const metaDetails = Object.entries(requestDetails)
        .map(([pn, details]) => ({
          paymentNetworkId: mapPnToBatchId[pn as BatchPaymentNetworks],
          requestDetails: details,
        }))
        .filter((details) => details.requestDetails.length > 0)
        .sort((a, b) => a.paymentNetworkId - b.paymentNetworkId);
    
      const hasNativePayment =
        requestDetails['pn-any-to-eth-proxy'].length > 0 ||
        requestDetails['pn-eth-fee-proxy-contract'].length > 0;
    
      const pathsToUSD = getUSDPathsForFeeLimit(
        [...metaDetails.map((details) => details.requestDetails).flat()],
        network,
        skipFeeUSDLimit,
        conversion.currencyManager,
        hasNativePayment,
      );
    
      const proxyContract = BatchConversionPayments__factory.createInterface();
      return proxyContract.encodeFunctionData('batchPayments', [
        metaDetails,
        pathsToUSD,
        feeAddress || constants.AddressZero,
      ]);
    }
    
    /**
     * Get the batch input associated to a request without conversion.
     * @param enrichedRequest The enrichedRequest to pay.
     */
    function getRequestDetailWithoutConversion(
      enrichedRequest: EnrichedRequest,
      isNative: boolean,
    ): PaymentTypes.RequestDetail {
      const request = enrichedRequest.request;
      isNative ? validateEthFeeProxyRequest(request) : validateErc20FeeProxyRequest(request);
    
      const currencyManager =
        enrichedRequest.paymentSettings?.currencyManager || CurrencyManager.getDefault();
      const tokenAddress = isNative
        ? currencyManager.getNativeCurrency(
            RequestLogicTypes.CURRENCY.ETH,
            request.currencyInfo.network as string,
          )?.hash
        : request.currencyInfo.value;
      if (!tokenAddress) {
        throw new Error('Could not find the request currency');
      }
      const { paymentReference, paymentAddress, feeAmount } = getRequestPaymentValues(request);
    
      return {
        recipient: paymentAddress,
        requestAmount: getAmountToPay(request).toString(),
        path: [tokenAddress],
        paymentReference: `0x${paymentReference}`,
        feeAmount: feeAmount?.toString() || '0',
        maxToSpend: '0',
        maxRateTimespan: '0',
      };
    }
    
    /**
     * Get the batch input associated to a request with conversion.
     * @param enrichedRequest The enrichedRequest to pay.
     */
    function getRequestDetailWithConversion(
      enrichedRequest: EnrichedRequest,
      isNative: boolean,
    ): PaymentTypes.RequestDetail {
      const { request, paymentSettings } = enrichedRequest;
      const { path, requestCurrency } = (
        isNative ? getConversionPathForEthRequest : getConversionPathForErc20Request
      )(request, paymentSettings);
    
      isNative
        ? validateEthFeeProxyRequest(
            request,
            undefined,
            undefined,
            ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY,
          )
        : validateConversionFeeProxyRequest(request, path);
    
      const { paymentReference, paymentAddress, feeAmount, maxRateTimespan } =
        getRequestPaymentValues(request);
    
      const requestAmount = BigNumber.from(request.expectedAmount).sub(request.balance?.balance || 0);
    
      const padRequestAmount = padAmountForChainlink(requestAmount, requestCurrency);
      const padFeeAmount = padAmountForChainlink(feeAmount || 0, requestCurrency);
      return {
        recipient: paymentAddress,
        requestAmount: padRequestAmount.toString(),
        path: path,
        paymentReference: `0x${paymentReference}`,
        feeAmount: padFeeAmount.toString(),
        maxToSpend: paymentSettings.maxToSpend.toString(),
        maxRateTimespan: maxRateTimespan || '0',
      };
    }
    
    const getBatchTxValue = (enrichedRequests: EnrichedRequest[]) => {
      return enrichedRequests.reduce((prev, curr) => {
        if (
          curr.paymentNetworkId !== ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY &&
          curr.paymentNetworkId !== ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT
        )
          return prev;
        return prev.add(
          curr.paymentNetworkId === ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY
            ? curr.paymentSettings.maxToSpend
            : getAmountToPay(curr.request),
        );
      }, BigNumber.from(0));
    };
    
    /**
     * Get the list of conversion paths from tokens to the USD address through currencyManager.
     * If there is no path to USD for a token, it goes to the next token.
     * @param requestDetails List of ERC20 requests to pay.
     * @param network The network targeted.
     * @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, it skips the path calculation.
     * @param currencyManager The currencyManager used to get token conversion paths to USD.
     */
    function getUSDPathsForFeeLimit(
      requestDetails: PaymentTypes.RequestDetail[],
      network: string,
      skipFeeUSDLimit: boolean,
      currencyManager: CurrencyTypes.ICurrencyManager<unknown>,
      hasNativePayment: boolean,
    ): string[][] {
      if (skipFeeUSDLimit) return [];
    
      const USDCurrency = currencyManager.fromSymbol('USD');
      if (!USDCurrency) throw 'Cannot find the USD currency information';
    
      // Native to USD conversion path
      let nativeConversionPath: string[] = [];
      if (hasNativePayment) {
        const nativeCurrencyHash = currencyManager.getNativeCurrency(
          RequestLogicTypes.CURRENCY.ETH,
          network,
        )?.hash;
        if (!nativeCurrencyHash) throw 'Cannot find the Native currency information';
        nativeConversionPath =
          currencyManager.getConversionPath({ hash: nativeCurrencyHash }, USDCurrency, network) || [];
      }
    
      // get a list of unique token addresses
      const tokenAddresses = requestDetails
        .map((rd) => rd.path[rd.path.length - 1])
        .filter((value, index, self) => self.indexOf(value) === index);
    
      // get the token currencies and keep the one that are defined
      const tokenCurrencies: Array<CurrencyTypes.CurrencyDefinition<unknown>> = tokenAddresses
        .map((token) => currencyManager.fromAddress(token, network))
        .filter((value): value is CurrencyTypes.CurrencyDefinition => !!value);
    
      // get all the conversion paths to USD when it exists and return it
      const path = tokenCurrencies
        .map((t) => currencyManager.getConversionPath(t, USDCurrency, network))
        .filter((value): value is string[] => !!value);
      return hasNativePayment ? path.concat([nativeConversionPath]) : path;
    }
    
    /**
     * @param network The network targeted.
     * @param version The version of the batch conversion proxy, the last one by default.
     * @returns
     */
    function getBatchDeploymentInformation(
      network: CurrencyTypes.EvmChainName,
      version?: string,
    ): { address: string } | null {
      return { address: batchConversionPaymentsArtifact.getAddress(network, version) };
    }
    
    /**
     * Gets batch conversion contract Address.
     * @param request The request for an ERC20 payment with/out conversion.
     * @param version The version of the batch conversion proxy.
     */
    export function getBatchConversionProxyAddress(
      request: ClientTypes.IRequestData,
      version?: string,
    ): string {
      return getProxyAddress(request, getBatchDeploymentInformation, version);
    }
    
    /**
     * ERC20 Batch conversion proxy approvals methods
     */
    
    /**
     * Processes the approval transaction of the targeted ERC20 with batch conversion proxy.
     * @param request The request for an ERC20 payment with/out conversion.
     * @param account The account that will be used to pay the request
     * @param signerOrProvider The Web3 provider, or signer. Defaults to window.ethereum.
     * @param paymentSettings The payment settings are necessary for conversion payment approval.
     * @param version The version of the batch conversion proxy, which can be different from request pn version.
     * @param overrides Optionally, override default transaction values, like gas.
     */
    export async function approveErc20BatchConversionIfNeeded(
      request: ClientTypes.IRequestData,
      account: string,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      amount: BigNumber = MAX_ALLOWANCE,
      paymentSettings?: IConversionPaymentSettings,
      version?: string,
      overrides?: ITransactionOverrides,
    ): Promise<ContractTransaction | void> {
      if (
        !(await hasErc20BatchConversionApproval(
          request,
          account,
          signerOrProvider,
          paymentSettings,
          version,
        ))
      ) {
        return approveErc20BatchConversion(
          request,
          getSigner(signerOrProvider),
          amount,
          paymentSettings,
          version,
          overrides,
        );
      }
    }
    
    /**
     * Checks if the batch conversion proxy has the necessary allowance from a given account
     * to pay a given request with ERC20 batch conversion proxy
     * @param request The request for an ERC20 payment with/out conversion.
     * @param account The account that will be used to pay the request
     * @param signerOrProvider The Web3 provider, or signer. Defaults to window.ethereum.
     * @param paymentSettings The payment settings are necessary for conversion payment approval.
     * @param version The version of the batch conversion proxy.
     */
    export async function hasErc20BatchConversionApproval(
      request: ClientTypes.IRequestData,
      account: string,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      paymentSettings?: IConversionPaymentSettings,
      version?: string,
    ): Promise<boolean> {
      return checkErc20Allowance(
        account,
        getBatchConversionProxyAddress(request, version),
        signerOrProvider,
        getTokenAddress(request, paymentSettings),
        request.expectedAmount,
      );
    }
    
    /**
     * Processes the transaction to approve the batch conversion proxy to spend signer's tokens to pay
     * the request in its payment currency. Can be used with a Multisig contract.
     * @param request The request for an ERC20 payment with/out conversion.
     * @param signerOrProvider The Web3 provider, or signer. Defaults to window.ethereum.
     * @param paymentSettings The payment settings are necessary for conversion payment approval.
     * @param version The version of the batch conversion proxy, which can be different from request pn version.
     * @param overrides Optionally, override default transaction values, like gas.
     */
    export async function approveErc20BatchConversion(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      amount: BigNumber = MAX_ALLOWANCE,
      paymentSettings?: IConversionPaymentSettings,
      version?: string,
      overrides?: ITransactionOverrides,
    ): Promise<ContractTransaction> {
      const preparedTx = prepareApproveErc20BatchConversion(
        request,
        signerOrProvider,
        amount,
        paymentSettings,
        version,
        overrides,
      );
      const signer = getSigner(signerOrProvider);
      const tx = await signer.sendTransaction(preparedTx);
      return tx;
    }
    
    /**
     * Prepare the transaction to approve the proxy to spend signer's tokens to pay
     * the request in its payment currency. Can be used with a Multisig contract.
     * @param request The request for an ERC20 payment with/out conversion.
     * @param signerOrProvider The Web3 provider, or signer. Defaults to window.ethereum.
     * @param paymentSettings The payment settings are necessary for conversion payment approval.
     * @param version The version of the batch conversion proxy.
     * @param overrides Optionally, override default transaction values, like gas.
     */
    export function prepareApproveErc20BatchConversion(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      amount: BigNumber = MAX_ALLOWANCE,
      paymentSettings?: IConversionPaymentSettings,
      version?: string,
      overrides?: ITransactionOverrides,
    ): IPreparedTransaction {
      const encodedTx = encodeApproveErc20BatchConversion(
        request,
        signerOrProvider,
        amount,
        paymentSettings,
        version,
      );
      return {
        data: encodedTx,
        to: getTokenAddress(request, paymentSettings),
        value: 0,
        ...overrides,
      };
    }
    
    /**
     * Encodes the transaction to approve the batch conversion proxy to spend signer's tokens to pay
     * the request in its payment currency. Can be used with a Multisig contract.
     * @param request The request for an ERC20 payment with/out conversion.
     * @param signerOrProvider The Web3 provider, or signer. Defaults to window.ethereum.
     * @param paymentSettings The payment settings are necessary for conversion payment approval.
     * @param version The version of the batch conversion proxy.
     */
    export function encodeApproveErc20BatchConversion(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      amount: BigNumber = MAX_ALLOWANCE,
      paymentSettings?: IConversionPaymentSettings,
      version?: string,
    ): string {
      const proxyAddress = getBatchConversionProxyAddress(request, version);
      return encodeApproveAnyErc20(
        getTokenAddress(request, paymentSettings),
        proxyAddress,
        getSigner(signerOrProvider),
        amount,
      );
    }
    
    /**
     * Get the address of the token to interact with,
     * if it is a conversion payment, the info is inside paymentSettings
     * @param request The request for an ERC20 payment with/out conversion.
     * @param paymentSettings The payment settings are necessary for conversion payment
     * */
    function getTokenAddress(
      request: ClientTypes.IRequestData,
      paymentSettings?: IConversionPaymentSettings,
    ): string {
      if (paymentSettings) {
        if (!paymentSettings.currency) throw 'paymentSetting must have a currency';
        return paymentSettings.currency.value;
      }
    
      return request.currencyInfo.value;
    }
    
    import { Wallet, BigNumber, providers, utils } from 'ethers';
    
    import {
      ClientTypes,
      ExtensionTypes,
      IdentityTypes,
      RequestLogicTypes,
    } from '@requestnetwork/types';
    import { deepCopy } from '@requestnetwork/utils';
    
    import { Erc20PaymentNetwork, PaymentReferenceCalculator } from '@requestnetwork/payment-detection';
    import { ERC20TransferableReceivable__factory } from '@requestnetwork/smart-contracts/types';
    
    import { approveErc20, getErc20Balance } from '../../src/payment/erc20';
    import {
      getReceivableTokenIdForRequest,
      mintErc20TransferableReceivable,
      payErc20TransferableReceivableRequest,
    } from '../../src/payment/erc20-transferable-receivable';
    import { getProxyAddress } from '../../src/payment/utils';
    
    /* eslint-disable no-magic-numbers */
    /* eslint-disable @typescript-eslint/no-unused-expressions */
    
    const erc20ContractAddress = '0x9FBDa871d559710256a2502A2517b794B482Db40';
    
    const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat';
    const feeAddress = '0x75c35C980C0d37ef46DF04d31A140b65503c0eEd';
    const provider = new providers.JsonRpcProvider('http://localhost:8545');
    const payeeWallet = Wallet.createRandom().connect(provider);
    const thirdPartyWallet = Wallet.createRandom().connect(provider);
    const wallet = Wallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/1").connect(provider);
    const paymentAddress = payeeWallet.address;
    
    const validRequest: ClientTypes.IRequestData = {
      balance: {
        balance: '0',
        events: [],
      },
      contentData: {},
      creator: {
        type: IdentityTypes.TYPE.ETHEREUM_ADDRESS,
        value: wallet.address,
      },
      currency: 'DAI',
      currencyInfo: {
        network: 'private',
        type: RequestLogicTypes.CURRENCY.ERC20,
        value: erc20ContractAddress,
      },
      events: [],
      expectedAmount: '100',
      extensions: {
        [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_TRANSFERABLE_RECEIVABLE]: {
          events: [],
          id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_TRANSFERABLE_RECEIVABLE,
          type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
          values: {
            feeAddress,
            feeAmount: '0',
            paymentAddress,
            salt: '0ee84db293a752c6',
          },
          version: '0.2.0',
        },
      },
      payee: {
        type: IdentityTypes.TYPE.ETHEREUM_ADDRESS,
        value: paymentAddress,
      },
      extensionsData: [],
      meta: {
        transactionManagerMeta: {},
      },
      pending: null,
      requestId: '0188791633ff0ec72a7dbdefb886d2db6cccfa98287320839c2f173c7a4e3ce7e1',
      state: RequestLogicTypes.STATE.CREATED,
      timestamp: 0,
      version: '1.0',
    };
    
    describe('erc20-transferable-receivable', () => {
      beforeAll(async () => {
        // Send funds to payeeWallet
        let tx = {
          to: paymentAddress,
          // Convert currency unit from ether to wei
          value: utils.parseEther('1'),
        };
    
        let txResponse = await wallet.sendTransaction(tx);
        await txResponse.wait(1);
    
        // Send funds to thirdPartyWallet
        tx = {
          to: thirdPartyWallet.address,
          // Convert currency unit from ether to wei
          value: utils.parseEther('1'),
        };
    
        txResponse = await wallet.sendTransaction(tx);
        await txResponse.wait(1);
    
        const mintTx = await mintErc20TransferableReceivable(validRequest, payeeWallet, {
          gasLimit: BigNumber.from('20000000'),
        });
        const confirmedTx = await mintTx.wait(1);
    
        expect(confirmedTx.status).toBe(1);
        expect(mintTx.hash).not.toBeUndefined();
      });
    
      beforeEach(() => {
        jest.restoreAllMocks();
      });
    
      describe('mintErc20TransferableReceivable works', () => {
        it('rejects paying without minting', async () => {
          // Different request without a minted receivable
          const request = deepCopy(validRequest) as ClientTypes.IRequestData;
          // Change the request id
          request.requestId = '0188791633ff0ec72a7dbdefb886d2db6cccfa98287320839c2f173c7a4e3ce7e2';
    
          await expect(payErc20TransferableReceivableRequest(request, wallet)).rejects.toThrowError(
            'The receivable for this request has not been minted yet. Please check with the payee.',
          );
        });
      });
    
      describe('payErc20TransferableReceivableRequest', () => {
        it('should throw an error if the request is not erc20', async () => {
          const request = deepCopy(validRequest) as ClientTypes.IRequestData;
          request.currencyInfo.type = RequestLogicTypes.CURRENCY.ETH;
    
          await expect(payErc20TransferableReceivableRequest(request, wallet)).rejects.toThrowError(
            'request cannot be processed, or is not an pn-erc20-transferable-receivable request',
          );
        });
    
        it('should throw an error if the currencyInfo has no value', async () => {
          const request = deepCopy(validRequest);
          request.currencyInfo.value = '';
          await expect(payErc20TransferableReceivableRequest(request, wallet)).rejects.toThrowError(
            'request cannot be processed, or is not an pn-erc20-transferable-receivable request',
          );
        });
    
        it('should throw an error if the payee is undefined', async () => {
          const request = deepCopy(validRequest);
          request.payee = undefined;
          await expect(payErc20TransferableReceivableRequest(request, wallet)).rejects.toThrowError(
            'Expected a payee for this request',
          );
        });
    
        it('should throw an error if currencyInfo has no network', async () => {
          const request = deepCopy(validRequest);
          // @ts-expect-error Type '""' is not assignable to type 'ChainName | undefined'
          request.currencyInfo.network = '';
          await expect(payErc20TransferableReceivableRequest(request, wallet)).rejects.toThrowError(
            'Payment currency must have a network',
          );
        });
    
        it('should throw an error if request has no extension', async () => {
          const request = deepCopy(validRequest);
          request.extensions = [] as any;
    
          await expect(payErc20TransferableReceivableRequest(request, wallet)).rejects.toThrowError(
            'PaymentNetwork not found',
          );
        });
    
        it('should consider override parameters', async () => {
          const spy = jest.fn();
          const originalSendTransaction = wallet.sendTransaction.bind(wallet);
          wallet.sendTransaction = spy;
          await payErc20TransferableReceivableRequest(validRequest, wallet, undefined, undefined, {
            gasPrice: '20000000000',
          });
          const shortReference = PaymentReferenceCalculator.calculate(
            validRequest.requestId,
            validRequest.extensions[ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_TRANSFERABLE_RECEIVABLE]
              .values.salt,
            paymentAddress,
          );
    
          const tokenId = await getReceivableTokenIdForRequest(validRequest, wallet);
          expect(tokenId.isZero()).toBe(false);
    
          expect(spy).toHaveBeenCalledWith({
            data: `0x314ee2d900000000000000000000000000000000${utils
              .hexZeroPad(tokenId.toHexString(), 16)
              .substring(
                2,
              )}000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075c35c980c0d37ef46df04d31a140b65503c0eed0000000000000000000000000000000000000000000000000000000000000008${shortReference}000000000000000000000000000000000000000000000000`,
            gasPrice: '20000000000',
            to: '0xF426505ac145abE033fE77C666840063757Be9cd',
            value: 0,
          });
          wallet.sendTransaction = originalSendTransaction;
        });
    
        it('should pay an ERC20 transferable receivable request with fees', async () => {
          // first approve the contract
          const approvalTx = await approveErc20(validRequest, wallet);
          const approvalTxReceipt = await approvalTx.wait(1);
    
          expect(approvalTxReceipt.status).toBe(1);
          expect(approvalTx.hash).not.toBeUndefined();
    
          // get the balance to compare after payment
          const balanceEthBefore = await wallet.getBalance();
          const balanceErc20Before = await getErc20Balance(validRequest, payeeWallet.address, provider);
    
          const tx = await payErc20TransferableReceivableRequest(validRequest, wallet, 1, 0, {
            gasLimit: BigNumber.from('20000000'),
          });
    
          const confirmedTx = await tx.wait(1);
    
          const balanceEthAfter = await wallet.getBalance();
          const balanceErc20After = await getErc20Balance(validRequest, payeeWallet.address, provider);
    
          expect(confirmedTx.status).toBe(1);
          expect(tx.hash).not.toBeUndefined();
    
          expect(balanceEthAfter.lte(balanceEthBefore)).toBeTruthy(); // 'ETH balance should be lower'
    
          // ERC20 balance should be lower
          expect(
            BigNumber.from(balanceErc20After).eq(BigNumber.from(balanceErc20Before).add(1)),
          ).toBeTruthy();
        });
    
        it('other wallets can mint receivable for owner', async () => {
          // Request without a receivable minted yet
          const request = deepCopy(validRequest) as ClientTypes.IRequestData;
          // Change the request id
          request.requestId = '0188791633ff0ec72a7dbdefb886d2db6cccfa98287320839c2f173c7a4e3ce7e3';
    
          const mintTx = await mintErc20TransferableReceivable(request, thirdPartyWallet, {
            gasLimit: BigNumber.from('20000000'),
          });
          let confirmedTx = await mintTx.wait(1);
    
          expect(confirmedTx.status).toBe(1);
          expect(mintTx.hash).not.toBeUndefined();
    
          // get the balance to compare after payment
          const balanceErc20Before = await getErc20Balance(request, payeeWallet.address, provider);
    
          const tx = await payErc20TransferableReceivableRequest(request, wallet, 1, 0, {
            gasLimit: BigNumber.from('20000000'),
          });
    
          confirmedTx = await tx.wait(1);
    
          const balanceErc20After = await getErc20Balance(request, payeeWallet.address, provider);
    
          expect(confirmedTx.status).toBe(1);
          expect(tx.hash).not.toBeUndefined();
    
          // ERC20 balance should be lower
          expect(
            BigNumber.from(balanceErc20After).eq(BigNumber.from(balanceErc20Before).add(1)),
          ).toBeTruthy();
        });
    
        it('rejects paying unless minted to correct owner', async () => {
          // Request without a receivable minted yet
          const request = deepCopy(validRequest) as ClientTypes.IRequestData;
          // Change the request id
          request.requestId = '0188791633ff0ec72a7dbdefb886d2db6cccfa98287320839c2f173c7a4e3ce7e4';
    
          let shortReference = PaymentReferenceCalculator.calculate(
            request.requestId,
            request.extensions[ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_TRANSFERABLE_RECEIVABLE].values
              .salt,
            paymentAddress,
          );
          let receivableContract = ERC20TransferableReceivable__factory.createInterface();
          let data = receivableContract.encodeFunctionData('mint', [
            thirdPartyWallet.address,
            `0x${shortReference}`,
            '100',
            erc20ContractAddress,
          ]);
          let tx = await thirdPartyWallet.sendTransaction({
            data,
            to: getProxyAddress(
              request,
              Erc20PaymentNetwork.ERC20TransferableReceivablePaymentDetector.getDeploymentInformation,
            ),
            value: 0,
          });
          let confirmedTx = await tx.wait(1);
    
          expect(confirmedTx.status).toBe(1);
          expect(tx.hash).not.toBeUndefined();
    
          await expect(payErc20TransferableReceivableRequest(request, wallet)).rejects.toThrowError(
            'The receivable for this request has not been minted yet. Please check with the payee.',
          );
    
          // Mint the receivable for the correct paymentAddress
          shortReference = PaymentReferenceCalculator.calculate(
            request.requestId,
            request.extensions[ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_TRANSFERABLE_RECEIVABLE].values
              .salt,
            paymentAddress,
          );
          receivableContract = ERC20TransferableReceivable__factory.createInterface();
          data = receivableContract.encodeFunctionData('mint', [
            paymentAddress,
            `0x${shortReference}`,
            '100',
            erc20ContractAddress,
          ]);
          tx = await thirdPartyWallet.sendTransaction({
            data,
            to: getProxyAddress(
              request,
              Erc20PaymentNetwork.ERC20TransferableReceivablePaymentDetector.getDeploymentInformation,
            ),
            value: 0,
          });
          confirmedTx = await tx.wait(1);
    
          expect(confirmedTx.status).toBe(1);
          expect(tx.hash).not.toBeUndefined();
    
          // get the balance to compare after payment
          const balanceErc20Before = await getErc20Balance(request, payeeWallet.address, provider);
    
          tx = await payErc20TransferableReceivableRequest(request, wallet, 1, 0, {
            gasLimit: BigNumber.from('20000000'),
          });
    
          confirmedTx = await tx.wait(1);
    
          const balanceErc20After = await getErc20Balance(request, payeeWallet.address, provider);
    
          expect(confirmedTx.status).toBe(1);
          expect(tx.hash).not.toBeUndefined();
    
          // ERC20 balance should be lower
          expect(
            BigNumber.from(balanceErc20After).eq(BigNumber.from(balanceErc20Before).add(1)),
          ).toBeTruthy();
        });
      });
    });
    
    import React, {
      createContext,
      useContext,
      useState,
      ReactNode,
      useEffect,
    } from "react";
    import { initializeRequestNetwork } from "./initializeRN";
    import type { RequestNetwork } from "@requestnetwork/request-client.js";
    import { useAccount, useWalletClient } from "wagmi";
    
    interface ContextType {
      requestNetwork: RequestNetwork | null;
    }
    
    const Context = createContext<ContextType | undefined>(undefined);
    
    export const Provider = ({ children }: { children: ReactNode }) => {
      const { data: walletClient } = useWalletClient();
      const { address, isConnected, chainId } = useAccount();
      const [requestNetwork, setRequestNetwork] = useState<RequestNetwork | null>(
        null
      );
    
      useEffect(() => {
        if (walletClient && isConnected && address && chainId) {
          initializeRequestNetwork(setRequestNetwork, walletClient);
        }
      }, [walletClient, chainId, address, isConnected]);
    
      return (
        <Context.Provider
          value={{
            requestNetwork,
          }}
        >
          {children}
        </Context.Provider>
      );
    };
    
    export const useAppContext = () => {
      const context = useContext(Context);
      if (!context) {
        throw new Error("useAppContext must be used within a Context Provider");
      }
      return context;
    };
    
    import { IConfig } from "@/utils/types";
    import { Config as WagmiConfig } from "wagmi";
    import type { RequestNetwork } from "@requestnetwork/request-client.js";
    
    declare global {
      namespace JSX {
        interface IntrinsicElements {
          "invoice-dashboard": InvoiceDashboardElement;
          "create-invoice-form": CreateInvoiceFormElement;
        }
      }
    }
    
    interface InvoiceDashboardElement {
      ref?: React.Ref<InvoiceDashboardProps>;
    }
    
    interface CreateInvoiceFormElement {
      ref?: React.Ref<CreateInvoiceFormProps>;
    }
    
    interface InvoiceDashboardProps extends HTMLElement {
      config: IConfig;
      wagmiConfig: WagmiConfig;
      requestNetwork: RequestNetwork;
      currencies: any;
    }
    
    interface CreateInvoiceFormProps extends HTMLElement {
      config: IConfig;
      wagmiConfig: WagmiConfig;
      requestNetwork: RequestNetwork;
      currencies: any;
    }
    
    import Head from "next/head";
    import dynamic from "next/dynamic";
    import { config } from "@/utils/config";
    import { useAppContext } from "@/utils/context";
    import { currencies } from "@/utils/currencies";
    import { rainbowKitConfig as wagmiConfig } from "@/utils/wagmiConfig";
    import { Spinner } from "@/components/ui";
    
    const InvoiceDashboard = dynamic(
      () => import("@requestnetwork/invoice-dashboard/react"),
      { ssr: false, loading: () => <Spinner /> }
    );
    
    export default function InvoiceDashboardPage() {
      const { requestNetwork } = useAppContext();
      return (
        <>
          <Head>
            <title>Request Invoicing</title>
          </Head>
          <div className="container m-auto  w-[100%]">
            <InvoiceDashboard
              config={config}
              currencies={currencies}
              requestNetwork={requestNetwork}
              wagmiConfig={wagmiConfig}
            />
          </div>
        </>
      );
    }
    
    import { Types } from "@requestnetwork/request-client.js";
    
    export const currencies = [
      {
        symbol: "FAU",
        address: "0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C",
        network: "sepolia",
        decimals: 18,
        type: Types.RequestLogic.CURRENCY.ERC20,
      },
      {
        symbol: "ETH",
        address: "eth",
        network: "sepolia",
        decimals: 18,
        type: Types.RequestLogic.CURRENCY.ETH,
      },
    ];
    
    import React, {
      createContext,
      useContext,
      useState,
      ReactNode,
      useEffect,
    } from "react";
    import { initializeRequestNetwork } from "./initializeRN";
    import type { RequestNetwork } from "@requestnetwork/request-client.js";
    import { useAccount, useWalletClient } from "wagmi";
    
    interface ContextType {
      requestNetwork: RequestNetwork | null;
    }
    
    const Context = createContext<ContextType | undefined>(undefined);
    
    export const Provider = ({ children }: { children: ReactNode }) => {
      const { data: walletClient } = useWalletClient();
      const { address, isConnected, chainId } = useAccount();
      const [requestNetwork, setRequestNetwork] = useState<RequestNetwork | null>(
        null
      );
    
      useEffect(() => {
        if (walletClient && isConnected && address && chainId) {
          initializeRequestNetwork(setRequestNetwork, walletClient);
        }
      }, [walletClient, chainId, address, isConnected]);
    
      return (
        <Context.Provider
          value={{
            requestNetwork,
          }}
        >
          {children}
        </Context.Provider>
      );
    };
    
    export const useAppContext = () => {
      const context = useContext(Context);
      if (!context) {
        throw new Error("useAppContext must be used within a Context Provider");
      }
      return context;
    };
    
    import { IConfig } from "@/utils/types";
    import { Config as WagmiConfig } from "wagmi";
    import type { RequestNetwork } from "@requestnetwork/request-client.js";
    
    declare global {
      namespace JSX {
        interface IntrinsicElements {
          "invoice-dashboard": InvoiceDashboardElement;
          "create-invoice-form": CreateInvoiceFormElement;
        }
      }
    }
    
    interface InvoiceDashboardElement {
      ref?: React.Ref<InvoiceDashboardProps>;
    }
    
    interface CreateInvoiceFormElement {
      ref?: React.Ref<CreateInvoiceFormProps>;
    }
    
    interface InvoiceDashboardProps extends HTMLElement {
      config: IConfig;
      wagmiConfig: WagmiConfig;
      requestNetwork: RequestNetwork;
      currencies: any;
    }
    
    interface CreateInvoiceFormProps extends HTMLElement {
      config: IConfig;
      wagmiConfig: WagmiConfig;
      requestNetwork: RequestNetwork;
      currencies: any;
    }
    
    import Head from "next/head";
    import dynamic from "next/dynamic";
    import { config } from "@/utils/config";
    import { useAppContext } from "@/utils/context";
    import { currencies } from "@/utils/currencies";
    import { rainbowKitConfig as wagmiConfig } from "@/utils/wagmiConfig";
    import { Spinner } from "@/components/ui";
    
    const CreateInvoiceForm = dynamic(
      () => import("@requestnetwork/create-invoice-form/react"),
      { ssr: false, loading: () => <Spinner /> }
    );
    
    export default function CreateInvoice() {
      const { requestNetwork } = useAppContext();
    
      return (
        <>
          <Head>
            <title>Request Invoicing - Create an Invoice</title>
          </Head>
          <div className="container m-auto  w-[100%]">
            <CreateInvoiceForm
              config={config}
              currencies={currencies}
              wagmiConfig={wagmiConfig}
              requestNetwork={requestNetwork}
            />
          </div>
        </>
      );
    }
    
    import { Types } from "@requestnetwork/request-client.js";
    
    export const currencies = [
      {
        symbol: "FAU",
        address: "0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C",
        network: "sepolia",
        decimals: 18,
        type: Types.RequestLogic.CURRENCY.ERC20,
      },
      {
        symbol: "ETH",
        address: "eth",
        network: "sepolia",
        decimals: 18,
        type: Types.RequestLogic.CURRENCY.ETH,
      },
    ];
    
    IHttpDataAccessConfig
    PaymentNetworkOptions
    Create a request
    Pay a request
    Difference between Conversion, Swap-to-Pay, and Swap-to-Conversion
    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/payment-processor/src/payment/erc20-transferable-receivable.ts
    import {
      ContractTransaction,
      Signer,
      BigNumberish,
      providers,
      BigNumber,
      constants,
      ethers,
    } from 'ethers';
    
    import {
      Erc20PaymentNetwork,
      ERC20TransferableReceivablePaymentDetector,
    } from '@requestnetwork/payment-detection';
    import { ERC20TransferableReceivable__factory } from '@requestnetwork/smart-contracts/types';
    import { ClientTypes } from '@requestnetwork/types';
    
    import { ITransactionOverrides } from './transaction-overrides';
    import {
      getAmountToPay,
      getProxyAddress,
      getProvider,
      getSigner,
      getRequestPaymentValues,
      validateERC20TransferableReceivable,
      validatePayERC20TransferableReceivable,
    } from './utils';
    import { IPreparedTransaction } from './prepared-transaction';
    
    // The ERC20 receivable smart contract ABI fragment
    const erc20TransferableReceivableContractAbiFragment = [
      'function receivableTokenIdMapping(bytes32) public view returns (uint256)',
    ];
    
    /**
     * Gets the receivableTokenId from a ERC20TransferableReceivable contract given
     * a paymentReference and paymentAddress of the request
     * @param request
     * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
     */
    export async function getReceivableTokenIdForRequest(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer,
    ): Promise<BigNumber> {
      // Setup the ERC20 proxy contract interface
      const contract = new ethers.Contract(
        getProxyAddress(request, ERC20TransferableReceivablePaymentDetector.getDeploymentInformation),
        erc20TransferableReceivableContractAbiFragment,
        signerOrProvider,
      );
    
      const { paymentReference, paymentAddress } = getRequestPaymentValues(request);
    
      return await contract.receivableTokenIdMapping(
        ethers.utils.solidityKeccak256(['address', 'bytes'], [paymentAddress, `0x${paymentReference}`]),
      );
    }
    
    /**
     * Helper method to determine whether a request has a receivable minted yet
     *
     * @param request
     * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
     */
    export async function hasReceivableForRequest(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer,
    ): Promise<boolean> {
      const receivableTokenId = await getReceivableTokenIdForRequest(request, signerOrProvider);
      return !receivableTokenId.isZero();
    }
    
    /**
     * Processes a transaction to mint an ERC20TransferableReceivable.
     * @param request
     * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
     * @param overrides optionally, override default transaction values, like gas.
     */
    export async function mintErc20TransferableReceivable(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      overrides?: ITransactionOverrides,
    ): Promise<ContractTransaction> {
      const { data, to, value } = prepareMintErc20TransferableReceivableTransaction(request);
      const signer = getSigner(signerOrProvider);
      return signer.sendTransaction({ data, to, value, ...overrides });
    }
    
    /**
     * Encodes the call to mint a request through an ERC20TransferableReceivable contract, can be used with a Multisig contract.
     * @param request request to pay
     */
    export function prepareMintErc20TransferableReceivableTransaction(
      request: ClientTypes.IRequestData,
    ): IPreparedTransaction {
      validateERC20TransferableReceivable(request);
    
      return {
        data: encodeMintErc20TransferableReceivableRequest(request),
        to: getProxyAddress(
          request,
          Erc20PaymentNetwork.ERC20TransferableReceivablePaymentDetector.getDeploymentInformation,
        ),
        value: 0,
      };
    }
    
    /**
     * Encodes call to mint a request through an ERC20TransferableReceivable contract, can be used with a Multisig contract.
     * @param request request to pay
     */
    export function encodeMintErc20TransferableReceivableRequest(
      request: ClientTypes.IRequestData,
    ): string {
      validateERC20TransferableReceivable(request);
    
      const tokenAddress = request.currencyInfo.value;
    
      const { paymentReference, paymentAddress } = getRequestPaymentValues(request);
      const amount = getAmountToPay(request);
    
      const receivableContract = ERC20TransferableReceivable__factory.createInterface();
      return receivableContract.encodeFunctionData('mint', [
        paymentAddress,
        `0x${paymentReference}`,
        amount,
        tokenAddress,
      ]);
    }
    
    /**
     * Processes a transaction to pay an ERC20 receivable Request.
     * @param request
     * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
     * @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
     * @param feeAmount optionally, the fee amount to pay. Defaults to the fee amount of the request.
     * @param overrides optionally, override default transaction values, like gas.
     */
    export async function payErc20TransferableReceivableRequest(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer = getProvider(),
      amount?: BigNumberish,
      feeAmount?: BigNumberish,
      overrides?: ITransactionOverrides,
    ): Promise<ContractTransaction> {
      await validatePayERC20TransferableReceivable(request, signerOrProvider, amount, feeAmount);
    
      const { data, to, value } = await prepareErc20TransferableReceivablePaymentTransaction(
        request,
        signerOrProvider,
        amount,
        feeAmount,
      );
      const signer = getSigner(signerOrProvider);
      return signer.sendTransaction({ data, to, value, ...overrides });
    }
    
    /**
     * Encodes the call to pay a request through the ERC20 receivable contract, can be used with a Multisig contract.
     * @param request request to pay
     * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
     * @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
     * @param feeAmountOverride optionally, the fee amount to pay. Defaults to the fee amount of the request.
     */
    export async function prepareErc20TransferableReceivablePaymentTransaction(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer,
      amount?: BigNumberish,
      feeAmountOverride?: BigNumberish,
    ): Promise<IPreparedTransaction> {
      return {
        data: await encodePayErc20TransferableReceivableRequest(
          request,
          signerOrProvider,
          amount,
          feeAmountOverride,
        ),
        to: getProxyAddress(
          request,
          Erc20PaymentNetwork.ERC20TransferableReceivablePaymentDetector.getDeploymentInformation,
        ),
        value: 0,
      };
    }
    
    /**
     * Encodes the call to pay a request through the ERC20 receivable contract, can be used with a Multisig contract.
     * @param request request to pay
     * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
     * @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
     * @param feeAmountOverride optionally, the fee amount to pay. Defaults to the fee amount of the request.
     */
    export async function encodePayErc20TransferableReceivableRequest(
      request: ClientTypes.IRequestData,
      signerOrProvider: providers.Provider | Signer,
      amount?: BigNumberish,
      feeAmountOverride?: BigNumberish,
    ): Promise<string> {
      const amountToPay = getAmountToPay(request, amount);
      const { paymentReference, feeAddress, feeAmount } = getRequestPaymentValues(request);
      const feeToPay = BigNumber.from(feeAmountOverride || feeAmount || 0);
    
      const receivableContract = ERC20TransferableReceivable__factory.createInterface();
    
      // get tokenId from request
      const receivableTokenId = await getReceivableTokenIdForRequest(request, signerOrProvider);
    
      return receivableContract.encodeFunctionData('payOwner', [
        receivableTokenId,
        amountToPay,
        `0x${paymentReference}`,
        feeToPay,
        feeAddress || constants.AddressZero,
      ]);
    }
    
    import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types';
    import { deepCopy } from '@requestnetwork/utils';
    import { CurrencyManager, UnsupportedCurrencyError } from '@requestnetwork/currency';
    
    import * as DataConversionERC20FeeAddData from '../../utils/payment-network/erc20/any-to-erc20-proxy-add-data-generator';
    import * as MetaCreate from '../../utils/payment-network/meta-pn-data-generator';
    import * as TestData from '../../utils/test-data-generator';
    import MetaPaymentNetwork from '../../../src/extensions/payment-network/meta';
    
    const metaPn = new MetaPaymentNetwork(CurrencyManager.getDefault());
    const baseParams = {
      feeAddress: '0x0000000000000000000000000000000000000001',
      feeAmount: '0',
      paymentAddress: '0x0000000000000000000000000000000000000002',
      refundAddress: '0x0000000000000000000000000000000000000003',
      salt: 'ea3bc7caf64110ca',
      network: 'rinkeby',
      acceptedTokens: ['0xFab46E002BbF0b4509813474841E0716E6730136'],
      maxRateTimespan: 1000000,
    } as ExtensionTypes.PnAnyToErc20.ICreationParameters;
    const otherBaseParams = {
      ...baseParams,
      salt: 'ea3bc7caf64110cb',
    } as ExtensionTypes.PnAnyToErc20.ICreationParameters;
    
    /* eslint-disable @typescript-eslint/no-unused-expressions */
    describe('extensions/payment-network/meta', () => {
      describe('createCreationAction', () => {
        it('can create a create action with all parameters', () => {
          expect(
            metaPn.createCreationAction({
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [baseParams, otherBaseParams],
            }),
          ).toEqual({
            action: 'create',
            id: ExtensionTypes.PAYMENT_NETWORK_ID.META,
            parameters: {
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [baseParams, otherBaseParams],
            },
            version: '0.1.0',
          });
        });
    
        it('can create a create action without fee parameters', () => {
          expect(
            metaPn.createCreationAction({
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
                { ...baseParams, feeAddress: undefined, feeAmount: undefined },
                otherBaseParams,
              ],
            }),
          ).toEqual({
            action: 'create',
            id: ExtensionTypes.PAYMENT_NETWORK_ID.META,
            parameters: {
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
                { ...baseParams, feeAddress: undefined, feeAmount: undefined },
                otherBaseParams,
              ],
            },
            version: '0.1.0',
          });
        });
    
        it('cannot createCreationAction with duplicated salt', () => {
          expect(() => {
            metaPn.createCreationAction({
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [baseParams, baseParams],
            });
          }).toThrowError('Duplicate payment network identifier (salt)');
        });
    
        it('cannot createCreationAction with payment address not an ethereum address', () => {
          expect(() => {
            metaPn.createCreationAction({
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
                { ...baseParams, paymentAddress: 'not an ethereum address' },
                otherBaseParams,
              ],
            });
          }).toThrowError("paymentAddress 'not an ethereum address' is not a valid address");
        });
    
        it('cannot createCreationAction with refund address not an ethereum address', () => {
          expect(() => {
            metaPn.createCreationAction({
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
                { ...baseParams, refundAddress: 'not an ethereum address' },
                otherBaseParams,
              ],
            });
          }).toThrowError("refundAddress 'not an ethereum address' is not a valid address");
        });
    
        it('cannot createCreationAction with fee address not an ethereum address', () => {
          expect(() => {
            metaPn.createCreationAction({
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
                { ...baseParams, feeAddress: 'not an ethereum address' },
                otherBaseParams,
              ],
            });
          }).toThrowError('feeAddress is not a valid address');
        });
    
        it('cannot createCreationAction with invalid fee amount', () => {
          expect(() => {
            metaPn.createCreationAction({
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
                { ...baseParams, feeAmount: '-2000' },
                otherBaseParams,
              ],
            });
          }).toThrowError('feeAmount is not a valid amount');
        });
      });
    
      describe('applyActionToExtension', () => {
        describe('applyActionToExtension/create', () => {
          it('can applyActionToExtensions of creation', () => {
            // 'new extension state wrong'
            expect(
              metaPn.applyActionToExtension(
                MetaCreate.requestStateNoExtensions.extensions,
                MetaCreate.actionCreationMultipleAnyToErc20,
                MetaCreate.requestStateNoExtensions,
                TestData.otherIdRaw.identity,
                TestData.arbitraryTimestamp,
              ),
            ).toEqual(MetaCreate.extensionFullStateMultipleAnyToErc20);
          });
    
          it('cannot applyActionToExtensions of creation with a previous state', () => {
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestFullStateCreated.extensions,
                MetaCreate.actionCreationMultipleAnyToErc20,
                MetaCreate.requestFullStateCreated,
                TestData.otherIdRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError('This extension has already been created');
          });
    
          it('cannot applyActionToExtensions of creation on a non supported currency', () => {
            const requestCreatedNoExtension: RequestLogicTypes.IRequest = deepCopy(
              TestData.requestCreatedNoExtension,
            );
            requestCreatedNoExtension.currency = {
              type: RequestLogicTypes.CURRENCY.ERC20,
              value: '0x967da4048cD07aB37855c090aAF366e4ce1b9F48', // OCEAN token address
              network: 'mainnet',
            };
    
            expect(() => {
              metaPn.applyActionToExtension(
                TestData.requestCreatedNoExtension.extensions,
                MetaCreate.actionCreationMultipleAnyToErc20,
                requestCreatedNoExtension,
                TestData.otherIdRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(
              'The currency (OCEAN-mainnet, 0x967da4048cD07aB37855c090aAF366e4ce1b9F48) of the request is not supported for this payment network.',
            );
          });
    
          it('cannot applyActionToExtensions of creation with payment address not valid', () => {
            const actionWithInvalidAddress = deepCopy(MetaCreate.actionCreationMultipleAnyToErc20);
            actionWithInvalidAddress.parameters[
              ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY
            ][0].paymentAddress = DataConversionERC20FeeAddData.invalidAddress;
    
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestStateNoExtensions.extensions,
                actionWithInvalidAddress,
                MetaCreate.requestStateNoExtensions,
                TestData.otherIdRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(
              `paymentAddress '${DataConversionERC20FeeAddData.invalidAddress}' is not a valid address`,
            );
          });
    
          it('cannot applyActionToExtensions of creation with no tokens accepted', () => {
            const actionWithInvalidToken = deepCopy(MetaCreate.actionCreationMultipleAnyToErc20);
            actionWithInvalidToken.parameters[
              ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY
            ][0].acceptedTokens = [];
    
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestStateNoExtensions.extensions,
                actionWithInvalidToken,
                MetaCreate.requestStateNoExtensions,
                TestData.otherIdRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError('acceptedTokens is required');
          });
    
          it('cannot applyActionToExtensions of creation with token address not valid', () => {
            const actionWithInvalidToken = deepCopy(MetaCreate.actionCreationMultipleAnyToErc20);
            actionWithInvalidToken.parameters[
              ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY
            ][0].acceptedTokens = ['invalid address'];
    
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestStateNoExtensions.extensions,
                actionWithInvalidToken,
                MetaCreate.requestStateNoExtensions,
                TestData.otherIdRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError('acceptedTokens must contains only valid ethereum addresses');
          });
    
          it('cannot applyActionToExtensions of creation with refund address not valid', () => {
            const testnetRefundAddress = deepCopy(MetaCreate.actionCreationMultipleAnyToErc20);
            testnetRefundAddress.parameters[
              ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY
            ][0].refundAddress = DataConversionERC20FeeAddData.invalidAddress;
    
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestStateNoExtensions.extensions,
                testnetRefundAddress,
                MetaCreate.requestStateNoExtensions,
                TestData.otherIdRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(
              `refundAddress '${DataConversionERC20FeeAddData.invalidAddress}' is not a valid address`,
            );
          });
          it('keeps the version used at creation', () => {
            const newState = metaPn.applyActionToExtension(
              {},
              { ...MetaCreate.actionCreationMultipleAnyToErc20, version: 'ABCD' },
              MetaCreate.requestStateNoExtensions,
              TestData.otherIdRaw.identity,
              TestData.arbitraryTimestamp,
            );
            expect(newState[metaPn.extensionId].version).toBe('ABCD');
          });
    
          it('requires a version at creation', () => {
            expect(() => {
              metaPn.applyActionToExtension(
                {},
                { ...MetaCreate.actionCreationMultipleAnyToErc20, version: '' },
                MetaCreate.requestStateNoExtensions,
                TestData.otherIdRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError('version is required at creation');
          });
        });
    
        describe('applyActionToExtension/applyApplyActionToExtension', () => {
          it('can applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress', () => {
            expect(
              metaPn.applyActionToExtension(
                MetaCreate.requestStateCreatedMissingAddress.extensions,
                MetaCreate.actionApplyActionToPn,
                MetaCreate.requestStateCreatedMissingAddress,
                TestData.payeeRaw.identity,
                TestData.arbitraryTimestamp,
              ),
            ).toEqual(MetaCreate.extensionStateWithApplyAddPaymentAddressAfterCreation);
          });
    
          it('cannot applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress without a previous state', () => {
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestStateNoExtensions.extensions,
                MetaCreate.actionApplyActionToPn,
                MetaCreate.requestStateNoExtensions,
                TestData.payeeRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(`No payment network with identifier ${MetaCreate.salt2}`);
          });
    
          it('cannot applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress without a payee', () => {
            const previousState = deepCopy(MetaCreate.requestStateCreatedMissingAddress);
            previousState.payee = undefined;
    
            expect(() => {
              metaPn.applyActionToExtension(
                previousState.extensions,
                MetaCreate.actionApplyActionToPn,
                previousState,
                TestData.payeeRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(`The request must have a payee`);
          });
    
          it('cannot applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress signed by someone else than the payee', () => {
            const previousState = deepCopy(MetaCreate.requestStateCreatedMissingAddress);
    
            expect(() => {
              metaPn.applyActionToExtension(
                previousState.extensions,
                MetaCreate.actionApplyActionToPn,
                previousState,
                TestData.payerRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(`The signer must be the payee`);
          });
    
          it('cannot applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress with payment address already given', () => {
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestFullStateCreated.extensions,
                MetaCreate.actionApplyActionToPn,
                MetaCreate.requestFullStateCreated,
                TestData.payeeRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(`Payment address already given`);
          });
    
          it('cannot applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress with payment address not valid', () => {
            const actionWithInvalidAddress = deepCopy(MetaCreate.actionApplyActionToPn);
            actionWithInvalidAddress.parameters.parameters.paymentAddress =
              DataConversionERC20FeeAddData.invalidAddress;
    
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestStateCreatedMissingAddress.extensions,
                actionWithInvalidAddress,
                MetaCreate.requestStateCreatedMissingAddress,
                TestData.payeeRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(
              `paymentAddress '${DataConversionERC20FeeAddData.invalidAddress}' is not a valid address`,
            );
          });
    
          it('cannot applyActionToExtensions applyApplyActionToExtension when the pn identifier is wrong', () => {
            const actionWithInvalidPnIdentifier = deepCopy(MetaCreate.actionApplyActionToPn);
            actionWithInvalidPnIdentifier.parameters.pnIdentifier = 'wrongId';
    
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestStateCreatedMissingAddress.extensions,
                actionWithInvalidPnIdentifier,
                MetaCreate.requestStateCreatedMissingAddress,
                TestData.payeeRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(`No payment network with identifier wrongId`);
          });
    
          it('cannot applyActionToExtensions applyApplyActionToExtension when the action does not exists on the sub pn', () => {
            const actionWithInvalidPnAction = deepCopy(MetaCreate.actionApplyActionToPn);
            actionWithInvalidPnAction.parameters.action = 'wrongAction' as ExtensionTypes.ACTION;
    
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestStateCreatedMissingAddress.extensions,
                actionWithInvalidPnAction,
                MetaCreate.requestStateCreatedMissingAddress,
                TestData.payeeRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(`Unknown action: wrongAction`);
          });
        });
      });
    
      describe('declarative tests', () => {
        describe('applyActionToExtension/declareSentPayment', () => {
          it('can applyActionToExtensions of declareSentPayment', () => {
            expect(
              metaPn.applyActionToExtension(
                MetaCreate.requestFullStateCreated.extensions,
                MetaCreate.actionDeclareSentPayment,
                MetaCreate.requestFullStateCreated,
                TestData.payerRaw.identity,
                TestData.arbitraryTimestamp,
              ),
            ).toEqual(MetaCreate.extensionStateWithDeclaredSent);
          });
    
          it('cannot applyActionToExtensions of declareSentPayment without a previous state', () => {
            expect(() => {
              metaPn.applyActionToExtension(
                MetaCreate.requestStateNoExtensions.extensions,
                MetaCreate.actionDeclareSentPayment,
                MetaCreate.requestStateNoExtensions,
                TestData.payerRaw.identity,
                TestData.arbitraryTimestamp,
              );
            }).toThrowError(`The extension should be created before receiving any other action`);
          });
        });
      });
    });
    
    import { keccak256Hash } from '@requestnetwork/utils';
    
    /**
     * Compute the payment reference
     *
     * @param requestId The requestId
     * @param salt The salt for the request
     * @param address Payment or refund address
     */
    
    export function calculate(requestId: string, salt: string, address: string): string {
      if (!requestId || !salt || !address) {
        throw new Error('RequestId, salt and address are mandatory to calculate the payment reference');
      }
      // "The value is the last 8 bytes of a salted hash of the requestId: `last8Bytes(hash(requestId + salt + address))`"
      /* eslint-disable no-magic-numbers */
      return keccak256Hash((requestId + salt + address).toLowerCase()).slice(-16);
    }
    

    contentData

    object

    Arbitrary content data. Consider using rnf_invoice v0.0.3 from @requestnetwork/data-format package.

    currencyInfo

    Additional info about the currency in which the request is denominated: type, value, and network

    pending

    IPendingRequest

    Shows recently submitted request contents that have not yet been persisted and indexed. Call .

    state

    The state of the request

    expectedAmount

    number | string

    The requested amount

    payee

    Identity of the payee. Not necessarily payment recipient address.

    payer

    Identity of the payer. Not necessarily payment sender address.

    extensions

    IExtensionStates

    The state of the extensions

    extensionsData

    any[]

    Extensions raw data

    events

    IEvent[]

    Historical list of actions that have occurred on the request (create, accept, cancel, etc.)

    timestamp

    number (Unix timestamp)

    Timestamp when request is created. User provided, so this is an agreement between payee and payer.

    nonce

    number

    Optional nonce to differentiate identical requests.

    escrowEvents

    EscrowNetworkEvent[]

    Array of escrow events

    ERC777

    'ERC777'

    Streamable fungible currency (USDCx, REQx, etc.)

    on()

    Event subscriber

    emit()

    Event emitter

    currency

    string

    The currency in which the request is denominated.

    meta

    IReturnMeta

    Metadata from the layer below (transaction manager), including ignored actions, if any.

    balance

    IBalanceWithEvents

    version

    string

    The Request Network protocol version

    requestId

    string

    The ID of the request

    creator

    IIdentity

    PENDING

    'pending'

    CREATED

    'created'

    ACCEPTED

    'accepted'

    CANCELED

    'canceled'

    balance

    string

    The sum of all payments and refunds related to this request. If this is >= expectedAmount, then the request is paid in full.

    events

    IPaymentNetworkEvent[]

    Array of payment events

    error

    IBalanceError

    amount

    string

    The amount of the detected payment

    parameters

    TEventParameters

    Depends on the Payment Network ID

    type

    Types.RequestLogic.CURRENCY

    Currency type

    value

    string

    Depends on type.

    • ERC20 contract address '0x123'

    • Fiat symbol 'USD'

    • Native symbol 'ETH'

    network

    ChainName

    ETH

    'ETH'

    Native (ETH, XDAI, etc.)

    BTC

    'BTC'

    Bitcoin

    ISO4217

    'ISO4217'

    Fiat (USD, EUR, etc.)

    ERC20

    'ERC20'

    Non-native fungible currency (USDC, REQ, etc.)

    The balance object

    Identity of the request creator

    Error occured while retrieiving payment events and calculating the balance

    The chain on which the currency exists

    Quickstart - Browser

    This page will introduce the primary operations provided by Request Network’s SDK while using the Web3SignatureProvider to sign requests with a private key stored inside a wallet.

    This approach works well for Browser environments with access to a web3 wallet.

    You will learn:

    Payment Networks

    Rules for how a payment should be processed and detected

    A payment network is a set of rules defining how a payment should be processed and detected for a Request. It specifies:

    1. Information required at request creation to enable payment detection

    2. The payment method

    3. The process for determining the balance (amount paid)

    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/initializeRN.ts
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/wagmiConfig.ts
    ICurrency
    waitForConfirmation()
    Types.RequestLogic.STATE
    IIdentity
    IIdentity
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/initializeRN.ts
    https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/wagmiConfig.ts
    Reference-based Payment Networks (Recommended)

    Reference-based payment networks use a payment reference to link payments to the corresponding Request. They process payments via payment proxy smart contracts deployed across a wide variety of supported chains. These contracts serve two key functions:

    1. They forward the payment to the payment recipient's address.

    2. They emit an event containing the payment amount and the payment reference.

    The payment reference is a unique identifier derived from the Request ID and payment recipient address. This derivation happens off-chain, not in the smart contract itself.

    It's important to note that the payment recipient's address can be different from the payee's identity address that typically signs to create the request.

    The payment proxy smart contracts do not perform error checking. It is the responsibility of the application using the Request Network to craft the payment transaction with the correct amount and payment reference.

    A payment subgraph indexes events from the payment proxy smart contracts, enabling efficient payment detection and balance calculation for each request.

    Please note that Reference-based payment networks inherit from the declarative payment network. This means that all reference-based requests can also be paid using declarative payments, providing flexibility in payment methods.

    ERC20 Fee Proxy Contract

    This payment network is used for direct ERC20 token payments.

    Example of creating a request with an ERC20 Fee Proxy Contract payment network:

    For details on how to use the ERC20 Fee Proxy, see the Quickstart - Browser or Quickstart - Node.js

    ETH Fee Proxy Contract ("Native Payment")

    This payment network is used for direct ETH (or native token) payments on Ethereum and EVM-compatible chains.

    Example of creating a request with an ETH Fee Proxy Contract payment network:

    This payment network allows for native token payments with an optional fee mechanism. It's suitable for ETH payments on Ethereum mainnet, or native token payments on other EVM-compatible chains like Polygon's MATIC or Binance Smart Chain's BNB.

    For details on how to use the ETH Fee Proxy, see Native Payment

    Any-to-ERC20 Proxy Contract "ERC20 Conversion Payments"

    This payment network allows for "ERC20 Conversion Payments", where the payment currency is an ERC20 token that is different from the request currency. This is most commonly used to denominate the request in fiat like USD but settle the payment in ERC20 tokens like USDC or USDT. This is useful for accounting because most people use fiat currency as their unit of account.

    Key features:

    • Supports payments in any ERC20 token for requests created in various currencies

    • Uses on-chain oracles for real-time currency conversion

    • Includes a fee mechanism similar to the ERC20 Fee Proxy Contract

    Example of creating a request with an Any-to-ERC20 Proxy Contract payment network:

    In this example, the request is created in USD, but can be paid using DAI (or any other specified ERC20 token). The conversion rate is determined at the time of payment using on-chain oracles.

    Conversion is different from Swap-to-Pay. For details see Difference between Conversion, Swap-to-Pay, and Swap-to-Conversion

    For details on how to use the Any-to-ERC20 Conversion Proxy Contract, see Conversion Payment

    Any-to-ETH Proxy Contract "Native Conversion Payments"

    This payment network allows for "Native Conversion Payments", where the payment currency is ETH (or native token) for requests denominated in other currencies, usually fiat. This is most commonly used to denominate the request in fiat like USD but settle the payment in native tokens like ETH on Ethereum or POL on Polygon PoS. This is useful for accounting because most people use fiat currency as their unit of account.

    Key features:

    • Supports payments in ETH (or native token) for requests created in various currencies

    • Uses on-chain oracles for real-time currency conversion

    • Includes a fee mechanism similar to the ETH Fee Proxy Contract

    Example of creating a request with an Any-to-ETH Proxy Contract payment network:

    In this example, the request is created in USD but can be paid using ETH. The conversion rate is determined at the time of payment using on-chain oracles.

    Conversion is different from Swap-to-Pay. For details see Difference between Conversion, Swap-to-Pay, and Swap-to-Conversion

    For details on how to use the Any-to-ETH Proxy Contract, see Conversion Payment

    ERC20 Transferable Receivable

    ERC20 Transferable Receivable allows requests to be minted as NFTs that can be transferred or sold. When the payment is processed, the owner of the NFT receives the payment.

    Example of creating an ERC20 Transferable Receivable request:

    For details on how to use ERC20 Transferable Receivables, see Transferable Receivable Payment

    Declarative Payment Network

    The declarative payment network allows for manual declaration of payments and refunds. It's particularly useful for currencies or payment methods not directly supported by Request Network like traditional finance payment rails, unsupported web3 payment protocols, and unsupported chains like Solana or Tron.

    Example of creating a request with a declarative payment network:

    For details on how to use Declarative Payments, See Declarative Payment

    Meta Payment Network

    The Meta Payment Network allows you to specify multiple potential payment networks for a single request. The payment can be settled by any one of the sub-payment networks.

    For details on how to use Meta Payment Network, see Meta Payments

    ERC777 Stream Payment Network "Streaming Payment"

    ERC777 Streaming Payment routes payments through the ERC20 Fee Proxy payment network, allowing for continuous, streaming payments.

    Example of creating an ERC777 Streaming Payment request:

    For details on how to use the ERC777 Stream Payment Network, see Streaming Payment

    Other Payment Networks

    Address-based Payment Networks (Not Recommended)

    These networks require a unique payment recipient address for each request. The balance is computed from all inbound transfers to this address. This method is not recommended due to its limitations and potential for errors.

    ETH Input Data Payment Network (Deprecated)

    This network used the call data field of Ethereum transactions to tag and detect payments. It has been deprecated in favor of the other reference-based payment networks that use payment proxy smart contracts.

    ERC20 Proxy and Ethereum Proxy (Superseded)

    These payment networks have been superseded by the ERC20 Fee Proxy and ETH Fee Proxy networks respectively. The only difference is that the ERC20 Proxy and Ethereum Proxy don't include the service fee mechanism. For most use cases, it's recommended to use the Fee Proxy versions with the fee set to 0 if no fee is required.

    Advanced Payment Types

    Advanced payment types are built on top of payment networks and provide additional functionality or flexibility.

    The Advanced Payment Types are *not* Payment Networks. You cannot create a request with one of these payment types in the paymentNetwork property.

    Here are some of the advanced payment types available:

    Batch Payments

    Batch payments allow you to pay multiple requests in the same transaction. This is supported for the following payment networks:

    • ERC20 Fee Proxy

    • ETH Fee Proxy

    • Any-to-ERC20 Proxy "ERC20 Conversion Payments"

    • Any-to-ETH Proxy "ETH Conversion Payments"

    • ERC20 Proxy networks

    See Batch Payment for additional details.

    ERC20 Swap-to-Pay Payments

    Swap-to-Pay payments execute a swap immediately before routing the payment through the ERC20 Fee Proxy payment network.

    Swap-to-Pay is different from Conversion. For details see Difference between Conversion, Swap-to-Pay, and Swap-to-Conversion

    See Swap-to-Pay Payment for additional details

    ERC20 Swap-to-Conversion

    Swap-to-Conversion is the combination of Conversion and Swap-to-Pay.

    Swap-to-Conversion executes a swap in Uniswap V2 immediately before routing the payment through the Any-to-ERC20 Payment Network.

    See Swap-to-Conversion Payment for additional details.

    Swap-to-Pay is different from Conversion. For details see Difference between Conversion, Swap-to-Pay, and Swap-to-Conversion

    ERC20 Escrow Payment

    ERC20 Escrow Payment allows for escrow functionality in payments.

    The Request Network Escrow lacks arbitration and is susceptible to deadlock in the case of a dispute.

    See Escrow Payment for additional details.

    Difference between Conversion, Swap-to-Pay, and Swap-to-Conversion

    Conversion is different from Swap-to-pay.

    • In a conversion payment, the request is denominated in currency A, the payer sends currency B and the payee receives currency B.

    • In a Swap-to-pay payment, the request is denominated in currency A, the payer sends currency B and the payee receives currency A.

    They can be combined into Swap-to-Conversion.

    • In a swap-to-conversion payment, the request is denominated in currency A, the payer sends currency B and the payee receives currency C.

    For more detailed information on specific payment networks or to contribute new implementations, please refer to our GitHub repository or join our Discord community.

    import { RequestNetwork } from "@requestnetwork/request-client.js";
    import { Web3SignatureProvider } from "@requestnetwork/web3-signature";
    import { getTheGraphClient } from "@requestnetwork/payment-detection";
    
    export const initializeRequestNetwork = (setter: any, walletClient: any) => {
      try {
        const web3SignatureProvider = new Web3SignatureProvider(walletClient);
    
        const requestNetwork = new RequestNetwork({
          nodeConnectionConfig: {
            baseURL: "https://gnosis.gateway.request.network/",
          },
          signatureProvider: web3SignatureProvider,
          httpConfig: {
            getConfirmationMaxRetry: 120,
          },
          paymentOptions: {
            getSubgraphClient: (chain: string) => {
              // Ternary because cannot dynamically access environment variables in the browser
              const paymentsSubgraphUrl =
              chain === "arbitrum-one"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_ARBITRUM_ONE || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-arbitrum-one/api"
                : chain === "avalanche"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_AVALANCHE || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-avalanche/api"
                : chain === "base"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_BASE || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-base/api"
                : chain === "bsc"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_BSC || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-bsc/api"
                : chain === "celo"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_CELO || "https://api.studio.thegraph.com/query/67444/request-payments-celo/version/latest"
                : chain === "core"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_CORE || "https://thegraph.coredao.org/subgraphs/name/requestnetwork/request-payments-core"
                : chain === "fantom"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_FANTOM || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-fantom/api"
                : chain === "fuse"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_FUSE || "https://api.studio.thegraph.com/query/67444/request-payments-fuse/version/latest"
                : chain === "mainnet"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_MAINNET || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-mainnet/api"
                : chain === "matic"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_MATIC || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-matic/api"
                : chain === "moonbeam"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_MOONBEAM || "https://api.studio.thegraph.com/query/67444/request-payments-moonbeam/version/latest"
                : chain === "optimism"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_OPTIMISM || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-optimism/api"
                : chain === "sepolia"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_SEPOLIA || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-sepolia/api"
                : chain === "xdai"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_XDAI || "https://api.studio.thegraph.com/query/67444/request-payments-xdai/version/latest"
                : chain === "zksyncera"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_ZKSYNCERA || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-zksyncera/api"
                : undefined;
              if (!paymentsSubgraphUrl) {
                throw new Error(
                  `Cannot get subgraph client for unknown chain: ${chain}`
                );
              }
              return getTheGraphClient(chain, paymentsSubgraphUrl);
            },
          },
        });
    
        setter(requestNetwork);
      } catch (error) {
        console.error("Failed to initialize the Request Network:", error);
        setter(null);
      }
    };
    
    import {
      bsc,
      celo,
      base,
      fuse,
      zksync,
      fantom,
      coreDao,
      polygon,
      mainnet,
      sepolia,
      arbitrum,
      moonbeam,
      optimism,
      avalanche,
      gnosis,
    } from "wagmi/chains";
    import { http } from "wagmi";
    import {
      coinbaseWallet,
      injectedWallet,
      ledgerWallet,
      metaMaskWallet,
      safeWallet,
      trustWallet,
      walletConnectWallet,
    } from "@rainbow-me/rainbowkit/wallets";
    import { getDefaultConfig } from "@rainbow-me/rainbowkit";
    
    export const rainbowKitConfig = getDefaultConfig({
      appName: "Request Invoicing",
      projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID as string,
      chains: [
        bsc,
        celo,
        base,
        fuse,
        zksync,
        gnosis,
        fantom,
        coreDao,
        polygon,
        mainnet,
        sepolia,
        arbitrum,
        moonbeam,
        optimism,
        avalanche,
      ],
      transports: {
        [arbitrum.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_ARBITRUM_ONE ||
            "https://arbitrum.llamarpc.com"
        ),
        [avalanche.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_AVALANCHE || "https://avalanche.drpc.org"
        ),
        [base.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_BASE || "https://base.llamarpc.com"
        ),
        [bsc.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_BSC || "https://bsc.llamarpc.com"
        ),
        [celo.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_CELO || "https://forno.celo.org"
        ),
        [coreDao.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_CORE || "https://rpc.coredao.org"
        ),
        [fantom.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_FANTOM ||
            "https://endpoints.omniatech.io/v1/fantom/mainnet/public"
        ),
        [fuse.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_FUSE || "https://fuse.drpc.org"
        ),
        [mainnet.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_ETHEREUM || "https://eth.llamarpc.com"
        ),
        [polygon.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_POLYGON || "https://1rpc.io/matic"
        ),
        [moonbeam.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_MOONBEAM ||
            "https://moonbeam-rpc.publicnode.com"
        ),
        [optimism.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_OPTIMISM ||
            "https://optimism.llamarpc.com"
        ),
        [sepolia.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_SEPOLIA || "https://sepolia.drpc.org"
        ),
        [gnosis.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_XDAI || "https://gnosis.drpc.org"
        ),
        [zksync.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_ZKSYNCERA ||
            "https://mainnet.era.zksync.io"
        ),
      },
      wallets: [
        {
          groupName: "Recommended",
          wallets: [injectedWallet, metaMaskWallet, walletConnectWallet],
        },
        {
          groupName: "Others",
          wallets: [safeWallet, coinbaseWallet, ledgerWallet, trustWallet],
        },
      ],
      ssr: true,
    });
    
    import { RequestNetwork } from "@requestnetwork/request-client.js";
    import { Web3SignatureProvider } from "@requestnetwork/web3-signature";
    import { getTheGraphClient } from "@requestnetwork/payment-detection";
    
    export const initializeRequestNetwork = (setter: any, walletClient: any) => {
      try {
        const web3SignatureProvider = new Web3SignatureProvider(walletClient);
    
        const requestNetwork = new RequestNetwork({
          nodeConnectionConfig: {
            baseURL: "https://gnosis.gateway.request.network/",
          },
          signatureProvider: web3SignatureProvider,
          httpConfig: {
            getConfirmationMaxRetry: 120,
          },
          paymentOptions: {
            getSubgraphClient: (chain: string) => {
              // Ternary because cannot dynamically access environment variables in the browser
              const paymentsSubgraphUrl =
              chain === "arbitrum-one"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_ARBITRUM_ONE || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-arbitrum-one/api"
                : chain === "avalanche"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_AVALANCHE || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-avalanche/api"
                : chain === "base"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_BASE || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-base/api"
                : chain === "bsc"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_BSC || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-bsc/api"
                : chain === "celo"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_CELO || "https://api.studio.thegraph.com/query/67444/request-payments-celo/version/latest"
                : chain === "core"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_CORE || "https://thegraph.coredao.org/subgraphs/name/requestnetwork/request-payments-core"
                : chain === "fantom"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_FANTOM || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-fantom/api"
                : chain === "fuse"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_FUSE || "https://api.studio.thegraph.com/query/67444/request-payments-fuse/version/latest"
                : chain === "mainnet"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_MAINNET || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-mainnet/api"
                : chain === "matic"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_MATIC || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-matic/api"
                : chain === "moonbeam"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_MOONBEAM || "https://api.studio.thegraph.com/query/67444/request-payments-moonbeam/version/latest"
                : chain === "optimism"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_OPTIMISM || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-optimism/api"
                : chain === "sepolia"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_SEPOLIA || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-sepolia/api"
                : chain === "xdai"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_XDAI || "https://api.studio.thegraph.com/query/67444/request-payments-xdai/version/latest"
                : chain === "zksyncera"
                ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_ZKSYNCERA || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-zksyncera/api"
                : undefined;
              if (!paymentsSubgraphUrl) {
                throw new Error(
                  `Cannot get subgraph client for unknown chain: ${chain}`
                );
              }
              return getTheGraphClient(chain, paymentsSubgraphUrl);
            },
          },
        });
    
        setter(requestNetwork);
      } catch (error) {
        console.error("Failed to initialize the Request Network:", error);
        setter(null);
      }
    };
    
    import {
      bsc,
      celo,
      base,
      fuse,
      zksync,
      fantom,
      coreDao,
      polygon,
      mainnet,
      sepolia,
      arbitrum,
      moonbeam,
      optimism,
      avalanche,
      gnosis,
    } from "wagmi/chains";
    import { http } from "wagmi";
    import {
      coinbaseWallet,
      injectedWallet,
      ledgerWallet,
      metaMaskWallet,
      safeWallet,
      trustWallet,
      walletConnectWallet,
    } from "@rainbow-me/rainbowkit/wallets";
    import { getDefaultConfig } from "@rainbow-me/rainbowkit";
    
    export const rainbowKitConfig = getDefaultConfig({
      appName: "Request Invoicing",
      projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID as string,
      chains: [
        bsc,
        celo,
        base,
        fuse,
        zksync,
        gnosis,
        fantom,
        coreDao,
        polygon,
        mainnet,
        sepolia,
        arbitrum,
        moonbeam,
        optimism,
        avalanche,
      ],
      transports: {
        [arbitrum.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_ARBITRUM_ONE ||
            "https://arbitrum.llamarpc.com"
        ),
        [avalanche.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_AVALANCHE || "https://avalanche.drpc.org"
        ),
        [base.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_BASE || "https://base.llamarpc.com"
        ),
        [bsc.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_BSC || "https://bsc.llamarpc.com"
        ),
        [celo.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_CELO || "https://forno.celo.org"
        ),
        [coreDao.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_CORE || "https://rpc.coredao.org"
        ),
        [fantom.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_FANTOM ||
            "https://endpoints.omniatech.io/v1/fantom/mainnet/public"
        ),
        [fuse.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_FUSE || "https://fuse.drpc.org"
        ),
        [mainnet.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_ETHEREUM || "https://eth.llamarpc.com"
        ),
        [polygon.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_POLYGON || "https://1rpc.io/matic"
        ),
        [moonbeam.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_MOONBEAM ||
            "https://moonbeam-rpc.publicnode.com"
        ),
        [optimism.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_OPTIMISM ||
            "https://optimism.llamarpc.com"
        ),
        [sepolia.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_SEPOLIA || "https://sepolia.drpc.org"
        ),
        [gnosis.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_XDAI || "https://gnosis.drpc.org"
        ),
        [zksync.id]: http(
          process.env.NEXT_PUBLIC_RPC_URL_ZKSYNCERA ||
            "https://mainnet.era.zksync.io"
        ),
      },
      wallets: [
        {
          groupName: "Recommended",
          wallets: [injectedWallet, metaMaskWallet, walletConnectWallet],
        },
        {
          groupName: "Others",
          wallets: [safeWallet, coinbaseWallet, ledgerWallet, trustWallet],
        },
      ],
      ssr: true,
    });
    
    const erc20Request = await requestNetwork.createRequest({
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
        parameters: {
          paymentNetworkName: 'sepolia',
          paymentAddress: paymentRecipientAddress,
          feeAddress: feeRecipient,  
          feeAmount: '0',
        },
      },
      requestInfo: {
        currency: {
          type: RequestLogicTypes.CURRENCY.ERC20,
          value: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT on Ethereum
          network: 'mainnet'
        },
        expectedAmount: '1000000', // 1 USDT (6 decimals)
        payee: payeeIdentity,
        payer: payerIdentity,
      },
      signer: payeeIdentity,
    });
    const nativeRequest = await requestNetwork.createRequest({
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT,
        parameters: {
          paymentNetworkName: 'mainnet',
          paymentAddress: paymentRecipientAddress,
          feeAddress: feeRecipient,
          feeAmount: '1000000000000000', // 0.001 ETH fee
        },
      },
      requestInfo: {
        currency: {
          type: RequestLogicTypes.CURRENCY.ETH,
          value: 'ETH',
          network: 'mainnet'
        },
        expectedAmount: '1000000000000000000', // 1 ETH (18 decimals)
        payee: payeeIdentity,
        payer: payerIdentity,
      },
      signer: payeeIdentity,
    });
    const erc20ConversionRequest = await requestNetwork.createRequest({
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
        parameters: {
          paymentNetworkName: 'mainnet',
          paymentAddress: paymentRecipientAddress,
          feeAddress: feeRecipient,
          feeAmount: '100', // Fee in request currency
          acceptedTokens: ['0x6B175474E89094C44Da98b954EedeAC495271d0F'], // DAI address
          maxRateTimespan: 1800, // 30 minutes
        },
      },
      requestInfo: {
        currency: {
          type: RequestLogicTypes.CURRENCY.ISO4217,
          value: 'USD'
        },
        expectedAmount: '1000000', // 1000.00 USD (2 decimals)
        payee: payeeIdentity,
        payer: payerIdentity,
      },
      signer: payeeIdentity,
    });
    const nativeConversionRequest = await requestNetwork.createRequest({
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY,
        parameters: {
          paymentNetworkName: 'mainnet',
          paymentAddress: paymentRecipientAddress,
          feeAddress: feeRecipient,
          feeAmount: '100', // Fee in request currency
          maxRateTimespan: 1800, // 30 minutes
        },
      },
      requestInfo: {
        currency: {
          type: RequestLogicTypes.CURRENCY.ISO4217,
          value: 'USD'
        },
        expectedAmount: '1000000', // 1000.00 USD (2 decimals)
        payee: payeeIdentity,
        payer: payerIdentity,
      },
      signer: payeeIdentity,
    });
    const transferableRequest = await requestNetwork.createRequest({
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ERC20_TRANSFERABLE_RECEIVABLE,
        parameters: {
          paymentAddress: paymentRecipientAddress,
          feeAddress: feeRecipient,
          feeAmount: '0',
          network: 'mainnet',
        },
      },
      requestInfo: {
        currency: {
          type: RequestLogicTypes.CURRENCY.ERC20,
          value: erc20TokenAddress,
          network: 'mainnet'
        },
        expectedAmount: '1000000000000000000', // 1 token with 18 decimals
        payee: payeeIdentity,
        payer: payerIdentity,
      },
      signer: payeeIdentity,
    });
    const request = await requestNetwork.createRequest({
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.DECLARATIVE,
        parameters: {
          paymentInfo: {
            IBAN: 'FR7630006000011234567890189',
            BIC: 'BNPAFRPP'
          },
        },
      },
      requestInfo: {
        currency: {
          type: RequestLogicTypes.CURRENCY.ISO4217,
          value: 'EUR'
        },
        expectedAmount: '100000', // 1000.00 EUR (2 decimals)
        payee: payeeIdentity,
        payer: payerIdentity,
      },
      signer: payeeIdentity,
    });
    const streamingRequest = await requestNetwork.createRequest({
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ERC777_STREAM,
        parameters: {
          paymentAddress: paymentRecipientAddress,
          feeAddress: feeRecipient,
          feeAmount: '0',
          network: 'mainnet',
          tokenAddress: erc777TokenAddress,
        },
      },
      requestInfo: {
        currency: {
          type: RequestLogicTypes.CURRENCY.ERC777,
          value: erc777TokenAddress,
          network: 'mainnet'
        },
        expectedAmount: '1000000000000000000', // 1 token with 18 decimals
        payee: payeeIdentity,
        payer: payerIdentity,
      },
      signer: payeeIdentity,
    });
    How to create a request
  • How to update a request (coming soon...)

  • How to pay a request

  • How to detect a payment

  • How to retrieve a user’s requests

  • Create a request

    To create an unencrypted ERC-20 request, first connect to an ethers v5 Provider and Signer or wagmi / viem WalletClient.

    Unfortunately, the Request Network SDK does not yet support ethers v6.

    import { providers } from "ethers";
    
    let provider;
    if (process.env.WEB3_PROVIDER_URL === undefined) {
      // Connect to Metamask and other injected wallets
      provider = new providers.Web3Provider(
    
    import { useWalletClient } from "wagmi";
    
    const { data: walletClient } = useWalletClient();

    Very similar to wagmi, but without using hooks. Construct your own WalletClient object.

    Then, construct a Web3SignatureProvider, passing in the ethers Provider or viem WalletClient.

    Then, construct a RequestNetwork, passing in the:

    • Request Node URL. In this example, we use the Sepolia Request Node Gateway.

    • Web3SignatureProvider constructed in the previous step.

    Then, prepare the Request creation parameters:

    Then, call createRequest() to prepare a Request object.

    Finally, call request.waitForConfirmation() to wait until:

    • The request contents are persisted in IPFS

    • The Content-addressable ID (CID) is stored on-chain

    • The resulting on-chain event is indexed by the storage subgraph.

    CodeSandBox: Create a request

    Pay a request

    First, construct a RequestNetwork object and connect it to a Request Node. In this example, we use the Sepolia Request Node Gateway:

    Note that paying a request doesn't require a SignatureProvider be passed into the RequestNetwork object.

    Then, retrieve the request and get the request data. Take note of the current request balance, to be used later for payment detection.

    Then, construct an ethers v5 Provider and Signer. These allow you to read and write to the chain, respectively.

    Unfortunately, the Request Network SDK does not yet support ethers v6.

    Ethers.js Adapters copied from https://wagmi.sh/react/ethers-adapters

    Very similar to wagmi, but without using hooks. Instead, call publicClientToProvider() or walletClientToSigner()

    Then, check that the payer has sufficient funds using hasSufficientFunds()

    Then, in the case of an ERC-20 request, check that the payer has granted sufficient approval using hasErc20Approval(). If not, submit an approval transaction using approveErc20. Wait for an appropriate number of block confirmations. On Sepolia or Ethereum, 2 block confirmations should suffice. Other chains may require more.

    Finally, pay the request using payRequest()

    You can detect that the payment was successful by polling the request and waiting until the request balance is greater than or equal to the expected amount.

    CodeSandBox: Create and pay a request and detect a payment

    Video: Create and pay a request and detect a payment

    Retrieve a user's requests

    First, construct a RequestNetwork object and connect it to a Request Node. In this example, we use the Sepolia Request Node Gateway:

    Then, call fromIdentity() to get an array of Request objects or fromRequestId() to get a single Request object. This function retrieves the Requests stored in IPFS and queries on-chain events to determine the balances paid so far. Finally, call getData() on each Request to get the request contents.

    CodeSandBox: Retrieve a user's requests

    Video: Retrieve a user's requests

    https://github.com/RequestNetwork/requestNetwork/blob/master/packages/payment-processor/test/payment/batch-proxy.test.ts
    import { providers } from "ethers";
    
    let provider;
    if (process.env.WEB3_PROVIDER_URL === undefined) {
      // Connect to Metamask and other injected wallets
      provider = new providers.Web3Provider(window.ethereum);
    } else {
      // Connect to your own Ethereum node or 3rd party node provider
      provider = new providers.JsonRpcProvider(process.env.WEB3_PROVIDER_URL);
    }
    // getDefaultProvider() won't work because it doesn't include a Signer.
    
    const signer = await provider.getSigner();
    page.tsx
    import { useEthersV5Provider } from './use-ethers-v5-provider';
    import { useEthersV5Signer } from './use-ethers-v5-signer';
    
    return Page() {
      const provider = useEthersV5Provider();
      const signer = useEthersV5Signer();
      ...
    }
    use-ethers-v5-provider.ts
    import { useMemo } from "react";
    import { providers } from "ethers";
    import { type HttpTransport } from "viem";
    import { type PublicClient, usePublicClient } from "wagmi";
    
    export function publicClientToProvider(publicClient: PublicClient) {
      const { chain, transport } = publicClient;
      const network = {
        chainId: chain.id,
        name: chain.name,
        ensAddress: chain.contracts?.ensRegistry?.address,
      };
      if (transport.type === "fallback")
        return new providers.FallbackProvider(
          (transport.transports as ReturnType<HttpTransport>[]).map(
            ({ value }) => new providers.JsonRpcProvider(value?.url, network)
          )
        );
    
      return new providers.JsonRpcProvider(transport.url as string, network);
    }
    
    /** Hook to convert a viem Public Client to an ethers.js Provider. */
    export function useEthersV5Provider({ chainId }: { chainId?: number } = {}) {
      const publicClient = usePublicClient({ chainId });
      return useMemo(() => publicClientToProvider(publicClient), [publicClient]);
    }
    use-ethers-v5-signer.ts
    import { useMemo } from "react";
    import { providers } from "ethers";
    import { type WalletClient, useWalletClient } from "wagmi";
    
    export function walletClientToSigner(walletClient: WalletClient) {
      const { account, chain, transport } = walletClient;
      const network = {
        chainId: chain.id,
        name: chain.name,
        ensAddress: chain.contracts?.ensRegistry?.address,
      };
      const provider = new providers.Web3Provider(transport, network);
      const signer = provider.getSigner(account.address);
      return signer;
    }
    
    /** Hook to convert a viem Wallet Client to an ethers.js Signer. */
    export function useEthersV5Signer({ chainId }: { chainId?: number } = {}) {
      const { data: walletClient } = useWalletClient({ chainId });
      return useMemo(
        () => (walletClient ? walletClientToSigner(walletClient) : undefined),
        [walletClient]
      );
    }
    
    const identityAddress = "0x519145B771a6e450461af89980e5C17Ff6Fd8A92";
    const requests = await requestClient.fromIdentity({
      type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
      value: identityAddress,
    });
    const requestDatas = requests.map((request) => request.getData());
    const request = await requestClient.fromRequestId(
      '019830e9ec0439e53ec41fc627fd1d0293ec4bc61c2a647673ec5aaaa0e6338855',
    );
    const requestData = request.getData();
    import { Web3SignatureProvider } from "@requestnetwork/web3-signature";
    
    const web3SignatureProvider = new Web3SignatureProvider(provider);
    import { RequestNetwork } from "@requestnetwork/request-client.js"
    
    const requestClient = new RequestNetwork({
      nodeConnectionConfig: { 
        baseURL: "https://sepolia.gateway.request.network/",
      },
      signatureProvider: web3SignatureProvider,
    });
    import { Types, Utils } from "@requestnetwork/request-client.js";
    
    const payeeIdentity = '0x7eB023BFbAeE228de6DC5B92D0BeEB1eDb1Fd567';
    const payerIdentity = '0x519145B771a6e450461af89980e5C17Ff6Fd8A92';
    const paymentRecipient = payeeIdentity;
    const feeRecipient = '0x0000000000000000000000000000000000000000';
    
    const requestCreateParameters = {
      requestInfo: {
        
        // The currency in which the request is denominated
        currency: {
          type: Types.RequestLogic.CURRENCY.ERC20,
          value: '0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C',
          network: 'sepolia',
        },
        
        // The expected amount as a string, in parsed units, respecting `decimals`
        // Consider using `parseUnits()` from ethers or viem
        expectedAmount: '1000000000000000000',
        
        // The payee identity. Not necessarily the same as the payment recipient.
        payee: {
          type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
          value: payeeIdentity,
        },
        
        // The payer identity. If omitted, any identity can pay the request.
        payer: {
          type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
          value: payerIdentity,
        },
        
        // The request creation timestamp.
        timestamp: Utils.getCurrentTimestampInSecond(),
      },
      
      // The paymentNetwork is the method of payment and related details.
      paymentNetwork: {
        id: Types.Extension.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
        parameters: {
          paymentNetworkName: 'sepolia',
          paymentAddress: payeeIdentity,
          feeAddress: feeRecipient,  
          feeAmount: '0',
        },
      },
      
      // The contentData can contain anything.
      // Consider using rnf_invoice format from @requestnetwork/data-format
      contentData: {
        reason: '🍕',
        dueDate: '2023.06.16',
      },
      
      // The identity that signs the request, either payee or payer identity.
      signer: {
        type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
        value: payeeIdentity,
      },
    };
    const request = await requestClient.createRequest(requestCreateParameters);
    const confirmedRequestData = await request.waitForConfirmation();
    import { RequestNetwork, Types } from "@requestnetwork/request-client.js";
    
    const requestClient = new RequestNetwork({
      nodeConnectionConfig: { 
        baseURL: "https://sepolia.gateway.request.network/",
      }
    });
    const request = await requestClient.fromRequestId(
      '019830e9ec0439e53ec41fc627fd1d0293ec4bc61c2a647673ec5aaaa0e6338855',
    );
    const requestData = request.getData();
    import { hasSufficientFunds } from "@requestnetwork/payment-processor";
    
    const _hasSufficientFunds = await hasSufficientFunds(
      requestData,
      payerAddress,
      {
        provider: provider,
      },
    );
    import { approveErc20, hasErc20Approval } from "@requestnetwork/payment-processor";
    
    const _hasErc20Approval = await hasErc20Approval(
      requestData,
      payerAddress,
      provider
    );
    if (!_hasErc20Approval) {
      const approvalTx = await approveErc20(requestData, signer);
      await approvalTx.wait(2);
    }
    import { payRequest } from "@requestnetwork/payment-processor";
    
    const paymentTx = await payRequest(requestData, signer);
    await paymentTx.wait(2);
    const request = await requestClient.fromRequestId(requestData.requestId);
    let requestData = request.getData();
    
    while (requestData.balance?.balance < requestData.expectedAmount) {
      requestData = await request.refresh();
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
    import { RequestNetwork, Types } from "@requestnetwork/request-client.js";
    const requestClient = new RequestNetwork({
      nodeConnectionConfig: { 
        baseURL: "https://sepolia.gateway.request.network/",
      },
    });
    https://github.com/RequestNetwork/rn-expo-support/blob/5a771c31051ce89c4feee533692028d45e1415ab/cryptoPolyfill.js
    import nacl from "tweetnacl";
    import forge from "node-forge";
    import { Buffer } from "buffer";
    
    const randomBytes = (size, callback) => {
      if (typeof size !== "number") {
        throw new TypeError("Expected number");
      }
      const bytes = Buffer.from(nacl.randomBytes(size));
      if (callback) {
        callback(null, bytes);
        return;
      }
      return bytes;
    };
    
    const createHash = (algorithm) => {
      const md = forge.md[algorithm.toLowerCase()].create();
      return {
        update: function (data) {
          md.update(
            typeof data === "string" ? data : forge.util.createBuffer(data)
          );
          return this;
        },
        digest: function (encoding) {
          const digest = md.digest().getBytes();
          return encoding === "hex"
            ? forge.util.bytesToHex(digest)
            : Buffer.from(digest, "binary");
        },
      };
    };
    
    const createCipheriv = (algorithm, key, iv) => {
      const cipher = forge.cipher.createCipher(
        algorithm,
        forge.util.createBuffer(key)
      );
      cipher.start({ iv: forge.util.createBuffer(iv) });
      let output = forge.util.createBuffer();
    
      return {
        update: (data) => {
          cipher.update(forge.util.createBuffer(data));
          output.putBuffer(cipher.output);
          return Buffer.from(output.getBytes(), "binary");
        },
        final: () => {
          cipher.finish();
          output.putBuffer(cipher.output);
          const result = Buffer.from(output.getBytes(), "binary");
          output.clear();
          return result;
        },
        getAuthTag: () => {
          if (algorithm.includes("gcm")) {
            return Buffer.from(cipher.mode.tag.getBytes(), "binary");
          }
          throw new Error("getAuthTag is only supported for GCM mode");
        },
      };
    };
    
    const createDecipheriv = (algorithm, key, iv) => {
      const decipher = forge.cipher.createDecipher(
        algorithm,
        forge.util.createBuffer(key)
      );
      decipher.start({ iv: forge.util.createBuffer(iv) });
      let output = forge.util.createBuffer();
      let authTag;
    
      return {
        update: (data) => {
          decipher.update(forge.util.createBuffer(data));
          output.putBuffer(decipher.output);
          return Buffer.from(output.getBytes(), "binary");
        },
        final: () => {
          decipher.finish();
          output.putBuffer(decipher.output);
          const result = Buffer.from(output.getBytes(), "binary");
          output.clear();
          return result;
        },
        setAuthTag: (tag) => {
          if (algorithm.includes("gcm")) {
            authTag = tag;
            decipher.mode.tag = forge.util.createBuffer(tag);
          } else {
            throw new Error("setAuthTag is only supported for GCM mode");
          }
        },
      };
    };
    
    const pbkdf2 = (password, salt, iterations, keylen, digest, callback) => {
      try {
        const derivedKey = forge.pkcs5.pbkdf2(
          password,
          salt,
          iterations,
          keylen,
          digest
        );
        const result = Buffer.from(derivedKey, "binary");
        if (callback) {
          callback(null, result);
        } else {
          return result;
        }
      } catch (error) {
        if (callback) {
          callback(error);
        } else {
          throw error;
        }
      }
    };
    
    const randomFillSync = (buffer, offset, size) => {
      const randomBytes = nacl.randomBytes(size);
      buffer.set(randomBytes, offset);
      return buffer;
    };
    
    const timingSafeEqual = (a, b) => {
      if (a.length !== b.length) {
        return false;
      }
      let result = 0;
      for (let i = 0; i < a.length; i++) {
        result |= a[i] ^ b[i];
      }
      return result === 0;
    };
    
    const cryptoPolyfill = {
      randomBytes,
      createHash,
      createCipheriv,
      createDecipheriv,
      pbkdf2,
      randomFillSync,
      timingSafeEqual,
    };
    
    cryptoPolyfill.default = cryptoPolyfill;
    
    module.exports = {
      randomBytes,
      createHash,
      createCipheriv,
      createDecipheriv,
      pbkdf2,
      randomFillSync,
      timingSafeEqual,
    };
    
    export default cryptoPolyfill;
    
    import { BigNumber, providers, Wallet } from 'ethers';
    
    import {
      ClientTypes,
      ExtensionTypes,
      IdentityTypes,
      RequestLogicTypes,
    } from '@requestnetwork/types';
    import {
      approveErc20BatchConversionIfNeeded,
      getBatchConversionProxyAddress,
      getErc20Balance,
      IConversionPaymentSettings,
      payBatchConversionProxyRequest,
      prepareBatchConversionPaymentTransaction,
    } from '../../src';
    import { deepCopy } from '@requestnetwork/utils';
    import { revokeErc20Approval } from '@requestnetwork/payment-processor/src/payment/utils';
    import { batchConversionPaymentsArtifact } from '@requestnetwork/smart-contracts';
    import { CurrencyManager, UnsupportedCurrencyError } from '@requestnetwork/currency';
    import { CurrencyTypes } from '@requestnetwork/types/src';
    import { EnrichedRequest, IRequestPaymentOptions } from 'payment-processor/src/types';
    
    /* eslint-disable no-magic-numbers */
    /* eslint-disable @typescript-eslint/no-unused-expressions */
    
    /** Used to to calculate batch fees */
    const BATCH_DENOMINATOR = 10000;
    const BATCH_FEE = 30;
    const BATCH_CONV_FEE = 30;
    
    const DAITokenAddress = '0x38cF23C52Bb4B13F051Aec09580a2dE845a7FA35';
    const FAUTokenAddress = '0x9FBDa871d559710256a2502A2517b794B482Db40';
    const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat';
    const paymentAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732';
    const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef';
    const provider = new providers.JsonRpcProvider('http://localhost:8545');
    const wallet = Wallet.fromMnemonic(mnemonic).connect(provider);
    
    const currencyManager = new CurrencyManager([
      ...CurrencyManager.getDefaultList(),
      {
        address: DAITokenAddress,
        decimals: 18,
        network: 'private',
        symbol: 'DAI',
        type: RequestLogicTypes.CURRENCY.ERC20,
      },
    ]);
    
    // Cf. ERC20Alpha in TestERC20.sol
    const currency: RequestLogicTypes.ICurrency = {
      type: RequestLogicTypes.CURRENCY.ERC20,
      value: DAITokenAddress,
      network: 'private',
    };
    
    const nativeCurrency: RequestLogicTypes.ICurrency = {
      type: RequestLogicTypes.CURRENCY.ETH,
      value: 'ETH-private',
      network: 'private',
    };
    
    const conversionPaymentSettings: IConversionPaymentSettings = {
      currency: currency,
      maxToSpend: '10000000000000000000000000000',
      currencyManager: currencyManager,
    };
    
    const ethConversionPaymentSettings: IConversionPaymentSettings = {
      currency: nativeCurrency,
      maxToSpend: '200000000000000000000',
      currencyManager: currencyManager,
    };
    
    const options: IRequestPaymentOptions = {
      conversion: {
        currency: currency,
        currencyManager: currencyManager,
      },
      skipFeeUSDLimit: true,
    };
    
    // requests setting
    
    const EURExpectedAmount = 55000_00; // 55 000 €
    const EURFeeAmount = 2_00; // 2 €
    // amounts used for DAI and FAU requests
    const expectedAmount = 100000;
    const feeAmount = 100;
    
    const EURToERC20ValidRequest: ClientTypes.IRequestData = {
      balance: {
        balance: '0',
        events: [],
      },
      contentData: {},
      creator: {
        type: IdentityTypes.TYPE.ETHEREUM_ADDRESS,
        value: wallet.address,
      },
      currency: 'EUR',
      currencyInfo: {
        type: RequestLogicTypes.CURRENCY.ISO4217,
        value: 'EUR',
      },
      events: [],
      expectedAmount: EURExpectedAmount,
      extensions: {
        [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: {
          events: [],
          id: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
          type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
          values: {
            feeAddress,
            feeAmount: EURFeeAmount,
            paymentAddress,
            salt: 'salt',
            network: 'private',
            acceptedTokens: [DAITokenAddress],
          },
          version: '0.1.0',
        },
      },
      extensionsData: [],
      meta: {
        transactionManagerMeta: {},
      },
      pending: null,
      requestId: 'abcd',
      state: RequestLogicTypes.STATE.CREATED,
      timestamp: 0,
      version: '1.0',
    };
    
    const DAIValidRequest: ClientTypes.IRequestData = {
      balance: {
        balance: '0',
        events: [],
      },
      contentData: {},
      creator: {
        type: IdentityTypes.TYPE.ETHEREUM_ADDRESS,
        value: wallet.address,
      },
      currency: 'DAI',
      currencyInfo: currency,
      events: [],
      expectedAmount: expectedAmount,
      extensions: {
        [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT]: {
          events: [],
          id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
          type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
          values: {
            feeAddress,
            feeAmount: feeAmount,
            paymentAddress: paymentAddress,
            salt: 'salt',
          },
          version: '0.1.0',
        },
      },
      extensionsData: [],
      meta: {
        transactionManagerMeta: {},
      },
      pending: null,
      requestId: 'abcd',
      state: RequestLogicTypes.STATE.CREATED,
      timestamp: 0,
      version: '1.0',
    };
    
    const EURToETHValidRequest: ClientTypes.IRequestData = {
      ...EURToERC20ValidRequest,
      extensions: {
        [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY]: {
          events: [],
          id: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY,
          type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
          values: {
            feeAddress,
            feeAmount: EURFeeAmount,
            paymentAddress,
            salt: 'salt',
            network: 'private',
          },
          version: '0.1.0',
        },
      },
    };
    
    const ETHValidRequest: ClientTypes.IRequestData = {
      ...DAIValidRequest,
      currency: 'ETH-private',
      currencyInfo: nativeCurrency,
      extensions: {
        [ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT]: {
          events: [],
          id: ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT,
          type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
          values: {
            feeAddress,
            feeAmount: feeAmount,
            paymentAddress,
            salt: 'salt',
            network: 'private',
          },
          version: '0.1.0',
        },
      },
    };
    
    const FAUValidRequest = deepCopy(DAIValidRequest) as ClientTypes.IRequestData;
    FAUValidRequest.currencyInfo = {
      network: 'private',
      type: RequestLogicTypes.CURRENCY.ERC20 as any,
      value: FAUTokenAddress,
    };
    
    let enrichedRequests: EnrichedRequest[] = [];
    // EUR and FAU requests modified within tests to throw errors
    let EURRequest: ClientTypes.IRequestData;
    let FAURequest: ClientTypes.IRequestData;
    
    /**
     * Calcul the expected amount to pay for X euro into Y tokens
     * @param amount in fiat: EUR
     */
    const expectedConversionAmount = (amount: number, isNative?: boolean): BigNumber => {
      //   token decimals       10**18
      //   amount               amount / 100
      //   AggEurUsd.sol     x  1.20
      //   AggDaiUsd.sol     /  1.01 OR AggEthUsd.sol / 500
      return BigNumber.from(10)
        .pow(18)
        .mul(amount)
        .div(100)
        .mul(120)
        .div(100)
        .mul(100)
        .div(isNative ? 50000 : 101);
    };
    
    describe('batch-proxy', () => {
      beforeAll(async () => {
        // Revoke DAI and FAU approvals
        await revokeErc20Approval(
          getBatchConversionProxyAddress(DAIValidRequest),
          DAITokenAddress,
          wallet,
        );
        await revokeErc20Approval(
          getBatchConversionProxyAddress(FAUValidRequest),
          FAUTokenAddress,
          wallet,
        );
    
        // Approve the contract to spent DAI with a conversion request
        const approvalTx = await approveErc20BatchConversionIfNeeded(
          EURToERC20ValidRequest,
          wallet.address,
          wallet.provider,
          undefined,
          conversionPaymentSettings,
        );
        expect(approvalTx).toBeDefined();
        if (approvalTx) {
          await approvalTx.wait(1);
        }
      });
    
      describe(`Conversion:`, () => {
        beforeEach(() => {
          jest.restoreAllMocks();
          EURRequest = deepCopy(EURToERC20ValidRequest);
          enrichedRequests = [
            {
              paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
              request: EURToERC20ValidRequest,
              paymentSettings: conversionPaymentSettings,
            },
            {
              paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
              request: EURRequest,
              paymentSettings: conversionPaymentSettings,
            },
          ];
        });
    
        describe('Throw an error', () => {
          it('should throw an error if the token is not accepted', async () => {
            await expect(
              payBatchConversionProxyRequest(
                [
                  {
                    paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
                    request: EURToERC20ValidRequest,
                    paymentSettings: {
                      ...conversionPaymentSettings,
                      currency: {
                        ...conversionPaymentSettings.currency,
                        value: '0x775eb53d00dd0acd3ec1696472105d579b9b386b',
                      },
                    } as IConversionPaymentSettings,
                  },
                ],
                wallet,
                options,
              ),
            ).rejects.toThrowError(
              new UnsupportedCurrencyError({
                value: '0x775eb53d00dd0acd3ec1696472105d579b9b386b',
                network: 'private',
              }),
            );
          });
          it('should throw an error if request has no currency within paymentSettings', async () => {
            const wrongPaymentSettings = deepCopy(conversionPaymentSettings);
            wrongPaymentSettings.currency = undefined;
            await expect(
              payBatchConversionProxyRequest(
                [
                  {
                    paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
                    request: EURRequest,
                    paymentSettings: wrongPaymentSettings,
                  },
                ],
                wallet,
                options,
              ),
            ).rejects.toThrowError('currency must be provided in the paymentSettings');
          });
          it('should throw an error if the request has a wrong network', async () => {
            EURRequest.extensions = {
              // ERC20_FEE_PROXY_CONTRACT instead of ANY_TO_ERC20_PROXY
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: {
                events: [],
                id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
                type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
                values: {
                  feeAddress,
                  feeAmount: feeAmount,
                  paymentAddress: paymentAddress,
                  salt: 'salt',
                  network: 'fakePrivate',
                },
                version: '0.1.0',
              },
            };
    
            await expect(
              payBatchConversionProxyRequest(enrichedRequests, wallet, options),
            ).rejects.toThrowError('All the requests must have the same network');
          });
          it('should throw an error if the request has a wrong payment network id', async () => {
            EURRequest.extensions = {
              // ERC20_FEE_PROXY_CONTRACT instead of ANY_TO_ERC20_PROXY
              [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT]: {
                events: [],
                id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
                type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
                values: {
                  feeAddress,
                  feeAmount: feeAmount,
                  paymentAddress: paymentAddress,
                  network: 'private',
                  salt: 'salt',
                },
                version: '0.1.0',
              },
            };
    
            await expect(
              payBatchConversionProxyRequest(enrichedRequests, wallet, options),
            ).rejects.toThrowError(
              'request cannot be processed, or is not an pn-any-to-erc20-proxy request',
            );
          });
          it("should throw an error if one request's currencyInfo has no value", async () => {
            EURRequest.currencyInfo.value = '';
            await expect(
              payBatchConversionProxyRequest(enrichedRequests, wallet, options),
            ).rejects.toThrowError("The currency '' is unknown or not supported");
          });
          it('should throw an error if a request has no extension', async () => {
            EURRequest.extensions = [] as any;
            await expect(
              payBatchConversionProxyRequest(enrichedRequests, wallet, options),
            ).rejects.toThrowError('no payment network found');
          });
          it('should throw an error if there is a wrong version mapping', async () => {
            EURRequest.extensions = {
              [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: {
                ...EURRequest.extensions[ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY],
                version: '0.3.0',
              },
            };
            await expect(
              payBatchConversionProxyRequest(enrichedRequests, wallet, options),
            ).rejects.toThrowError('Every payment network type and version must be identical');
          });
        });
    
        describe('payment', () => {
          it('should consider override parameters', async () => {
            const spy = jest.fn();
            const originalSendTransaction = wallet.sendTransaction.bind(wallet);
            wallet.sendTransaction = spy;
            await payBatchConversionProxyRequest(
              [
                {
                  paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
                  request: EURToERC20ValidRequest,
                  paymentSettings: conversionPaymentSettings,
                },
              ],
              wallet,
              options,
              { gasPrice: '20000000000' },
            );
            expect(spy).toHaveBeenCalledWith({
              data: '0x92cddb91000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000c5fdf4076b8f3a5357c5e395ab970b5b54098fef000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f17f52151ebef6c7334fad080c5704d77216b73200000000000000000000000000000000000000000000000000000500918bd80000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000bebc2000000000000000000000000000000000000000000204fce5e3e250261100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000017b4158805772ced11225e77339f90beb5aae968000000000000000000000000775eb53d00dd0acd3ec1696472105d579b9b386b00000000000000000000000038cf23c52bb4b13f051aec09580a2de845a7fa35000000000000000000000000000000000000000000000000000000000000000886dfbccad783599a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
              gasPrice: '20000000000',
              to: getBatchConversionProxyAddress(EURToERC20ValidRequest, '0.1.0'),
              value: BigNumber.from(0),
            });
            wallet.sendTransaction = originalSendTransaction;
          });
    
          for (const skipFeeUSDLimit of ['true', 'false']) {
            it(`should convert and pay a request in EUR with ERC20, ${
              skipFeeUSDLimit === 'true' ? 'skipFeeUSDLimit' : 'no skipFeeUSDLimit'
            } `, async () => {
              // Get the balances to compare after payment
              const initialETHFromBalance = await wallet.getBalance();
              const initialDAIFromBalance = await getErc20Balance(
                DAIValidRequest,
                wallet.address,
                provider,
              );
    
              const tx = await payBatchConversionProxyRequest(
                [
                  {
                    paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
                    request: EURToERC20ValidRequest,
                    paymentSettings: conversionPaymentSettings,
                  },
                ],
                wallet,
                {
                  ...options,
                  skipFeeUSDLimit: skipFeeUSDLimit === 'true',
                },
              );
              const confirmedTx = await tx.wait(1);
              expect(confirmedTx.status).toEqual(1);
              expect(tx.hash).toBeDefined();
    
              // Get the new balances
              const ETHFromBalance = await wallet.getBalance();
              const DAIFromBalance = await getErc20Balance(DAIValidRequest, wallet.address, provider);
    
              // Check each balance
              const amountToPay = expectedConversionAmount(EURExpectedAmount);
              const feeToPay = expectedConversionAmount(EURFeeAmount);
              const totalFeeToPay =
                skipFeeUSDLimit === 'true'
                  ? amountToPay.add(feeToPay).mul(BATCH_CONV_FEE).div(BATCH_DENOMINATOR).add(feeToPay)
                  : BigNumber.from('150891089116411368418'); // eq to $150 batch fee (USD limit) + 2€
              const expectedAmountToPay = amountToPay.add(totalFeeToPay);
              expect(
                BigNumber.from(initialETHFromBalance).sub(ETHFromBalance).toNumber(),
              ).toBeGreaterThan(0);
              expect(
                BigNumber.from(initialDAIFromBalance).sub(BigNumber.from(DAIFromBalance)),
                // Calculation of expectedAmountToPay when there there is no fee USD limit
                //   expectedAmount:    55 000
                //   feeAmount:        +     2
                //   AggEurUsd.sol     x  1.20
                //   AggDaiUsd.sol     /  1.01
                //   BATCH_CONV_FEE      x  1.003
              ).toEqual(expectedAmountToPay);
            });
            it(`should convert and pay a request in EUR with ETH, ${
              skipFeeUSDLimit === 'true' ? 'skipFeeUSDLimit' : 'no skipFeeUSDLimit'
            } `, async () => {
              const fromOldBalance = await provider.getBalance(wallet.address);
              const toOldBalance = await provider.getBalance(paymentAddress);
              const feeOldBalance = await provider.getBalance(feeAddress);
    
              const tx = await payBatchConversionProxyRequest(
                [
                  {
                    paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY,
                    request: EURToETHValidRequest,
                    paymentSettings: ethConversionPaymentSettings,
                  },
                ],
                wallet,
                {
                  ...options,
                  skipFeeUSDLimit: skipFeeUSDLimit === 'true',
                },
              );
              const confirmedTx = await tx.wait(1);
              expect(confirmedTx.status).toEqual(1);
              expect(tx.hash).toBeDefined();
    
              // Get the new balances
              const fromNewBalance = await provider.getBalance(wallet.address);
              const toNewBalance = await provider.getBalance(paymentAddress);
              const feeNewBalance = await provider.getBalance(feeAddress);
              const gasPrice = confirmedTx.effectiveGasPrice;
    
              const amountToPay = expectedConversionAmount(EURExpectedAmount, true);
              const feeToPay = expectedConversionAmount(EURFeeAmount, true);
              const totalFeeToPay =
                skipFeeUSDLimit === 'true'
                  ? amountToPay.add(feeToPay).mul(BATCH_CONV_FEE).div(BATCH_DENOMINATOR).add(feeToPay)
                  : // Capped fee total:
                    //                2 (Fees in €)
                    //           x 1.20
                    //           +  150 (Batch Fees capped to 150$)
                    //           /  500
                    BigNumber.from('304800000000000000');
              const expectedAmountToPay = amountToPay.add(totalFeeToPay);
    
              expect(
                fromOldBalance
                  .sub(fromNewBalance)
                  .sub(confirmedTx.cumulativeGasUsed.mul(gasPrice))
                  .toString(),
              ).toEqual(expectedAmountToPay.toString());
    
              expect(feeNewBalance.sub(feeOldBalance).toString()).toEqual(totalFeeToPay.toString());
    
              expect(toNewBalance.sub(toOldBalance).toString()).toEqual(amountToPay.toString());
            });
          }
    
          it('should convert and pay two requests in EUR with ERC20', async () => {
            // Get initial balances
            const initialETHFromBalance = await wallet.getBalance();
            const initialDAIFromBalance = await getErc20Balance(
              DAIValidRequest,
              wallet.address,
              provider,
            );
            // Convert and pay
            const tx = await payBatchConversionProxyRequest(
              Array(2).fill({
                paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
                request: EURToERC20ValidRequest,
                paymentSettings: conversionPaymentSettings,
              }),
              wallet,
              options,
            );
            const confirmedTx = await tx.wait(1);
            expect(confirmedTx.status).toEqual(1);
            expect(tx.hash).toBeDefined();
    
            // Get balances
            const ETHFromBalance = await wallet.getBalance();
            const DAIFromBalance = await getErc20Balance(DAIValidRequest, wallet.address, provider);
    
            // Checks ETH balances
            expect(
              BigNumber.from(initialETHFromBalance).sub(ETHFromBalance).toNumber(),
            ).toBeGreaterThan(0);
    
            // Checks DAI balances
            const amountToPay = expectedConversionAmount(EURExpectedAmount).mul(2); // multiply by the number of requests: 2
            const feeToPay = expectedConversionAmount(EURFeeAmount).mul(2); // multiply by the number of requests: 2
            const expectedAmoutToPay = amountToPay
              .add(feeToPay)
              .mul(BATCH_DENOMINATOR + BATCH_CONV_FEE)
              .div(BATCH_DENOMINATOR);
            expect(BigNumber.from(initialDAIFromBalance).sub(BigNumber.from(DAIFromBalance))).toEqual(
              expectedAmoutToPay,
            );
          });
    
          it('should convert and pay two requests in EUR with ETH', async () => {
            const fromOldBalance = await provider.getBalance(wallet.address);
            const toOldBalance = await provider.getBalance(paymentAddress);
            const feeOldBalance = await provider.getBalance(feeAddress);
    
            // Convert and pay
            const tx = await payBatchConversionProxyRequest(
              Array(2).fill({
                paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY,
                request: EURToETHValidRequest,
                paymentSettings: ethConversionPaymentSettings,
              }),
              wallet,
              options,
            );
            const confirmedTx = await tx.wait(1);
            expect(confirmedTx.status).toEqual(1);
            expect(tx.hash).toBeDefined();
    
            // Get the new balances
            const fromNewBalance = await provider.getBalance(wallet.address);
            const toNewBalance = await provider.getBalance(paymentAddress);
            const feeNewBalance = await provider.getBalance(feeAddress);
            const gasPrice = confirmedTx.effectiveGasPrice;
    
            const amountToPay = expectedConversionAmount(EURExpectedAmount, true).mul(2);
            const feeToPay = expectedConversionAmount(EURFeeAmount, true).mul(2);
            const totalFeeToPay = amountToPay
              .add(feeToPay)
              .mul(BATCH_CONV_FEE)
              .div(BATCH_DENOMINATOR)
              .add(feeToPay);
            const expectedAmountToPay = amountToPay.add(totalFeeToPay);
    
            expect(
              fromOldBalance
                .sub(fromNewBalance)
                .sub(confirmedTx.cumulativeGasUsed.mul(gasPrice))
                .toString(),
            ).toEqual(expectedAmountToPay.toString());
    
            expect(feeNewBalance.sub(feeOldBalance).toString()).toEqual(totalFeeToPay.toString());
    
            expect(toNewBalance.sub(toOldBalance).toString()).toEqual(amountToPay.toString());
          });
    
          it('should pay heterogeneous payments (ETH/ERC20 with/without conversion)', async () => {
            const fromOldBalanceETH = await provider.getBalance(wallet.address);
            const toOldBalanceETH = await provider.getBalance(paymentAddress);
            const feeOldBalanceETH = await provider.getBalance(feeAddress);
            const fromOldBalanceDAI = await getErc20Balance(DAIValidRequest, wallet.address, provider);
            const toOldBalanceDAI = await getErc20Balance(DAIValidRequest, paymentAddress, provider);
            const feeOldBalanceDAI = await getErc20Balance(DAIValidRequest, feeAddress, provider);
    
            // Convert the two first requests and pay the three requests
            const tx = await payBatchConversionProxyRequest(
              [
                {
                  paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
                  request: EURToERC20ValidRequest,
                  paymentSettings: conversionPaymentSettings,
                },
                {
                  paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY,
                  request: EURToERC20ValidRequest,
                  paymentSettings: conversionPaymentSettings,
                },
                {
                  paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
                  request: DAIValidRequest,
                  paymentSettings: { maxToSpend: '0' },
                },
                {
                  paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT,
                  request: ETHValidRequest,
                  paymentSettings: {
                    ...ethConversionPaymentSettings,
                    maxToSpend: '0',
                  },
                },
                {
                  paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY,
                  request: EURToETHValidRequest,
                  paymentSettings: ethConversionPaymentSettings,
                },
              ],
              wallet,
              options,
            );
            const confirmedTx = await tx.wait(1);
            expect(confirmedTx.status).toEqual(1);
            expect(tx.hash).toBeDefined();
    
            const gasPrice = confirmedTx.effectiveGasPrice;
    
            const fromNewBalanceETH = await provider.getBalance(wallet.address);
            const toNewBalanceETH = await provider.getBalance(paymentAddress);
            const feeNewBalanceETH = await provider.getBalance(feeAddress);
            const fromNewBalanceDAI = await getErc20Balance(DAIValidRequest, wallet.address, provider);
            const toNewBalanceDAI = await getErc20Balance(DAIValidRequest, paymentAddress, provider);
            const feeNewBalanceDAI = await getErc20Balance(DAIValidRequest, feeAddress, provider);
    
            // Computes amount related to DAI with conversion payments
            const DAIConvAmount = expectedConversionAmount(EURExpectedAmount).mul(2);
            const DAIConvFeeAmount = expectedConversionAmount(EURFeeAmount).mul(2);
            const DAIConvTotalFees = DAIConvAmount.add(DAIConvFeeAmount)
              .mul(BATCH_CONV_FEE)
              .div(BATCH_DENOMINATOR)
              .add(DAIConvFeeAmount);
            const DAIConvTotal = DAIConvAmount.add(DAIConvTotalFees);
    
            // Computes amount related to payments without conversion (same for ETH or ERC20)
            const NoConvAmount = BigNumber.from(expectedAmount);
            const NoConvFeeAmount = BigNumber.from(feeAmount);
            const NoConvTotalFees = NoConvAmount.add(NoConvFeeAmount)
              .mul(BATCH_CONV_FEE)
              .div(BATCH_DENOMINATOR)
              .add(NoConvFeeAmount);
            const NoConvTotal = NoConvAmount.add(NoConvTotalFees);
    
            // Computes amount related to ETH with conversion payments
            const ETHConvAmount = expectedConversionAmount(EURExpectedAmount, true);
            const ETHConvFeeAmount = expectedConversionAmount(EURFeeAmount, true);
            const ETHConvTotalFees = ETHConvAmount.add(ETHConvFeeAmount)
              .mul(BATCH_CONV_FEE)
              .div(BATCH_DENOMINATOR)
              .add(ETHConvFeeAmount);
            const ETHConvTotal = ETHConvAmount.add(ETHConvTotalFees);
    
            // Totals
            const DAIAmount = DAIConvAmount.add(NoConvAmount);
            const DAIFeesTotal = DAIConvTotalFees.add(NoConvTotalFees);
            const DAITotal = DAIConvTotal.add(NoConvTotal);
            const ETHAmount = ETHConvAmount.add(NoConvAmount);
            const ETHFeesTotal = ETHConvTotalFees.add(NoConvTotalFees);
            const ETHTotal = ETHConvTotal.add(NoConvTotal);
    
            // DAI Checks
            expect(BigNumber.from(fromOldBalanceDAI).sub(fromNewBalanceDAI)).toEqual(DAITotal);
            expect(BigNumber.from(toNewBalanceDAI).sub(toOldBalanceDAI)).toEqual(DAIAmount);
            expect(BigNumber.from(feeNewBalanceDAI).sub(feeOldBalanceDAI)).toEqual(DAIFeesTotal);
    
            // ETH Checks
            expect(
              fromOldBalanceETH
                .sub(fromNewBalanceETH)
                .sub(confirmedTx.cumulativeGasUsed.mul(gasPrice))
                .toString(),
            ).toEqual(ETHTotal.toString());
            expect(toNewBalanceETH.sub(toOldBalanceETH)).toEqual(ETHAmount);
            expect(feeNewBalanceETH.sub(feeOldBalanceETH).toString()).toEqual(ETHFeesTotal.toString());
          }, 20000);
        });
      });
    
      describe('No conversion:', () => {
        beforeEach(() => {
          FAURequest = deepCopy(FAUValidRequest);
          enrichedRequests = [
            {
              paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
              request: DAIValidRequest,
              paymentSettings: { maxToSpend: '0' },
            },
            {
              paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
              request: FAURequest,
              paymentSettings: { maxToSpend: '0' },
            },
          ];
        });
    
        describe('Throw an error', () => {
          it('should throw an error if the request is not erc20', async () => {
            FAURequest.currencyInfo.type = RequestLogicTypes.CURRENCY.ETH;
            await expect(
              payBatchConversionProxyRequest(enrichedRequests, wallet, options),
            ).rejects.toThrowError('wrong request currencyInfo type');
          });
    
          it("should throw an error if one request's currencyInfo has no value", async () => {
            FAURequest.currencyInfo.value = '';
            await expect(
              payBatchConversionProxyRequest(enrichedRequests, wallet, options),
            ).rejects.toThrowError(
              'request cannot be processed, or is not an pn-erc20-fee-proxy-contract request',
            );
          });
    
          it("should throw an error if one request's currencyInfo has no network", async () => {
            FAURequest.currencyInfo.network = '' as CurrencyTypes.ChainName;
            await expect(
              payBatchConversionProxyRequest(enrichedRequests, wallet, options),
            ).rejects.toThrowError(
              'request cannot be processed, or is not an pn-erc20-fee-proxy-contract request',
            );
          });
    
          it('should throw an error if request has no extension', async () => {
            FAURequest.extensions = [] as any;
            await expect(
              payBatchConversionProxyRequest(enrichedRequests, wallet, options),
            ).rejects.toThrowError('no payment network found');
          });
    
          it('should throw an error if there is a wrong version mapping', async () => {
            FAURequest.extensions = {
              [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT]: {
                ...DAIValidRequest.extensions[
                  ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT
                ],
                version: '0.3.0',
              },
            };
            await expect(
              payBatchConversionProxyRequest(enrichedRequests, wallet, options),
            ).rejects.toThrowError('Every payment network type and version must be identical');
          });
        });
    
        describe('payBatchConversionProxyRequest', () => {
          it('should consider override parameters', async () => {
            const spy = jest.fn();
            const originalSendTransaction = wallet.sendTransaction.bind(wallet);
            wallet.sendTransaction = spy;
            await payBatchConversionProxyRequest(enrichedRequests, wallet, options, {
              gasPrice: '20000000000',
            });
            expect(spy).toHaveBeenCalledWith({
              data: '0x92cddb9100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000c5fdf4076b8f3a5357c5e395ab970b5b54098fef00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000f17f52151ebef6c7334fad080c5704d77216b73200000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000038cf23c52bb4b13f051aec09580a2de845a7fa35000000000000000000000000000000000000000000000000000000000000000886dfbccad783599a000000000000000000000000000000000000000000000000000000000000000000000000f17f52151ebef6c7334fad080c5704d77216b73200000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009fbda871d559710256a2502a2517b794b482db40000000000000000000000000000000000000000000000000000000000000000886dfbccad783599a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
              gasPrice: '20000000000',
              to: getBatchConversionProxyAddress(DAIValidRequest, '0.1.0'),
              value: BigNumber.from(0),
            });
            wallet.sendTransaction = originalSendTransaction;
          });
          it(`should pay 2 different ERC20 requests with fees`, async () => {
            // Approve the contract for DAI and FAU tokens
            const FAUApprovalTx = await approveErc20BatchConversionIfNeeded(
              FAUValidRequest,
              wallet.address,
              wallet,
            );
            if (FAUApprovalTx) await FAUApprovalTx.wait(1);
    
            const DAIApprovalTx = await approveErc20BatchConversionIfNeeded(
              DAIValidRequest,
              wallet.address,
              wallet,
            );
            if (DAIApprovalTx) await DAIApprovalTx.wait(1);
    
            // Get initial balances
            const initialETHFromBalance = await wallet.getBalance();
            const initialDAIFromBalance = await getErc20Balance(
              DAIValidRequest,
              wallet.address,
              provider,
            );
            const initialDAIFeeBalance = await getErc20Balance(DAIValidRequest, feeAddress, provider);
    
            const initialFAUFromBalance = await getErc20Balance(
              FAUValidRequest,
              wallet.address,
              provider,
            );
            const initialFAUFeeBalance = await getErc20Balance(FAUValidRequest, feeAddress, provider);
    
            // Batch payment
            const tx = await payBatchConversionProxyRequest(enrichedRequests, wallet, options);
            const confirmedTx = await tx.wait(1);
            expect(confirmedTx.status).toBe(1);
            expect(tx.hash).not.toBeUndefined();
    
            // Get balances
            const ETHFromBalance = await wallet.getBalance();
            const DAIFromBalance = await getErc20Balance(DAIValidRequest, wallet.address, provider);
            const DAIFeeBalance = await getErc20Balance(DAIValidRequest, feeAddress, provider);
            const FAUFromBalance = await getErc20Balance(FAUValidRequest, wallet.address, provider);
            const FAUFeeBalance = await getErc20Balance(FAUValidRequest, feeAddress, provider);
    
            // Checks ETH balances
            expect(ETHFromBalance.lte(initialETHFromBalance)).toBeTruthy(); // 'ETH balance should be lower'
    
            // Check FAU balances
            const expectedFAUFeeAmountToPay =
              feeAmount + ((FAUValidRequest.expectedAmount as number) * BATCH_FEE) / BATCH_DENOMINATOR;
    
            expect(BigNumber.from(FAUFromBalance)).toEqual(
              BigNumber.from(initialFAUFromBalance).sub(
                (FAUValidRequest.expectedAmount as number) + expectedFAUFeeAmountToPay,
              ),
            );
            expect(BigNumber.from(FAUFeeBalance)).toEqual(
              BigNumber.from(initialFAUFeeBalance).add(expectedFAUFeeAmountToPay),
            );
            // Check DAI balances
            const expectedDAIFeeAmountToPay =
              feeAmount + ((DAIValidRequest.expectedAmount as number) * BATCH_FEE) / BATCH_DENOMINATOR;
    
            expect(BigNumber.from(DAIFromBalance)).toEqual(
              BigNumber.from(initialDAIFromBalance)
                .sub(DAIValidRequest.expectedAmount as number)
                .sub(expectedDAIFeeAmountToPay),
            );
            expect(BigNumber.from(DAIFeeBalance)).toEqual(
              BigNumber.from(initialDAIFeeBalance).add(expectedDAIFeeAmountToPay),
            );
          });
        });
    
        describe('prepareBatchPaymentTransaction', () => {
          it('should consider the version mapping', () => {
            expect(
              prepareBatchConversionPaymentTransaction(
                [
                  {
                    paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
                    request: {
                      ...DAIValidRequest,
                      extensions: {
                        [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT]: {
                          ...DAIValidRequest.extensions[
                            ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT
                          ],
                          version: '0.1.0',
                        },
                      },
                    } as any,
                  } as EnrichedRequest,
                  {
                    paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
                    request: {
                      ...FAUValidRequest,
                      extensions: {
                        [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT]: {
                          ...FAUValidRequest.extensions[
                            ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT
                          ],
                          version: '0.1.0',
                        },
                      },
                    } as any,
                  } as unknown as EnrichedRequest,
                ],
                options,
              ).to,
            ).toBe(batchConversionPaymentsArtifact.getAddress('private', '0.1.0'));
          });
        });
      });
    });
    
    window
    .ethereum);
    } else {
    // Connect to your own Ethereum node or 3rd party node provider
    provider = new providers.JsonRpcProvider(process.env.WEB3_PROVIDER_URL);
    }
    // getDefaultProvider() won't work because it doesn't include a Signer.

    createRequest()

    Description

    Create an unencrypted request

    Parameters

    Name
    Type
    Required?
    Description

    Returns

    Promise<>

    Types and Interfaces

    ICreateRequestParameters

    Name
    Type
    Required?
    Description

    ICreateRequestOptions

    Name
    Type
    Requried
    Description

    IRequestInfo

    Name
    Type
    Required?
    Description

    PaymentNetworkCreateParameters

    Name
    Type
    Required
    Description

    ICurrency

    Name
    Type
    Required?
    Description

    Types.RequestLogic.CURRENCY

    Name
    Value

    Types.Extension.PAYMENT_NETWORK_ID

    Name
    Value
    Description

    Escrow payments use the ERC20_FEE_PROXY_CONTRACT payment network.

    ICreationParameters

    PnAnyDeclarative.ICreationParameters

    Name
    Type
    Required
    Description

    PnAddressBased.ICreationParameters

    Name
    Type
    Required
    Description

    PnReferenceBased.ICreationParameters

    Name
    Type
    Required
    Description

    PnFeeReferenceBased.ICreationParameters

    Name
    Type
    Required
    Description

    PnAnyToAnyConversion.ICreationParameters

    Name
    Type
    Required
    Description

    PnAnyToErc20.ICreationParameters

    Name
    Type
    Required
    Description

    PnAnyToEth.ICreationParameters

    Identical to

    PnStreamReferenceBased.ICreationParameters

    Equal to OR

    IOriginalRequestCreationParameters

    Name
    Type
    Required
    Description

    ISubsequentRequestCreationParameters

    Name
    Type
    Required
    Description

    topics

    string[]

    List of hashes, usually one for each of the request stakeholders

    contentData

    Object

    Additional arbitrary request contents

    disablePaymentDetection

    boolean

    Disable payment detection

    disableEvents

    boolean

    Diable event callbacks

    payer

    Identity of the payer. Required if payee not set

    extensionsData

    any[]

    Raw extensionsData. Not recommended. Prefer contentData and paymentNetwork instead.

    timestamp

    number (Unix timestamp)

    Timestamp when request is created. User provided, so this is an agreement between payee and payer.

    nonce

    number

    Optional nonce to differentiate identical requests created at the same timestamp.

    ERC777

    'ERC777'

    Streamable fungible currency (USDCx, REQx, etc.)

    BITCOIN_ADDRESS_BASED

    'pn-bitcoin-address-based'

    Payee generates a new Bitcoin address. Use block explorer to detect all payments to that address.

    ERC20_ADDRESS_BASED

    'pn-erc20-address-based'

    Payee generates a new Ethereum address. Use block explorer to detect all payments to that address.

    ERC20_FEE_PROXY_CONTRACT

    'pn-erc20-fee-proxy-contract'

    Send ERC20 via smart contract with an optional fee.

    ERC20_PROXY_CONTRACT

    'pn-erc20-proxy-contract'

    Send ERC20 via smart contract

    ERC20_TRANSFERABLE_RECEIVABLE

    'pn-erc20-transferable-receivable'

    Mint a Request as an NFT. The holder receives the payment.

    ERC777_STREAM

    'pn-erc777-stream'

    Superfluid stream

    ETH_FEE_PROXY_CONTRACT

    'pn-eth-fee-proxy-contract'

    Send native token via smart contract with an optional fee.

    ETH_INPUT_DATA

    'pn-eth-input-data'

    Send native token with paymentReference in the call data.

    NATIVE_TOKEN

    'pn-native-token'

    Send native token via smart contract with an optional fee on NEAR.

    TESTNET_BITCOIN_ADDRESS_BASED

    'pn-testnet-bitcoin-address-based'

    Payee generates a new Bitcoin testnet address. Use block explorer to detect all payments to that address.

    payerDelegate

    Identity that can update the request on behalf of the payer

    salt

    string

    A random number with at least 8 bytes of randomness. It must be unique to each request

    parameters

    ICreateRequestParameters

    Parameters to create a request

    options

    ICreateRequestOptions

    Options to create a request

    requestInfo

    IRequestInfo

    Core request contents

    signer

    IIdentity

    Identity of the creator and signer of the request. Must be either the payee or payer.

    paymentNetwork

    PaymentNetworkCreateParameters

    skipRefresh

    boolean

    Disable the request refresh after creation. Warning: the balance will be null.

    currency

    string | ICurrency

    The currency in which the request is denominated. Not necessarily the currency in which the payment will occur.

    expectedAmount

    number | string

    The requested amount in machine-readable integer units.

    payee

    IIdentity

    id

    Types.Extension.PAYMENT_NETWORK_ID

    Payment network ID

    parameters

    ICreationParameters

    Payment network parameters. Contents depend on id

    type

    Types.RequestLogic.CURRENCY

    Currency type

    value

    string

    Depends on type.

    • ERC20 contract address '0x123'

    • Fiat symbol 'USD'

    • Native symbol 'ETH'

    network

    ChainName

    ETH

    'ETH'

    Native (ETH, XDAI, etc.)

    BTC

    'BTC'

    Bitcoin

    ISO4217

    'ISO4217'

    Fiat (USD, EUR, etc.)

    ERC20

    'ERC20'

    Non-native fungible currency (USDC, REQ, etc.)

    ANY_DECLARATIVE

    'pn-any-declarative'

    Payer declares payment sent. Payee declares payment received.

    ANY_TO_ERC20_PROXY

    'pn-any-to-erc20-proxy'

    Swap to ERC20 before sending to payee

    ANY_TO_ETH_PROXY

    'pn-any-to-eth-proxy'

    Swap to native token before sending to payee. Only works on EVM-compatible chains.

    ANY_TO_NATIVE_TOKEN

    'pn-any-to-native-token'

    paymentInfo

    any

    refundInfo

    any

    payeeDelegate

    IIdentity

    paymentAddress

    string

    The payment recipient address

    refundAddress

    string

    The refund recipient address

    salt

    string

    paymentNetworkName

    ChainName

    The chain name on which the payment will occur

    feeAddress

    string

    The address to which fees will be sent

    feeAmount

    string

    The fee amount in machine-readable integer units

    maxRateTimespan

    number

    The maximum acceptable time span between the payment and the conversion rate timestamp

    network

    ChainName

    The network of the tokens accepted for payments

    acceptedTokens

    string[]

    A list of token addresses accepted for payments and refunds

    network

    EvmChainName

    the network of the tokens accepted for payments

    expectedFlowRate

    string

    expectedStartDate

    string

    previousRequestId

    string

    originalRequestId

    string

    recurrenceNumber

    string

    Request
    PnAnyToAnyConversion.ICreationParameters
    IOriginalRequestCreationParameters
    ISubsequentRequestCreationParameters

    Payment method

    Identity of the payee. Required if payer not set

    The chain on which the currency exists

    Swap to native token before sending to payee. Only works on NEAR.

    Identity that can update the request on behalf of the payee

    https://codesandbox.io/p/sandbox/pay-a-request-dn7kcf?file=/app/page.tsx:71,1
    https://codesandbox.io/p/sandbox/create-a-request-shffng?file=/app/page.tsx:43,1
    https://codesandbox.io/p/sandbox/retrieve-a-users-requests-mqrjqy?file=/app/page.tsx:10,1
    IIdentity
    IIdentity
    https://github.com/RequestNetwork/quickstart-node-js/blob/main/src/payRequest.js
    https://github.com/RequestNetwork/quickstart-node-js/blob/main/src/createRequest.js
    https://github.com/RequestNetwork/quickstart-node-js/blob/main/src/retrieveRequest.js
    (async () => {
      const {
        RequestNetwork,
        Types,
        Utils,
      } = require("@requestnetwork/request-client.js");
      const {
        EthereumPrivateKeySignatureProvider,
      } = require("@requestnetwork/epk-signature");
      const { config } = require("dotenv");
      const { Wallet } = require("ethers");
    
      // Load environment variables from .env file
      config();
    
      const epkSignatureProvider = new EthereumPrivateKeySignatureProvider({
        method: Types.Signature.METHOD.ECDSA,
        privateKey: process.env.PAYEE_PRIVATE_KEY, // Must include 0x prefix
      });
    
      const requestClient = new RequestNetwork({
        nodeConnectionConfig: {
          baseURL: "https://sepolia.gateway.request.network/",
        },
        signatureProvider: epkSignatureProvider,
      });
    
      // In this example, the payee is also the payer and payment recipient.
      const payeeIdentity = new Wallet(process.env.PAYEE_PRIVATE_KEY).address;
      const payerIdentity = payeeIdentity;
      const paymentRecipient = payeeIdentity;
      const feeRecipient = "0x0000000000000000000000000000000000000000";
    
      const requestCreateParameters = {
        requestInfo: {
          currency: {
            type: Types.RequestLogic.CURRENCY.ERC20,
            value: "0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C",
            network: "sepolia",
          },
          expectedAmount: "1000000000000000000",
          payee: {
            type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
            value: payeeIdentity,
          },
          payer: {
            type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
            value: payerIdentity,
          },
          timestamp: Utils.getCurrentTimestampInSecond(),
        },
        paymentNetwork: {
          id: Types.Extension.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
          parameters: {
            paymentNetworkName: "sepolia",
            paymentAddress: paymentRecipient,
            feeAddress: feeRecipient,
            feeAmount: "0",
          },
        },
        contentData: {
          reason: "🍕",
          dueDate: "2023.06.16",
        },
        signer: {
          type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
          value: payeeIdentity,
        },
      };
    
      const request = await requestClient.createRequest(requestCreateParameters);
      const requestData = await request.waitForConfirmation();
      console.log(JSON.stringify(requestData));
    })();
    
    (async () => {
      const {
        RequestNetwork,
        Types,
        Utils,
      } = require("@requestnetwork/request-client.js");
      const {
        EthereumPrivateKeySignatureProvider,
      } = require("@requestnetwork/epk-signature");
      const {
        approveErc20,
        hasSufficientFunds,
        hasErc20Approval,
        payRequest,
      } = require("@requestnetwork/payment-processor");
      const { providers, Wallet } = require("ethers");
      const { config } = require("dotenv");
    
      // Load environment variables from .env file
      config();
    
      const epkSignatureProvider = new EthereumPrivateKeySignatureProvider({
        method: Types.Signature.METHOD.ECDSA,
        privateKey: process.env.PAYEE_PRIVATE_KEY, // Must include 0x prefix
      });
    
      const requestClient = new RequestNetwork({
        nodeConnectionConfig: {
          baseURL: "https://sepolia.gateway.request.network/",
        },
        signatureProvider: epkSignatureProvider,
      });
    
      const payeeIdentity = new Wallet(process.env.PAYEE_PRIVATE_KEY).address;
      const payerIdentity = payeeIdentity;
      const paymentRecipient = payeeIdentity;
      const feeRecipient = "0x0000000000000000000000000000000000000000";
    
      const requestCreateParameters = {
        requestInfo: {
          currency: {
            type: Types.RequestLogic.CURRENCY.ERC20,
            value: "0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C",
            network: "sepolia",
          },
          expectedAmount: "1000000000000000000",
          payee: {
            type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
            value: payeeIdentity,
          },
          payer: {
            type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
            value: payerIdentity,
          },
          timestamp: Utils.getCurrentTimestampInSecond(),
        },
        paymentNetwork: {
          id: Types.Extension.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT,
          parameters: {
            paymentNetworkName: "sepolia",
            paymentAddress: paymentRecipient,
            feeAddress: feeRecipient,
            feeAmount: "0",
          },
        },
        contentData: {
          reason: "🍕",
          dueDate: "2023.06.16",
        },
        signer: {
          type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
          value: payeeIdentity,
        },
      };
    
      const request = await requestClient.createRequest(requestCreateParameters);
      let requestData = await request.waitForConfirmation();
      console.log(`Created Request: ${JSON.stringify(requestData)}`);
    
      const provider = new providers.JsonRpcProvider(
        process.env.JSON_RPC_PROVIDER_URL,
      );
      const payerWallet = new Wallet(
        process.env.PAYER_PRIVATE_KEY, // Must have 0x prefix
        provider,
      );
    
      console.log(
        `Checking if payer ${payerWallet.address} has sufficient funds...`,
      );
      const _hasSufficientFunds = await hasSufficientFunds({
        request: requestData,
        address: payerWallet.address,
        providerOptions: { provider: provider },
      });
      console.log(`_hasSufficientFunds = ${_hasSufficientFunds}`);
      if (!_hasSufficientFunds) {
        throw new Error(`Insufficient Funds: ${payerWallet.address}`);
      }
    
      console.log(
        `Checking if payer ${payerWallet.address} has sufficient approval...`,
      );
      const _hasErc20Approval = await hasErc20Approval(
        requestData,
        payerWallet.address,
        provider,
      );
      console.log(`_hasErc20Approval = ${_hasErc20Approval}`);
      if (!_hasErc20Approval) {
        console.log(`Requesting approval...`);
        const approvalTx = await approveErc20(requestData, payerWallet);
        await approvalTx.wait(2);
        console.log(`Approval granted. ${approvalTx.hash}`);
      }
    
      const paymentTx = await payRequest(requestData, payerWallet);
      await paymentTx.wait(2);
      console.log(`Payment complete. ${paymentTx.hash}`);
    
      let startTime = Date.now();
      while (requestData.balance?.balance < requestData.expectedAmount) {
        requestData = await request.refresh();
        console.log(`current balance = ${requestData.balance?.balance}`);
        await new Promise((resolve) => setTimeout(resolve, 1000));
        // Check if 5 seconds have passed, and if so, break out of the loop
        if (Date.now() - startTime >= 5000) {
          console.log("Timeout: Exiting loop after 5 seconds.");
          break;
        }
      }
    })();
    
    (async () => {
      const {
        RequestNetwork,
        Types,
      } = require("@requestnetwork/request-client.js");
      const requestClient = new RequestNetwork({
        nodeConnectionConfig: {
          baseURL: "https://sepolia.gateway.request.network/",
        },
      });
    
      const identity = "0x519145B771a6e450461af89980e5C17Ff6Fd8A92";
      const requests = await requestClient.fromIdentity({
        type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
        value: identity,
      });
      const requestDatas = requests.map((request) => request.getData());
      console.log(JSON.stringify(requestDatas));
    })();
    

    Integration Tutorial

    In this page you will learn how to integrate Request Network API into your application

    We will be creating a simple node server integrating the Request Network API to create payments and track their status. We are going to use fastify as our server and use drizzle with SQLite to store our payment data. Additionally, we'll be creating a simple React web application to interact with the API and execute payments.

    View the entire codebase on Code Sandbox.

    Backend

    In this section we'll create your API that integrates Request Network's API to create and track payments. After we are done with it, we'll jump over and create a web app connecting to your API and put everything together!

    Setup

    As mentioned, we are using fastify and drizzle for this demo, you can of course choose whatever suits you best.

    Create a new project. In it create a folder called rn-test-backend inside and copy over this package.json file to rn-test-backend.

    The folder structure for the demo is going to be simple:

    Then run npm install and when that's done, run npm run db:push.

    Get your API key

    Before starting your integration, you need to sign up on our , which you can access via this .

    On our API portal dashboard, you can easily create an API key.

    Now copy over its value to your .env file

    Create your first payment

    Let's create two new endpoints, one for creating a payment on our API and the other to fetch all of the payments users have made on our API.

    Note: the amount our API receives is human readable, so just send over the amount in invoiceCurrency you wish, no BigNumbers needed!

    Let-s try it out!

    Call our /payments endpoint with the right data to create a payout and let's see what we get back.

    The response should look something like the following object ():

    Now you can check your database with npm run db:studio and assert that the payment is there.

    Setting up webhooks

    In order for your app to make use of our payment tracking easily and in real-time, we provide webhook support. You just provide the endpoint and the Request Network API does the rest.

    Let's create a new route for handling webhook calls.

    We'll go into more detail on how to get the RN_WEBHOOK_SECRET in the next subsection.

    Testing webhooks locally

    As you may know, it's impossible for our webhooks to call your locally running server. In order to test them, use a tool like . Install it and run ngrok http 3000 in your terminal. In a few moments, you should see something similar to the screenshot below and copy the URL.

    Next up, go back to the and add a new webhook. In the case above it's the URL from ngrok with the /webhooks appendix ().

    Next thing, copy over the signing secret and add it to your .env file, then restart the app.

    If you want to test it out, click the Send test event button and observe your server's logs. Your output should look something like the following:

    Testing webhooks live

    Once your application is deployed, you will need to add a new webhook via the just like we did above, but use your deployment URL's webhook route.

    Then copy over the secret to your deployment's variables and you can test your handlers just as we did above!

    Responding to payment confirmation events

    To make use of payment tracking, we need to map different event types to handlers. For demo purposes, let's create a new handler that will update the status of a payment in your API to confirmed when it's been confirmed by Request Network.

    This is it for the API, now to properly test this, we're going to build a simple frontend app that will interact with the newly created API!

    Frontend

    We cannot test out the entire flow without a user actually paying a request. For testing purposes, I will use a wallet. In order for you to properly test this, I advise using a wallet and giving yourself some test Sepolia ETH from a faucet like .

    If you really want to check out what happens to your funds, create two accounts in your wallet. We'll be using Request Network to move funds from one to another.

    Setup

    Well be using to create a simple React app. Move to the root directory in the created project and run npm create vite@latest rn-test-frontend -- --template react-ts in the terminal. Then move to the created directory rn-test-frontend, run npm install .

    NOTE: We are not going to be using any advanced patterns or libraries here, we'll try to keep it as simple as possible and let you build in your own way.

    Next up, let's scaffold our app. Create a folder called components, and then create two files CreatePayment.tsx and ViewPayments.tsx.

    Next up, let's modify our App.tsx file to display two tabs.

    The final result should look something like this:

    Connecting the user's wallet

    We'll be using to enable wallet connection. To do that we need to do a few things:

    1. Install wagmi and its dependencies npm install wagmi viem @tanstack/react-query --save

    2. Create a wagmi config at src/config/wagmi.ts

    1. Update main.tsx to include the new providers

    1. Create a new component at src/components/wallet-connect/index.tsx

    1. Render this component from our App component

    The final result should look something like this with the wallet connection working.

    Viewing payments

    Since we have created a few payments via cURL before, we can implement viewing of payments first. Let's create a .env file and add the following to it:

    Next up, let's modify the ViewPayments component.

    It should look something like this:

    Creating payments

    Let's update our CreatePayment component. It's going to do the following:

    1. The user inputs payment information - the payee address, amount, invoice currency and payment currency

    2. After submitting the form, we create a payment on the API, receive the response and use the transactions property to execute the payment with our connected wallet.

    3. Immediately after that succeeds, we update the payment status on the backend to in-progress

    The end result is a form that looks like the following:

    Trying everything out

    We recommend using two different Metamask accounts you own. That way you will be able to confirm that the funds were moved on your very own.

    NOTE: For this demo, we recommend inputting your second account for the Payee address value and use the same invoice and payment currencies.

    1. Let's create a payment from the client, moving 0.02 Sepolia ETH to our second account

    1. Create the payment and sign the transaction

    1. Navigate to the View payments tab , verify that the last payment is In progress and let's wait for the transaction to go through. You can patiently watch your server's logs to check when the webhook is called.

    1. In a few moments the payment's status should be set to Confirmed .

    This is it, you have succesfully built a basic application integrating our API to move actual test funds between two wallets.

    Happy building 🎉

    API portal
    link
    full API reference
    ngrok
    API portal
    https://34c701d1d7f9.ngrok-free.app/webhooks
    API portal
    Metamask
    Google
    Vite
    wagmi
    {
      "name": "request-api-demo",
      "version": "1.0.0",
      "description": "Request API demo",
      "main": "dist/index.js",
      "scripts": {
        "build": "tsc",
        "start": "node dist/index.js",
        "dev": "ts-node src/index.ts",
        "dev:watch": "ts-node-dev --respawn --transpile-only src/index.ts",
        "db:push": "drizzle-kit push",
        "db:generate": "drizzle-kit generate",
        "db:migrate": "drizzle-kit migrate",
        "db:studio": "drizzle-kit studio"
      },
      "keywords": [
        "fastify",
        "typescript",
        "node"
      ],
      "author": "",
      "license": "MIT",
      "devDependencies": {
        "@types/node": "^20.0.0",
        "ts-node": "^10.9.0",
        "ts-node-dev": "^2.0.0",
        "typescript": "^5.0.0"
      },
      "dependencies": {
        "@fastify/cors": "^11.1.0",
        "@types/better-sqlite3": "^7.6.13",
        "better-sqlite3": "^12.2.0",
        "dotenv": "^17.2.1",
        "drizzle-kit": "^0.31.4",
        "drizzle-orm": "^0.44.5",
        "fastify": "^5.5.0"
      }
    }
    
    // src/db/index.ts
    import Database from 'better-sqlite3';
    import { drizzle } from 'drizzle-orm/better-sqlite3';
    import * as schema from './schema';
    
    const sqlite = new Database('database.sqlite');
    export const db = drizzle(sqlite, { schema });
    // src/db/schema.ts
    import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
    
    export const payments = sqliteTable('payments', {
      id: integer('id').primaryKey({ autoIncrement: true }),
      requestId: text('request_id').notNull(),
      status: text('status').notNull(),
    });
    
    export type Payment = typeof payments.$inferSelect;
    // src/index.ts
    import 'dotenv/config';
    import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
    
    const fastify = Fastify({
      logger: true
    });
    
    fastify.get('/', async (request: FastifyRequest, reply: FastifyReply) => {
      return { message: 'Hello World!' };
    });
    
    const start = async () => {
      try {
        const port =  3000;
        const host = 'localhost';
        
        await fastify.register(require('@fastify/cors'), {
          origin: true, // change to your frontend URL in production
          methods: ['GET', 'POST', 'PATCH'],
        });
        await fastify.listen({ port, host });
        console.log(`Server listening on http://${host}:${port}`);
      } catch (err) {
        fastify.log.error(err);
        process.exit(1);
      }
    };
    
    start();
    
    // drizzle-config.ts
    import { defineConfig } from 'drizzle-kit';
    
    export default defineConfig({
      schema: './src/db/schema.ts',
      out: './drizzle',
      dialect: 'sqlite',
      dbCredentials: {
        url: './database.sqlite',
      },
    });
    // tsconfig.json
    {
      "compilerOptions": {
        "target": "ES2020",
        "module": "commonjs",
        "lib": ["ES2020"],
        "outDir": "./dist",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "resolveJsonModule": true,
        "declaration": true,
        "declarationMap": true,
        "sourceMap": true
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules", "dist"]
    }
    
    // .env
    RN_API_KEY=<insert-you-api-key>
    RN_API_URL=https://api.request.network/v2 
    // src/index.ts
    import 'dotenv/config';
    import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
    import { db } from './db';
    import { payments } from './db/schema';
    
    const fastify = Fastify({
      logger: true
    });
    
    fastify.get('/', async (request: FastifyRequest, reply: FastifyReply) => {
      return { message: 'Hello World!' };
    });
    
    interface PaymentBody {
      payee: string;
      amount: string;
      invoiceCurrency: string;
      paymentCurrency: string;
    }
    
    fastify.post('/payments', async (request: FastifyRequest<{ Body: PaymentBody }>, reply: FastifyReply) => {
      try {
        const { payee, amount, invoiceCurrency, paymentCurrency } = request.body;
    
        if (!payee || !amount || !invoiceCurrency || !paymentCurrency) {
          return reply.status(400).send({ 
            error: 'Missing required fields: payee, amount, invoiceCurrency, paymentCurrency' 
          });
        }
    
        const response = await fetch(`${process.env.RN_API_URL}/payouts`, {
          method: 'POST',
          headers: {
            'X-Api-Key': process.env.RN_API_KEY,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            payee,
            amount,
            invoiceCurrency,
            paymentCurrency
          })
        });
    
        if (!response.ok) {
          const errorText = await response.text();
          fastify.log.error(`Request Network API error: ${response.status} - ${errorText}`);
          return reply.status(response.status).send({ 
            error: 'Failed to create payment with Request Network API',
            details: errorText
          });
        }
    
        const rnApiResponse: any = await response.json();
        console.log('Request Network API response:', JSON.stringify(rnApiResponse, null, 2));
    
        const [savedPayment] = await db.insert(payments).values({
          requestId: rnApiResponse.requestId,
          status: 'pending'
        }).returning();
    
        console.log('Payment saved to database:', savedPayment);
    
        return {
          payment: savedPayment,
          calldata: {
            transactions: rnApiResponse.transactions,
            metadata: rnApiResponse.metadata
          }
        };
    
      } catch (error) {
        console.error('Error creating payment:', error);
        return reply.status(500).send({ 
          error: 'Internal server error',
          details: error instanceof Error ? error.message : 'Unknown error'
        });
      }
    });
    
    interface UpdatePaymentStatusBody {
      status: string;
    }
    // we will use this endpoint later on, don't think too much about it right now!
    fastify.patch('/payments/:id', async (request: FastifyRequest<{ 
      Params: { id: string };
      Body: UpdatePaymentStatusBody 
    }>, reply: FastifyReply) => {
      try {
        const { id } = request.params;
        const { status } = request.body;
    
        if (!status) {
          return reply.status(400).send({ 
            error: 'Status is required' 
          });
        }
    
        const updatedPayment = await db.update(payments)
          .set({ status })
          .where(eq(payments.id, parseInt(id)))
          .returning();
    
        if (!updatedPayment.length) {
          return reply.status(404).send({ 
            error: 'Payment not found' 
          });
        }
    
        console.log('Payment status updated:', updatedPayment[0]);
    
        return {
          payment: updatedPayment[0]
        };
    
      } catch (error) {
        console.error('Error updating payment status:', error);
        return reply.status(500).send({ 
          error: 'Internal server error',
          details: error instanceof Error ? error.message : 'Unknown error'
        });
      }
    });
    
    
    fastify.get('/payments', async (request: FastifyRequest, reply: FastifyReply) => {
      try {
        const allPayments = await db.select().from(payments);
        return { payments: allPayments };
      } catch (error) {
        console.error('Error fetching payments:', error);
        return reply.status(500).send({ 
          error: 'Failed to fetch payments',
          details: error instanceof Error ? error.message : 'Unknown error'
        });
      }
    });
    
    const start = async () => {
      try {
        const port =  3000;
        const host = 'localhost';
        
        await fastify.register(require('@fastify/cors'), {
          origin: true, // change to your frontend URL in production
          methods: ['GET', 'POST', 'PATCH'],
        });
        await fastify.listen({ port, host });
        console.log(`Server listening on http://${host}:${port}`);
      } catch (err) {
        fastify.log.error(err);
        process.exit(1);
      }
    };
    
    start();
    
    curl -X POST http://localhost:3000/payments \
      -H "Content-Type: application/json" \
      -d '{
        "payee": "<RECIPIENT_ADDRESS>",
        "amount": "0.2",
        "invoiceCurrency": "ETH-sepolia-sepolia",
        "paymentCurrency": "ETH-sepolia-sepolia"
      }'
    {
      "requestId": "011d9f76e07a678b8321ccfaa300efd4d80832652b8bbc07ea4069ca71006210b5",
      "paymentReference": "0xe23a6b02059c2b30",
      "transactions": [
        {
          "data": "0xb868980b00000000000000000000000029eab540117632a112ea29ba8be686a1b66467a700000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dead0000000000000000000000000000000000000000000000000000000000000008e23a6b02059c2b30000000000000000000000000000000000000000000000000",
          "to": "0xe11BF2fDA23bF0A98365e1A4c04A87C9339e8687",
          "value": {
            "type": "BigNumber",
            "hex": "0x02c68af0bb140000"
          }
        }
      ],
      "metadata": {
        "stepsRequired": 1,
        "needsApproval": false,
        "paymentTransactionIndex": 0
      }
    }
    // Add this to src/index.ts
    import crypto from "node:crypto";
    
    fastify.post('/webhooks', async (request: FastifyRequest, reply: FastifyReply) => {
      let webhookData: Record<string, unknown> = {};
    
      try {
        const body = request.body as Record<string, unknown>;
        webhookData = body;
        
        const signature = request.headers['x-request-network-signature'] as string;
        const webhookSecret = process.env.RN_WEBHOOK_SECRET;
    
        if (!webhookSecret) {
          fastify.log.error('RN_WEBHOOK_SECRET is not set');
          return reply.status(500).send({ error: 'Webhook secret not configured' });
        }
    
        const expectedSignature = crypto.createHmac('sha256', webhookSecret)
          .update(JSON.stringify(body))
          .digest('hex');
    
        if (signature !== expectedSignature) {
          fastify.log.error('Invalid webhook signature');
          return reply.status(401).send({ error: 'Invalid signature' });
        }
    
        const { requestId, event } = body;
    
        console.log(`Webhook received: ${event} for request ${requestId}`, {
          webhookData: body
        });
    
        // Log the event
        console.log(`Webhook event: ${event}`);
        console.log('Full webhook data:', JSON.stringify(body, null, 2));
    
        return reply.send({ code: 200, message: 'Webhook received' });
    
      } catch (error) {
        console.error('Webhook error:', {
          error,
          requestId: webhookData?.requestId,
          event: webhookData?.event,
        });
    
        return reply.status(500).send({ error: 'Internal server error' });
      }
    });
    // .env
    RN_API_KEY=<YOUR_API_KEY>
    RN_API_URL=https://api.request.network/v2
    RN_WEBHOOK_SECRET=<THE_SECRET_WE_JUST_CREATED>
    Webhook received: payment.confirmed for request req_test123456789abcdef {
      webhookData: {
        event: 'payment.confirmed',
        requestId: 'req_test123456789abcdef',
        requestID: 'req_test123456789abcdef',
        paymentReference: '0x1234567890abcdef1234567890abcdef12345678',
        explorer: 'https://scan.request.network/request/req_test123456789abcdef',
        amount: '100.0',
        totalAmountPaid: '100.0',
        expectedAmount: '100.0',
        timestamp: '2025-08-28T12:25:45.995Z',
        txHash: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
        network: 'ethereum',
        currency: 'USDC',
        paymentCurrency: 'USDC',
        isCryptoToFiat: false,
        subStatus: '',
        paymentProcessor: 'request-network',
        fees: [ [Object] ]
      }
    }
    Webhook event: payment.confirmed
    Full webhook data: {
      "event": "payment.confirmed",
      "requestId": "req_test123456789abcdef",
      "requestID": "req_test123456789abcdef",
      "paymentReference": "0x1234567890abcdef1234567890abcdef12345678",
      "explorer": "https://scan.request.network/request/req_test123456789abcdef",
      "amount": "100.0",
      "totalAmountPaid": "100.0",
      "expectedAmount": "100.0",
      "timestamp": "2025-08-28T12:25:45.995Z",
      "txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
      "network": "ethereum",
      "currency": "USDC",
      "paymentCurrency": "USDC",
      "isCryptoToFiat": false,
      "subStatus": "",
      "paymentProcessor": "request-network",
      "fees": [
        {
          "type": "network",
          "amount": "0.02",
          "currency": "ETH"
        }
      ]
    }
    // Update our handler in src/index.ts
    
    fastify.post('/webhooks', async (request: FastifyRequest, reply: FastifyReply) => {
      let webhookData: Record<string, unknown> = {};
    
      try {
        const body = request.body as Record<string, unknown>;
        webhookData = body;
        
        const signature = request.headers['x-request-network-signature'] as string;
        const webhookSecret = process.env.RN_WEBHOOK_SECRET;
    
        if (!webhookSecret) {
          fastify.log.error('RN_WEBHOOK_SECRET is not set');
          return reply.status(500).send({ error: 'Webhook secret not configured' });
        }
    
        const expectedSignature = crypto.createHmac('sha256', webhookSecret)
          .update(JSON.stringify(body))
          .digest('hex');
    
        if (signature !== expectedSignature) {
          fastify.log.error('Invalid webhook signature');
          return reply.status(401).send({ error: 'Invalid signature' });
        }
    
        const { requestId, event } = body;
    
        console.log(`Webhook received: ${event} for request ${requestId}`, {
          webhookData: body
        });
    
        // Log the event
        console.log(`Webhook event: ${event}`);
        console.log('Full webhook data:', JSON.stringify(body, null, 2));
        
        switch (event) {
          // handling the payment.confirmed event
          case "payment.confirmed":
            await db.update(payments)
              .set({ status: 'confirmed' })
              .where(eq(payments.requestId, requestId as string));
            break;
        }
    
        return reply.send({ code: 200, message: 'Webhook received' });
    
      } catch (error) {
        console.error('Webhook error:', {
          error,
          requestId: webhookData?.requestId,
          event: webhookData?.event,
        });
    
        return reply.status(500).send({ error: 'Internal server error' });
      }
    });
    // src/components/create-payment/index.tsx
    import React from 'react';
    
    const CreatePayment: React.FC = () => {
      return (
        <div>
          <h2>Create Payment</h2>
          <p>This will be a form to create new payments</p>
        </div>
      );
    };
    
    export default CreatePayment;
    // src/components/view-payments/index.tsx
    import React from 'react';
    
    const ViewPayments: React.FC = () => {
      return (
        <div>
          <h2>View Payments</h2>
          <p>This will show all payments from the database</p>
        </div>
      );
    };
    
    export default ViewPayments;
    // src/App.tsx
    import { useState } from 'react'
    import './App.css'
    import ViewPayments from './components/ViewPayments'
    import CreatePayment from './components/CreatePayment'
    
    type TabType = 'view' | 'create';
    
    function App() {
      const [activeTab, setActiveTab] = useState<TabType>('view');
    
      return (
        <div className="app">
          <h1>Request Network Demo</h1>
          
          <div className="tabs">
            <button 
              className={activeTab === 'view' ? 'tab-button active' : 'tab-button'}
              onClick={() => setActiveTab('view')}
            >
              View Payments
            </button>
            <button 
              className={activeTab === 'create' ? 'tab-button active' : 'tab-button'}
              onClick={() => setActiveTab('create')}
            >
              Create Payment
            </button>
          </div>
    
          <div className="tab-content">
            {activeTab === 'view' && <ViewPayments />}
            {activeTab === 'create' && <CreatePayment />}
          </div>
        </div>
      )
    }
    
    export default App
    
    // src/App.css
    #root {
      max-width: 1280px;
      margin: 0 auto;
      padding: 2rem;
      text-align: center;
    }
    
    .app {
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
    }
    
    .tabs {
      display: flex;
      gap: 10px;
      margin-bottom: 30px;
    }
    
    .tab-button {
      border-radius: 0px;
      padding: 12px 24px;
      border: none;
      background: none;
      cursor: pointer;
      font-size: 16px;
      border-bottom: 3px solid transparent;
      transition: all 0.2s;
    }
    
    .tab-button:hover {
      background-color: #f5f5f5;
    }
    
    .tab-button.active {
      border-bottom-color: #11c9a0;
      color: #11c9a0;
      font-weight: 600;
    }
    
    .tab-content {
      min-height: 400px;
    }
    
    h1 {
      text-align: center;
      margin-bottom: 40px;
    }
    
    // src/config/wagmi.ts
    import { createConfig, http } from 'wagmi'
    import {  sepolia } from 'wagmi/chains'
    import { injected } from 'wagmi/connectors'
    
    export const config = createConfig({
      chains: [sepolia],
      connectors: [
        injected(), 
      ],
      transports: {
        [sepolia.id]: http(),
      },
    })
    // src/main.tsx
    import React from 'react'
    import ReactDOM from 'react-dom/client'
    import { WagmiProvider } from 'wagmi'
    import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
    import { config } from './config/wagmi'
    import App from './App.tsx'
    import './index.css'
    
    const queryClient = new QueryClient()
    
    ReactDOM.createRoot(document.getElementById('root')!).render(
      <React.StrictMode>
        <WagmiProvider config={config}>
          <QueryClientProvider client={queryClient}>
            <App />
          </QueryClientProvider>
        </WagmiProvider>
      </React.StrictMode>,
    )
    
    // src/components/wallet-connect/index.tsx
    import { useAccount, useConnect, useDisconnect } from 'wagmi'
    import './styles.css';
    
    const WalletConnect: React.FC = () => {
      const { address, isConnected } = useAccount()
      const { connect, connectors } = useConnect()
      const { disconnect } = useDisconnect()
    
      if (isConnected) {
        return (
          <div className="wallet-status">
            <div className="wallet-info">
              <span>Connected: {address?.slice(0, 6)}...{address?.slice(-4)}</span>
              <button onClick={() => disconnect()} className="disconnect-btn">
                Disconnect
              </button>
            </div>
          </div>
        )
      }
    
      return (
        <div className="wallet-connect">
          <button 
            onClick={() => connect({ connector: connectors[0] })}
            className="connect-btn"
          >
            Connect Wallet
          </button>
        </div>
      )
    }
    
    export default WalletConnect
    // src/components/wallet-connect/styles.css
    .connect-btn {
      background: #646cff;
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
    }
    
    .connect-btn:hover {
      background: #5145d4;
    }
    
    .disconnect-btn {
      background: #dc2626;
      color: white;
      border: none;
      padding: 6px 12px;
      border-radius: 4px;
      cursor: pointer;
      font-size: 12px;
    }
    
    .disconnect-btn:hover {
      background: #b91c1c;
    }
    // src/App.tsx
    import { useState } from 'react'
    import './App.css'
    import ViewPayments from './components/view-payments'
    import CreatePayment from './components/create-payment'
    import WalletConnect from './components/wallet-connect';
    
    type TabType = 'view' | 'create';
    
    function App() {
      const [activeTab, setActiveTab] = useState<TabType>('view');
    
      return (
        <div className="app">
          <div className='header'>
            <h1>Request Network Demo</h1>
            <WalletConnect />
          </div>
          <div className="tabs">
            <button 
              className={activeTab === 'view' ? 'tab-button active' : 'tab-button'}
              onClick={() => setActiveTab('view')}
            >
              View Payments
            </button>
            <button 
              className={activeTab === 'create' ? 'tab-button active' : 'tab-button'}
              onClick={() => setActiveTab('create')}
            >
              Create Payment
            </button>
          </div>
    
          <div className="tab-content">
            {activeTab === 'view' && <ViewPayments />}
            {activeTab === 'create' && <CreatePayment />}
          </div>
        </div>
      )
    }
    
    export default App
    
    // src/App.css, add this class in
    
    .header {
      display: flex;
      justify-content: space-between;
      gap: 32px;
      align-items: center;
      margin-bottom: 40px;
    }
    // .env
    VITE_API_URL=http://localhost:3000
    // src/components/view-payments/index.tsx
    import React, { useState, useEffect } from 'react';
    import './styles.css';
    
    interface Payment {
      id: number;
      requestId: string;
      status: string;
    }
    
    const ViewPayments: React.FC = () => {
      const [payments, setPayments] = useState<Payment[]>([]);
      const [isLoading, setIsLoading] = useState(true);
    
      const fetchPayments = async () => {
        try {
          const response = await fetch(`${import.meta.env.VITE_API_URL}/payments`);
          if (response.ok) {
            const data = await response.json();
            setPayments(data.payments || []);
          } else {
            console.error('Failed to fetch payments');
          }
        } catch (error) {
          console.error('Error fetching payments:', error);
        } finally {
          setIsLoading(false);
        }
      };
    
      useEffect(() => {
        fetchPayments();
    
        const interval = setInterval(fetchPayments, 3000);
    
        return () => clearInterval(interval);
      }, []);
    
      const getStatusClass = (status: string) => {
        switch (status.toLowerCase()) {
          case 'pending':
            return 'status-pending';
          case 'in-progress':
            return 'status-in-progress';
          case 'confirmed':
            return 'status-confirmed';
          case 'failed':
            return 'status-failed';
          default:
            return 'status-pending';
        }
      };
    
      if (isLoading && payments.length === 0) {
        return (
          <div className="view-payments">
            <h2>View Payments</h2>
            <div className="loading">Loading payments...</div>
          </div>
        );
      }
    
      return (
        <div className="view-payments">
          <h2>View Payments</h2>
          <div className="payments-container">
            {payments.length === 0 ? (
              <div className="no-payments">No payments found</div>
            ) : (
              payments.map((payment) => (
                <div key={payment.id} className="payment-item">
                  <span className="payment-id">Payment ID: {payment.id}</span>
                  <span className={`status-pill ${getStatusClass(payment.status)}`}>
                    {payment.status}
                  </span>
                </div>
              ))
            )}
          </div>
        </div>
      );
    };
    
    export default ViewPayments;
    // src/components/view-payments/styles.css
    .view-payments {
      max-width: 600px;
      margin: 0 auto;
    }
    
    .payments-container {
      display: flex;
      flex-direction: column;
      gap: 12px;
      margin-top: 20px;
    }
    
    .payment-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 16px;
      border: 1px solid #4a4a4a;
      border-radius: 8px;
      background-color: #2a2a2a;
      transition: background-color 0.2s;
    }
    
    .payment-item:hover {
      background-color: #333333;
    }
    
    .payment-id {
      font-weight: 500;
      color: #e5e7eb;
    }
    
    .status-pill {
      padding: 6px 14px;
      border-radius: 20px;
      font-size: 12px;
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 0.5px;
      border: 1px solid transparent;
    }
    
    .status-pending {
      background-color: #451a03;
      color: #fcd34d;
      border-color: #92400e;
    }
    
    .status-in-progress {
      background-color: #1e3a8a;
      color: #93c5fd;
      border-color: #3b82f6;
    }
    
    .status-confirmed {
      background-color: #064e3b;
      color: #6ee7b7;
      border-color: #10b981;
    }
    
    .status-failed {
      background-color: #7f1d1d;
      color: #fca5a5;
      border-color: #ef4444;
    }
    
    .no-payments {
      text-align: center;
      color: #9ca3af;
      font-style: italic;
      padding: 40px 20px;
      background-color: #1f1f1f;
      border-radius: 8px;
      border: 1px solid #4a4a4a;
    }
    
    .loading {
      text-align: center;
      color: #9ca3af;
      padding: 20px;
      background-color: #1f1f1f;
      border-radius: 8px;
      border: 1px solid #4a4a4a;
    }
    // src/components/create-payment/index.tsx
    import React, { useState } from 'react';
    import { useSendTransaction, useAccount } from 'wagmi';
    import './styles.css';
    
    interface PaymentForm {
      payee: string;
      amount: string;
      invoiceCurrency: string;
      paymentCurrency: string;
    }
    
    const CreatePayment: React.FC = () => {
      const [formData, setFormData] = useState<PaymentForm>({
        payee: '',
        amount: '',
        invoiceCurrency: 'ETH-sepolia-sepolia',
        paymentCurrency: 'ETH-sepolia-sepolia'
      });
    
      const [isExecuting, setIsExecuting] = useState(false);
      const { sendTransactionAsync } = useSendTransaction();
      const { isConnected } = useAccount();
    
      const currencyOptions = [
        { value: 'ETH-sepolia-sepolia', label: 'ETH (Sepolia)' },
        { value: 'FAU-sepolia', label: 'FAU (Sepolia)' },
        { value: 'fUSDC-sepolia', label: 'fUSDC (Sepolia)' }
      ];
    
      const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
        const { name, value } = e.target;
        setFormData(prev => ({
          ...prev,
          [name]: value
        }));
      };
    
      const updatePaymentStatus = async (paymentId: number, status: string) => {
        try {
          const response = await fetch(`${import.meta.env.VITE_API_URL}/payments/${paymentId}`, {
            method: 'PATCH',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({ status }),
          });
    
          if (!response.ok) {
            console.error('Failed to update payment status');
          } else {
            console.log(`Payment ${paymentId} status updated to: ${status}`);
          }
        } catch (error) {
          console.error('Error updating payment status:', error);
        }
      };
    
      const executeTransactions = async (transactions: Array<{ to: string; data: string; value: { hex: string } }>, paymentId: number) => {
        if (!isConnected) {
          alert('Please connect your wallet first');
          return;
        }
    
        try {
          for (let i = 0; i < transactions.length; i++) {
            const tx = transactions[i];
            
            console.log(`Executing transaction ${i + 1}/${transactions.length}:`, tx);
            
            
            const txHash = await sendTransactionAsync({
              to: tx.to as `0x${string}`,
              data: tx.data as `0x${string}`,
              value: BigInt(tx.value.hex)
            });
    
            // As soon as we start sending transactions, update status to 'in-progress'
            await updatePaymentStatus(paymentId, 'in-progress');
    
            console.log(`Transaction ${i + 1} sent with hash:`, txHash);
          }
          
          alert('All transactions executed successfully!');
          
        } catch (error) {
          console.error('Transaction execution failed:', error);
          alert(`Transaction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
          await updatePaymentStatus(paymentId, 'failed');
          throw error;
        }
      };
    
      const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        
        setIsExecuting(true);
    
        try {
          const response = await fetch(`${import.meta.env.VITE_API_URL}/payments`, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify(formData),
          });
    
          if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || 'Failed to create payment');
          }
    
          const data = await response.json();
          console.log('Backend response:', data);
    
          if (data.calldata && data.calldata.transactions) {
            await executeTransactions(data.calldata.transactions, data.payment.id);
          } else {
            throw new Error('No transaction data received from backend');
          }
    
        } catch (error) {
          console.error('Error in payment flow:', error);
          alert(`Payment failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
        } finally {
          setIsExecuting(false);
        }
      };
    
      return (
        <div className="create-payment">
          <h2>Create Payment</h2>
          
          <form onSubmit={handleSubmit} className="payment-form">
            <div className="form-group">
              <label htmlFor="payee">Payee Address</label>
              <input
                type="text"
                id="payee"
                name="payee"
                value={formData.payee}
                onChange={handleInputChange}
                placeholder="0x..."
                required
              />
            </div>
    
            <div className="form-group">
              <label htmlFor="amount">Payment Amount</label>
              <input
                type="number"
                id="amount"
                name="amount"
                value={formData.amount}
                onChange={handleInputChange}
                placeholder="0.00"
                step="0.000001"
                min="0"
                required
              />
            </div>
    
            <div className="form-group">
              <label htmlFor="invoiceCurrency">Invoice Currency</label>
              <select
                id="invoiceCurrency"
                name="invoiceCurrency"
                value={formData.invoiceCurrency}
                onChange={handleInputChange}
                required
              >
                {currencyOptions.map(option => (
                  <option key={option.value} value={option.value}>
                    {option.label}
                  </option>
                ))}
              </select>
            </div>
    
            <div className="form-group">
              <label htmlFor="paymentCurrency">Payment Currency</label>
              <select
                id="paymentCurrency"
                name="paymentCurrency"
                value={formData.paymentCurrency}
                onChange={handleInputChange}
                required
              >
                {currencyOptions.map(option => (
                  <option key={option.value} value={option.value}>
                    {option.label}
                  </option>
                ))}
              </select>
            </div>
    
            <button 
              type="submit" 
              className="submit-btn"
              disabled={isExecuting || !isConnected}
            >
              {isExecuting ? 'Processing...' : 'Create & Execute Payment'}
            </button>
            
            {!isConnected && (
              <p style={{ color: '#dc2626', fontSize: '14px', marginTop: '10px' }}>
                Please connect your wallet to create payments
              </p>
            )}
          </form>
        </div>
      );
    };
    
    export default CreatePayment;
    // src/components/create-payment/index.tsx
    .create-payment {
      max-width: 500px;
      margin: 0 auto;
    }
    
    .payment-form {
      display: flex;
      flex-direction: column;
      gap: 20px;
    }
    
    .form-group {
      display: flex;
      flex-direction: column;
      gap: 6px;
    }
    
    .form-group label {
      font-weight: 600;
      color: #374151;
      font-size: 14px;
    }
    
    .form-group input,
    .form-group select {
      padding: 12px;
      border: 2px solid #e5e7eb;
      border-radius: 6px;
      font-size: 16px;
      transition: border-color 0.2s;
      background-color: inherit;
      color: inherit;
    }
    
    .form-group input:focus,
    .form-group select:focus {
      outline: none;
      border-color: #11c9a0;
      box-shadow: 0 0 0 3px rgba(100, 108, 255, 0.1);
    }
    
    .form-group select {
      cursor: pointer;
      appearance: none;
      background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
      background-position: right 12px center;
      background-repeat: no-repeat;
      background-size: 16px;
      padding-right: 40px;
    }
    
    .form-group select option {
      background-color: #1a1a1a;
      color: #ffffff;
      padding: 8px 12px;
    }
    
    .form-group select option:hover {
      background-color: #11c9a0;
    }
    
    .submit-btn {
      background: #11c9a0;
      color: white;
      border: none;
      padding: 14px 24px;
      border-radius: 6px;
      font-size: 16px;
      font-weight: 600;
      cursor: pointer;
      transition: background-color 0.2s;
      margin-top: 10px;
    }
    
    .submit-btn:hover {
      background: #5145d4;
    }
    
    .submit-btn:active {
      background: #4338ca;
    }
    
    input[type="number"]::-webkit-outer-spin-button,
    input[type="number"]::-webkit-inner-spin-button {
      -webkit-appearance: none;
      margin: 0;
    }
    
    input[type="number"] {
      appearance: textfield;
      -moz-appearance: textfield;
    }

    Create a new request

    post
    /v2/request

    Create a new payment request

    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Body
    payerstringOptional

    The wallet address of the payer

    payeestringOptional

    The wallet address of the payee. Required for all requests except crypto-to-fiat

    amountstringRequired

    The payable amount of the invoice, in human readable format

    invoiceCurrencystringRequired

    Invoice Currency ID, from the Request Network Token List e.g: USD

    paymentCurrencystringRequired

    Payment currency ID, from the Request Network Token List e.g: ETH-sepolia-sepolia

    isCryptoToFiatAvailablebooleanOptional

    Whether crypto-to-fiat payment is available for this request

    referencestring · min: 1 · max: 255Optional

    Merchant reference for receipt tracking and identification

    originalRequestIdstring · min: 1Optional

    ID of the original request for recurring payments

    originalRequestPaymentReferencestring · min: 1Optional

    Payment reference of the original request for recurring payments

    Get request status

    get
    /v2/request/{requestId}

    Get the status of a payment request

    Path parameters
    requestIdstringRequired

    The requestId for the request

    Example: 01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb
    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Responses
    200

    Request status retrieved successfully

    application/json
    404

    Request not found

    429

    Too Many Requests

    get
    /v2/request/{requestId}

    Update a recurring request

    patch
    /v2/request/{requestId}

    Update a recurring request

    Path parameters
    requestIdstringRequired

    The requestId for the request

    Example: 01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb
    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Responses
    200

    Recurrence updated successfully

    No content

    404

    Request not found

    429

    Too Many Requests

    patch
    /v2/request/{requestId}

    No content

    Get payment calldata

    get
    /v2/request/{requestId}/pay

    Get the calldata needed to pay a request. For same-chain payments, returns transaction calldata that can be directly executed. For crosschain payments (when chain and token parameters are provided and differ from the request's native chain), returns a payment intent that needs to be signed and processed through the crosschain bridge. For off-ramp payments, use the query parameters clientUserId and paymentDetailsId. Note: Crosschain requests with an expectedAmount less than 1 are rejected.

    Path parameters
    requestIdstringRequired

    The requestId of the request

    Example: 01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb
    Query parameters
    walletstringOptional

    The wallet address of the payer.

    Example: 0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7
    chainstring · enumOptional

    The source chain of the crosschain payment

    Possible values:
    tokenstring · enumOptional

    The source token of the crosschain payment

    Possible values:
    amountstringOptional

    The amount to pay, in human readable format

    clientUserIdstringOptional

    Optional client user ID for off-ramp payments

    Example: user-123
    paymentDetailsIdstringOptional

    Optional payment details ID for off-ramp payments

    Example: fa898aec-519c-46be-9b4c-e76ef4ff99d9
    feePercentagestringOptional

    Fee percentage to apply at payment time (e.g., '2.5' for 2.5%)

    Example: 0.02
    feeAddressstringOptional

    Address to receive the fee

    Example: 0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7

    Initiate a payment

    post
    /v2/payouts

    Initiate a payment without having to create a request first. Supports both one-time and recurring payments. For recurring payments, specify the recurrence object with start date, frequency, total executions, and payer address. The system will create a recurring payment schedule and return the necessary transactions for allowance approval and signature submission. Optionally includes customer information (firstName, lastName, email, address) and a merchant reference field for checkout widget implementations and receipt tracking.

    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Body
    payeestringRequired

    The wallet address of the payee

    amountstringRequired

    The payable amount of the invoice, in human readable format

    invoiceCurrencystringRequired

    Invoice Currency ID, from the Request Network Token List e.g: USD

    paymentCurrencystringRequired

    Payment currency ID, from the Request Network Token List e.g: ETH-sepolia-sepolia

    feePercentagestringOptional

    Fee percentage to apply at payment time (e.g., '2.5' for 2.5%)

    feeAddressstringOptional

    Address to receive the fee

    payerWalletstringOptional

    The wallet address of the payer, use to check if payer approval exists

    referencestring · min: 1 · max: 255Optional

    Merchant reference for receipt tracking and identification

    Get currencies

    get
    /v2/currencies

    Get a list of all available tokens, or filter by network, symbol, or id.

    Query parameters
    networkstringOptional

    The network of the token(s)

    Example: mainnet
    symbolstringOptional

    The symbol of the token

    Example: USDC
    firstOnlystringOptional

    Whether to return only the first token. can only be used when both network and symbol are provided.

    Example: true
    idstringOptional

    The Request Network id of the token

    Example: USDC-mainnet
    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Responses
    200

    List of tokens retrieved successfully

    application/json
    400

    Validation failed

    application/json
    404

    Token not found

    application/json
    429

    Too Many Requests

    get
    /v2/currencies

    Get conversion routes for a specific currency

    get
    /v1/currencies/{currencyId}/conversion-routes

    Get a list of currency objects (with all details) that can be converted to from the specified currency. Optionally filter by network using the 'network' query parameter.

    Path parameters
    currencyIdstringRequired
    Query parameters
    networkstringOptional

    The network of the token to filter by

    networksstringOptional

    A comma-separated list of networks to filter by (e.g., sepolia,mainnet,polygon)

    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Responses
    200

    Conversion routes retrieved successfully

    application/json
    404

    Currency not found

    application/json
    429

    Too Many Requests

    get
    /v1/currencies/{currencyId}/conversion-routes

    Get payment routes

    get
    /v2/request/{requestId}/routes

    Get available payment routes for a request. This endpoint analyzes the payer's wallet balance across supported chains and returns possible payment methods. Routes include direct same-chain payments and crosschain bridging options when the payer has sufficient balance on different chains than the request's native chain.

    Path parameters
    requestIdstringRequired

    The requestId of the request

    Example: 01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb
    Query parameters
    walletstringRequired

    The wallet address of the payer

    Example: 0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7
    amountstringOptional

    The amount to pay, in human readable format

    feePercentagestringOptional

    Fee percentage to apply at payment time (e.g., '2.5' for 2.5%)

    feeAddressstringOptional

    Address to receive the fee

    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Get payment calldata

    get
    /v2/request/{requestId}/pay

    Get the calldata needed to pay a request. For same-chain payments, returns transaction calldata that can be directly executed. For crosschain payments (when chain and token parameters are provided and differ from the request's native chain), returns a payment intent that needs to be signed and processed through the crosschain bridge. For off-ramp payments, use the query parameters clientUserId and paymentDetailsId. Note: Crosschain requests with an expectedAmount less than 1 are rejected.

    Path parameters
    requestIdstringRequired

    The requestId of the request

    Example: 01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb
    Query parameters
    walletstringOptional

    The wallet address of the payer.

    Example: 0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7
    chainstring · enumOptional

    The source chain of the crosschain payment

    Possible values:
    tokenstring · enumOptional

    The source token of the crosschain payment

    Possible values:
    amountstringOptional

    The amount to pay, in human readable format

    clientUserIdstringOptional

    Optional client user ID for off-ramp payments

    Example: user-123
    paymentDetailsIdstringOptional

    Optional payment details ID for off-ramp payments

    Example: fa898aec-519c-46be-9b4c-e76ef4ff99d9
    feePercentagestringOptional

    Fee percentage to apply at payment time (e.g., '2.5' for 2.5%)

    Example: 0.02
    feeAddressstringOptional

    Address to receive the fee

    Example: 0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7
    get
    /v2/request/{requestId}/pay

    Send a payment intent

    post
    /v2/request/payment-intents/{paymentIntentId}

    Send a payment intent

    Path parameters
    paymentIntentIdstringRequired

    The payment intent ID

    Example: 01JNZYZPK7B4YBPD44TM72NDNJ
    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Body

    Initiate a payment

    post
    /v2/payouts

    Initiate a payment without having to create a request first. Supports both one-time and recurring payments. For recurring payments, specify the recurrence object with start date, frequency, total executions, and payer address. The system will create a recurring payment schedule and return the necessary transactions for allowance approval and signature submission. Optionally includes customer information (firstName, lastName, email, address) and a merchant reference field for checkout widget implementations and receipt tracking.

    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Body
    payeestringRequired

    The wallet address of the payee

    amountstringRequired

    The payable amount of the invoice, in human readable format

    invoiceCurrencystringRequired

    Invoice Currency ID, from the Request Network Token List e.g: USD

    paymentCurrencystringRequired

    Payment currency ID, from the Request Network Token List e.g: ETH-sepolia-sepolia

    feePercentagestringOptional

    Fee percentage to apply at payment time (e.g., '2.5' for 2.5%)

    feeAddressstringOptional

    Address to receive the fee

    payerWalletstringOptional

    The wallet address of the payer, use to check if payer approval exists

    referencestring · min: 1 · max: 255Optional

    Merchant reference for receipt tracking and identification

    post
    /v2/payouts

    Submit a recurring payment signature

    post
    /v2/payouts/recurring/{id}

    Submit a signature for a recurring payment permit to activate the recurring payment schedule. This endpoint is called after creating a recurring payment and obtaining the permit data. The signature authorizes the recurring payment contract to execute payments on behalf of the payer according to the schedule. Once activated, payments will be executed automatically at the specified intervals.

    Path parameters
    idstringRequired

    The ID of the recurring payment

    Example: 01JXYJKCAHGFTDR15F2D072ESG
    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Body
    permitSignaturestringRequired

    The signature of the recurring payment permit.

    Get the status of a recurring payment

    get
    /v2/payouts/recurring/{id}

    Retrieve the current status and execution details of a recurring payment. Returns information about executed payments, remaining executions, next payment date, and overall status. This endpoint is useful for monitoring recurring payment progress and checking if payments are being executed as expected. Note: Customer information (PII) is not included in the response for security reasons.

    Path parameters
    idstringRequired

    The ID of the recurring payment

    Example: 01JXYJKCAHGFTDR15F2D072ESG
    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Responses
    200

    Recurring payment status retrieved successfully

    application/json
    404

    Recurring payment not found

    application/json
    429

    Too Many Requests

    get
    /v2/payouts/recurring/{id}

    Update a recurring payment

    patch
    /v2/payouts/recurring/{id}

    Update a recurring payment by cancelling it or unpausing it. When cancelling, optionally returns a transaction to decrease allowance. When unpausing, resumes execution of a paused recurring payment.

    Path parameters
    idstringRequired

    The ID of the recurring payment

    Example: 01JXYJKCAHGFTDR15F2D072ESG
    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Body
    actionstring · enumRequired

    The action to perform on the recurring payment

    Possible values:
    Responses
    200

    Recurring payment updated successfully

    application/json
    400

    Bad request

    application/json
    404

    Recurring payment not found

    429

    Too Many Requests

    patch
    /v2/payouts/recurring/{id}

    Pay multiple requests in one transaction

    post
    /v2/payouts/batch

    Pays multiple payment requests in one transaction by either creating new requests or using existing request IDs. All requests must be on the same network. Supports mixed ERC20, Native, and conversion requests.

    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Body
    requestIdsstring[]Optional

    The request IDs of the existing requests to be paid. Requests must be on the same network. Either requests or requestIds must be provided, but not both.

    payerstringOptional

    The wallet address of the payer, user to check if approval is needed or not.

    Create compliance data for a user

    post
    /v2/payer

    Checks compliance status and returns necessary URLs for completing compliance.

    Header parameters
    x-api-keystringRequired

    API key for authentication

    Body
    clientUserIdstring · min: 1Required

    Client User ID

    emailstring · emailRequired

    Email

    firstNamestring · min: 1Required

    First Name

    lastNamestring · min: 1Required

    Last Name

    beneficiaryTypestring · enumRequiredPossible values:
    companyNamestringOptional

    Company Name

    dateOfBirthstringRequired

    Date of birth in YYYY-MM-DD format

    Pattern: ^\d{4}-\d{2}-\d{2}$
    addressLine1string · min: 1Required

    Address Line 1

    addressLine2stringOptional

    Address Line 2

    citystring · min: 1Required

    City

    statestring · min: 1Required

    State

    postcodestring · min: 1Required

    Postcode

    countrystring · min: 2 · max: 2Required

    Country

    nationalitystring · min: 2 · max: 2Required

    Nationality

    phonestringRequired

    Phone in E.164 format

    Pattern: ^\+?[1-9]\d{1,14}$
    ssnstring · min: 1Required

    Social Security Number

    sourceOfFundsstringOptional

    Source of Funds

    businessActivitystringOptional

    Business Activity

    Get compliance status for a user

    get
    /v2/payer/{clientUserId}

    Retrieves the comprehensive compliance status for a specific user, including KYC and agreement status.

    Path parameters
    clientUserIdstringRequired

    The client user ID to check compliance status for

    Example: user-123
    Header parameters
    x-api-keystringRequired

    API key for authentication

    Responses
    200

    Compliance status retrieved successfully

    application/json
    401

    Unauthorized

    404

    User not found

    application/json
    429

    Too Many Requests

    get
    /v2/payer/{clientUserId}

    Update agreement status

    patch
    /v2/payer/{clientUserId}

    Update the agreement completion status for a user.

    Path parameters
    clientUserIdstringRequired

    The client user ID to update

    Example: user-123
    Header parameters
    x-api-keystringRequired

    API key for authentication

    Body
    agreementCompletedbooleanRequired
    Responses
    200

    Compliance status updated successfully

    application/json
    400

    Invalid request data

    application/json
    401

    Unauthorized

    404

    User not found

    application/json
    429

    Too Many Requests

    patch
    /v2/payer/{clientUserId}

    Create payment details

    post
    /v2/payer/{clientUserId}/payment-details

    Create payment details for a user

    Path parameters
    clientUserIdstringRequired

    The client user ID

    Example: user-123
    Header parameters
    x-api-keystringRequired

    API key for authentication

    Body
    bankNamestring · min: 1Required

    Name of the bank

    accountNamestring · min: 1Required

    Name of the account holder

    accountNumberstringOptional

    Bank account number

    routingNumberstringOptional

    Bank routing number (US)

    beneficiaryTypestring · enumRequired

    Type of beneficiary

    Possible values:
    currencystring · min: 3 · max: 3Required

    Three-letter currency code (ISO 4217)

    addressLine1string · min: 1Required

    Primary address line

    addressLine2stringOptional

    Secondary address line

    citystring · min: 1Required

    City name

    statestringOptional

    State or province code

    countrystring · min: 2 · max: 2Required

    Two-letter country code (ISO 3166-1 alpha-2)

    dateOfBirthstringRequired

    Date of birth in YYYY-MM-DD format

    Pattern: ^\d{4}-\d{2}-\d{2}$
    postalCodestring · min: 1Required

    Postal or ZIP code

    railsstring · enumOptional

    Payment rail type

    Default: localPossible values:
    sortCodestringOptional

    UK bank sort code

    ibanstringOptional

    International Bank Account Number

    swiftBicstringOptional

    SWIFT/BIC code

    documentNumberstringOptional

    Government-issued ID number

    documentTypestringOptional

    Type of government-issued ID (e.g., passport, driver's license)

    accountTypestring · enumOptional

    Type of bank account

    Possible values:
    ribNumberstringOptional

    French RIB number

    bsbNumberstringOptional

    Australian BSB number

    nccstringOptional

    New Zealand NCC number

    branchCodestringOptional

    Bank branch code

    bankCodestringOptional

    Bank code

    ifscstringOptional

    Indian Financial System Code

    Get payment details for a user

    get
    /v2/payer/{clientUserId}/payment-details

    Retrieves the registered bank account details for a user. Optionally filter by payment details ID.

    Path parameters
    clientUserIdstringRequired

    The client user ID to get payment details for

    Example: user-123
    Query parameters
    paymentDetailsIdstringOptional

    Optional ID of specific payment details to retrieve

    Example: fa898aec-519c-46be-9b4c-e76ef4ff99d9
    Header parameters
    x-api-keystringRequired

    API key for authentication

    Responses
    200

    Payment details retrieved successfully

    application/json
    401

    Unauthorized

    404

    User or payment details not found

    application/json
    429

    Too Many Requests

    get
    /v2/payer/{clientUserId}/payment-details

    Create a new request

    post
    /v2/request

    Create a new payment request

    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Body
    payerstringOptional

    The wallet address of the payer

    payeestringOptional

    The wallet address of the payee. Required for all requests except crypto-to-fiat

    amountstringRequired

    The payable amount of the invoice, in human readable format

    invoiceCurrencystringRequired

    Invoice Currency ID, from the Request Network Token List e.g: USD

    paymentCurrencystringRequired

    Payment currency ID, from the Request Network Token List e.g: ETH-sepolia-sepolia

    isCryptoToFiatAvailablebooleanOptional

    Whether crypto-to-fiat payment is available for this request

    referencestring · min: 1 · max: 255Optional

    Merchant reference for receipt tracking and identification

    originalRequestIdstring · min: 1Optional

    ID of the original request for recurring payments

    originalRequestPaymentReferencestring · min: 1Optional

    Payment reference of the original request for recurring payments

    Responses
    201

    Request created successfully

    application/json
    400

    Validation failed

    404

    Wallet not found

    429

    Too Many Requests

    post
    /v2/request

    Smart Contract Addresses

    For an explanation about what each smart contract does, see Smart Contracts Overview

    Payment

    Ethereum

    Contract Name
    Address

    Sepolia

    Contract Name
    Address

    Optimism

    Contract Name
    Address

    Arbitrum One

    Contract Name
    Address

    Base

    Contract Name
    Address

    zkSync Era

    Contract Name
    Address

    Gnosis

    Contract Name
    Address

    Polygon

    Contract Name
    Address

    BNB Smart Chain (BSC)

    Contract Name
    Address

    BNB Smart Chain (BSC) Testnet

    Contract Name
    Address

    Celo

    Contract Name
    Address

    Alfajores

    Contract Name
    Address

    Fantom

    Contract Name
    Address

    Tombchain

    Contract Name
    Address

    Core

    Contract Name
    Address

    Avalanche

    Contract Name
    Address

    Fuse

    Contract Name
    Address

    Moonbeam

    Contract Name
    Address

    Ronin

    Contract Name
    Address

    Mantle

    Contract Name
    Address

    NEAR

    Contract Name
    Address

    NEAR Testnet

    Contract Name
    Address

    Storage

    Gnosis

    Contract Name
    Address

    Sepolia

    Contract Name
    Address

    Ethereum (Deprecated)

    Contract Name
    Address

    REQ Token and Burn Mechanism

    Ethereum

    Contract Name
    Address

    Gnosis

    Contract Name
    Address
    Responses
    201

    Batch payment calldata retrieved successfully

    application/json
    400

    Requests must be on the same network

    application/json
    429

    Too Many Requests

    post
    /v2/payouts/batch
    Responses
    200

    Available payment routes

    application/json
    400

    Invalid or missing wallet address

    404

    Request not found

    429

    Too Many Requests

    get
    /v2/request/{requestId}/routes
    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Responses
    200

    Payment calldata retrieved successfully

    application/json
    Responseany of
    or
    400

    Validation failed

    application/json
    404

    Request not found

    429

    Too Many Requests

    Responses
    200

    Payment intent sent successfully

    No content

    404

    Payment intent data not found

    429

    Too Many Requests

    post
    /v2/request/payment-intents/{paymentIntentId}

    No content

    Responses
    201

    Request created and payment initiated successfully

    application/json
    404

    Wallet not found

    429

    Too Many Requests

    Responses
    201

    Recurring payment signature submitted successfully

    application/json
    400

    Bad request

    application/json
    404

    Recurring payment not found

    429

    Too Many Requests

    post
    /v2/payouts/recurring/{id}
    Responses
    200

    Compliance data retrieved successfully

    application/json
    400

    Invalid request data

    application/json
    401

    Unauthorized

    404

    Request not found

    application/json
    429

    Too Many Requests

    post
    /v2/payer
    Responses
    201

    Payment details created successfully

    application/json
    400

    Invalid request data

    application/json
    401

    Unauthorized

    404

    User not found

    application/json
    429

    Too Many Requests

    post
    /v2/payer/{clientUserId}/payment-details
    POST /v2/payouts/batch HTTP/1.1
    Host: api.request.network
    Content-Type: application/json
    Accept: */*
    Content-Length: 747
    
    {
      "requests": [
        {
          "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
          "amount": "2",
          "invoiceCurrency": "FAU-sepolia",
          "paymentCurrency": "FAU-sepolia"
        },
        {
          "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
          "amount": "2",
          "invoiceCurrency": "fUSDC-sepolia",
          "paymentCurrency": "fUSDC-sepolia"
        },
        {
          "payee": "0xb07D2398d2004378cad234DA0EF14f1c94A530e4",
          "amount": "10",
          "invoiceCurrency": "USD",
          "paymentCurrency": "FAU-sepolia"
        },
        {
          "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
          "amount": "0.00001",
          "invoiceCurrency": "ETH-sepolia-sepolia",
          "paymentCurrency": "ETH-sepolia-sepolia"
        },
        {
          "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
          "amount": "10",
          "invoiceCurrency": "USD",
          "paymentCurrency": "ETH-sepolia-sepolia"
        }
      ],
      "payer": "0x2e2E5C79F571ef1658d4C2d3684a1FE97DD30570"
    }
    {
      "ERC20ApprovalTransactions": [
        {
          "data": "0x095ea7b3...",
          "to": "0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C",
          "value": 0
        }
      ],
      "batchPaymentTransaction": {
        "data": "0x92cddb91...",
        "to": "0x67818703c92580c0e106e401F253E8A410A66f8B",
        "value": {
          "type": "BigNumber",
          "hex": "0x0d83b3d1afc58b"
        }
      }
    }
    GET /v2/request/{requestId}/routes?wallet=0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7 HTTP/1.1
    Host: api.request.network
    Accept: */*
    
    {
      "routes": [
        {
          "id": "REQUEST_NETWORK_PAYMENT",
          "fee": 0,
          "speed": "FAST",
          "price_impact": 0,
          "chain": "MAINNET",
          "token": "REQ"
        }
      ]
    }
    POST /v2/request/payment-intents/{paymentIntentId} HTTP/1.1
    Host: api.request.network
    Content-Type: application/json
    Accept: */*
    Content-Length: 154
    
    {
      "signedPaymentIntent": {
        "signature": "text",
        "nonce": "text",
        "deadline": "text"
      },
      "signedApprovalPermit": {
        "signature": "text",
        "nonce": "text",
        "deadline": "text"
      }
    }
    GET /v2/request/{requestId}/pay HTTP/1.1
    Host: api.request.network
    Accept: */*
    
    {
      "transactions": [
        {
          "data": "0xb868980b...00",
          "to": "0x11BF2fDA23bF0A98365e1A4e04A87C9339e8687",
          "value": {
            "type": "BigNumber",
            "hex": "0x038d7ea4c68000"
          }
        }
      ],
      "metadata": {
        "stepsRequired": 1,
        "needsApproval": false,
        "approvalTransactionIndex": null,
        "hasEnoughBalance": true,
        "hasEnoughGas": true
      }
    }
    {
      "requestId": "01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb",
      "paymentReference": "0xb3581f0b0f74cc61",
      "transactions": [
        {
          "data": "0xb868980b...00",
          "to": "0x11BF2fDA23bF0A98365e1A4e04A87C9339e8687",
          "value": {
            "type": "BigNumber",
            "hex": "0x038d7ea4c68000"
          }
        }
      ],
      "metadata": {
        "stepsRequired": 1,
        "needsApproval": false,
        "approvalTransactionIndex": null,
        "hasEnoughBalance": true,
        "hasEnoughGas": true
      }
    }
    POST /v2/payouts/recurring/{id} HTTP/1.1
    Host: api.request.network
    Content-Type: application/json
    Accept: */*
    Content-Length: 122
    
    {
      "permitSignature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b"
    }
    {
      "message": "Recurring payment activated successfully",
      "id": "01JXYJKCAHGFTDR15F2D072ESG",
      "status": "active"
    }
    {
      "processedPayments": 3,
      "totalPayments": 30,
      "lastPaymentDate": "2025-01-04T10:00:00.000Z",
      "nextPaymentDate": "2025-01-05T10:00:00.000Z",
      "status": "active",
      "requests": [
        {
          "paymentReference": "0xb3581f0b0f74cc61",
          "requestId": "01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb"
        }
      ],
      "payments": [
        {
          "id": "01JXYJKCAHGFTDR15F2D072ESG",
          "amount": "10",
          "timestamp": "2025-01-04T10:00:00.000Z",
          "txHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
        }
      ]
    }
    PATCH /v2/payouts/recurring/{id} HTTP/1.1
    Host: api.request.network
    Content-Type: application/json
    Accept: */*
    Content-Length: 19
    
    {
      "action": "cancel"
    }
    {
      "id": "01JXYJKCAHGFTDR15F2D072ESG",
      "status": "cancelled",
      "transactions": [
        {
          "to": "0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8",
          "data": "0x095ea7b30000000000000000000000000363dD3ccD4f187d7033c57354CA81f998451D590000000000000000000000000000000000000000000000000000000000000000",
          "value": "0x0"
        }
      ],
      "metadata": {
        "remainingPayments": 5,
        "remainingAmount": "5000000000000000000",
        "processedPayments": 3,
        "totalPayments": 8
      }
    }
    POST /v2/payouts HTTP/1.1
    Host: api.request.network
    Content-Type: application/json
    Accept: */*
    Content-Length: 213
    
    {
      "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
      "amount": "10",
      "invoiceCurrency": "USD",
      "paymentCurrency": "ETH-sepolia-sepolia",
      "feePercentage": "0.02",
      "feeAddress": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7"
    }
    GET /v2/payouts/recurring/{id} HTTP/1.1
    Host: api.request.network
    Accept: */*
    
    POST /v2/payer HTTP/1.1
    Host: api.request.network
    x-api-key: text
    Content-Type: application/json
    Accept: */*
    Content-Length: 312
    
    {
      "clientUserId": "user-123",
      "email": "[email protected]",
      "firstName": "John",
      "lastName": "Doe",
      "beneficiaryType": "individual",
      "dateOfBirth": "1985-12-12",
      "addressLine1": "123 Main Street",
      "city": "New York",
      "state": "NY",
      "postcode": "10001",
      "country": "US",
      "nationality": "US",
      "phone": "+12125551234",
      "ssn": "123-45-6789"
    }
    {
      "agreementUrl": "https://core-api.pay.so/v1/public/agreements?email=john.doe%40example.com",
      "kycUrl": "https://sumsub.com/idensic/l/#/sbx_VvK9E9P2A23xQPoA",
      "status": {
        "agreementStatus": "not_started",
        "kycStatus": "not_started"
      }
    }
    {
      "kycStatus": "completed",
      "agreementStatus": "completed",
      "isCompliant": true,
      "userId": "a25a4274-8f50-4579-b476-8f35b297d4ad"
    }
    {
      "success": true
    }
    POST /v2/payer/{clientUserId}/payment-details HTTP/1.1
    Host: api.request.network
    x-api-key: text
    Content-Type: application/json
    Accept: */*
    Content-Length: 316
    
    {
      "bankName": "Chase",
      "accountName": "Gordon's Chase Business Account",
      "accountNumber": "253009233489",
      "routingNumber": "026013356",
      "beneficiaryType": "business",
      "currency": "usd",
      "addressLine1": "24 Theatre St.",
      "city": "Paramount",
      "state": "CA",
      "postalCode": "90723",
      "country": "US",
      "dateOfBirth": "1985-12-12",
      "rails": "local"
    }
    {
      "payment_detail": {
        "id": "pd_123456",
        "clientUserId": "user-123",
        "bankName": "Chase",
        "accountName": "Gordon's Chase Business Account",
        "currency": "usd",
        "beneficiaryType": "business"
      }
    }
    {
      "paymentDetails": [
        {
          "id": "fa898aec-519c-46be-9b4c-e76ef4ff99d9",
          "userId": "a25a4274-8f50-4579-b476-8f35b297d4ad",
          "bankName": "Chase",
          "accountName": "Gordon's Chase Business Account",
          "beneficiaryType": "business",
          "accountNumber": "253009233489",
          "routingNumber": "026013356",
          "currency": "usd",
          "status": "approved",
          "rails": "local"
        }
      ]
    }
    GET /v2/payer/{clientUserId} HTTP/1.1
    Host: api.request.network
    x-api-key: text
    Accept: */*
    
    PATCH /v2/payer/{clientUserId} HTTP/1.1
    Host: api.request.network
    x-api-key: text
    Content-Type: application/json
    Accept: */*
    Content-Length: 27
    
    {
      "agreementCompleted": true
    }
    GET /v2/payer/{clientUserId}/payment-details HTTP/1.1
    Host: api.request.network
    x-api-key: text
    Accept: */*
    
    POST /v2/request HTTP/1.1
    Host: api.request.network
    Content-Type: application/json
    Accept: */*
    Content-Length: 224
    
    {
      "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
      "amount": "10",
      "invoiceCurrency": "USD",
      "paymentCurrency": "ETH-sepolia-sepolia",
      "isCryptoToFiatAvailable": false,
      "recurrence": {
        "startDate": "2030-01-01",
        "frequency": "DAILY"
      }
    }
    {
      "paymentReference": "0xb3581f0b0f74cc61",
      "requestId": "01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb"
    }
    [
      {
        "id": "USDC-mainnet",
        "name": "USD Coin",
        "symbol": "USDC",
        "decimals": 6,
        "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "network": "mainnet",
        "type": "ERC20",
        "hash": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "chainId": 1
      },
      {
        "id": "USDT-mainnet",
        "name": "Tether USD",
        "symbol": "USDT",
        "decimals": 6,
        "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
        "network": "mainnet",
        "type": "ERC20",
        "hash": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
        "chainId": 1
      }
    ]
    {
      "currencyId": "USD",
      "network": "mainnet",
      "conversionRoutes": [
        {
          "id": "USDT-mainnet",
          "name": "Tether USD",
          "symbol": "USDT",
          "decimals": 6,
          "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
          "network": "mainnet",
          "type": "ERC20",
          "hash": "0xdac17f958d2ee523a2206206994597c13d831ec7",
          "chainId": 1
        },
        {
          "id": "ETH-mainnet",
          "name": "Ether",
          "symbol": "ETH",
          "decimals": 18,
          "address": "0xf5af88e117747e87fc5929f2ff87221b1447652e",
          "network": "mainnet",
          "type": "ETH",
          "hash": "0xf5af88e117747e87fc5929f2ff87221b1447652e",
          "chainId": 1
        }
      ]
    }
    GET /v2/currencies HTTP/1.1
    Host: api.request.network
    Accept: */*
    
    GET /v1/currencies/{currencyId}/conversion-routes HTTP/1.1
    Host: api.request.network
    Accept: */*
    
    Responses
    201

    Request created successfully

    application/json
    400

    Validation failed

    404

    Wallet not found

    429

    Too Many Requests

    post
    /v2/request
    Header parameters
    x-api-keystringOptional

    API key for authentication (optional if using Client ID)

    x-client-idstringOptional

    Client ID for frontend authentication (optional if using API key)

    OriginstringOptional

    Origin header (required for Client ID auth, automatically set by browser)

    Responses
    200

    Payment calldata retrieved successfully

    application/json
    Responseany of
    or
    400

    Validation failed

    application/json
    404

    Request not found

    429

    Too Many Requests

    get
    /v2/request/{requestId}/pay
    Responses
    201

    Request created and payment initiated successfully

    application/json
    404

    Wallet not found

    429

    Too Many Requests

    post
    /v2/payouts

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0xe72Ecea44b6d8B2b3cf5171214D9730E86213cA2

    0x7Ebf48a26253810629C191b56C3212Fd0D211c26

    0x3b4837C9F4A606b71e61FD56Db6241781194df92

    0xcE80D17d38cfee8E5E6c682F7712bfb5A04Ae912

    0xD5933C74414ce80D9d7082cc89FBAdcfF4751fAF

    0xC5519f3fcECC8EC85caaF8836563dEe9a00080f9

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0xaD61121DAfAAe495095Cd466022b519Cb7503a4E

    0xc861aE0Cd70b73b0C8F1D62Fa669E6D1d7

    D7e0aB

    0x05e94CDdd14E0b18317AE21BAFAEC24156BdB7C9

    0xB5E53C3d145Cbaa61C7028736A1fF0bC6817A4c5

    0xf8cACE7EE4c03Eb4f225434B0709527938D365b4

    0x7c285b9F2dA5E2c10feA25C00Ce1aCB107F85475

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0x1550A8C4F4E5afC67Ea07e8ac590fdcAdB4bBfb1

    0x7Ebf48a26253810629C191b56C3212Fd0D211c26

    0x80D1EE67ffAf7047d3E6EbF7317cF0eAd63FFc78

    N/A

    0xf8cACE7EE4c03Eb4f225434B0709527938D365b4

    0x0818Ad7016138f0A40DFAe30F64a923c2A8F61bA

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0xA5186dec7dC1ec85B42A3cd2Dc8289e248530B07

    0x7Ebf48a26253810629C191b56C3212Fd0D211c26

    0x1d6B06C6f7adFd9314BD4C58a6D306261113a1D4

    N/A

    0x4D417AA04DBb207201a794E5B7381B3cde815281

    0x0818Ad7016138f0A40DFAe30F64a923c2A8F61bA

    0x78384dB9674109A3Edf9F2814eFF4B6fc25D816A

    0x8296D56321cf207925a7804E5A8E3F579838e6Ad

    0xEdfD8386d5DE52072B4Ad8dC69BBD0bB89f9A1fb

    0xFbBd0854048a8A75a8823c230e673F8331140483

    N/A

    0xAdc0001eA67Ab36D5321612c6b500572704fFF20

    0x3dF89c727eaDF67eeD7b4d09EC4F2b41f8Dec2ca

    N/A

    N/A

    N/A

    N/A

    N/A

    0x9Fd503e723e5EfcCde3183632b443fFF49E68715

    N/A

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0xf0f49873C50765239F6f9534Ba13c4fe16eD5f2E

    0x3E3B04e1bF170522a5c5DDE628C4d365c0342239

    0x3b4837C9F4A606b71e61FD56Db6241781194df92

    N/A

    0x4D417AA04DBb207201a794E5B7381B3cde815281

    0x05D782aD6D6556179A6387Ff1D2fA104FD5c515a

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0xf0f49873C50765239F6f9534Ba13c4fe16eD5f2E

    0x7Ebf48a26253810629C191b56C3212Fd0D211c26

    0x3b4837C9F4A606b71e61FD56Db6241781194df92

    0xd6C04C5d0e561D94B15622e770045776D4ce3739

    0x4D417AA04DBb207201a794E5B7381B3cde815281

    0x0818Ad7016138f0A40DFAe30F64a923c2A8F61bA

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0xbbd9c5D112343A4Aa2bc194245760CaeeaF118Be

    0x7Ebf48a26253810629C191b56C3212Fd0D211c26

    0x1d6B06C6f7adFd9314BD4C58a6D306261113a1D4

    N/A

    0x4D417AA04DBb207201a794E5B7381B3cde815281

    0x0818Ad7016138f0A40DFAe30F64a923c2A8F61bA

    N/A

    0xf0f49873C50765239F6f9534Ba13c4fe16eD5f2E

    N/A

    0x1d6B06C6f7adFd9314BD4C58a6D306261113a1D4

    N/A

    N/A

    0xEEc4790306C43DC00cebbE4D0c36Fadf8634B533

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0xf0f49873C50765239F6f9534Ba13c4fe16eD5f2E

    0x7Ebf48a26253810629C191b56C3212Fd0D211c26

    0x1d6B06C6f7adFd9314BD4C58a6D306261113a1D4

    N/A

    0x8d996a0591a0F9eB65301592C88303e07Ec481db

    0x0818Ad7016138f0A40DFAe30F64a923c2A8F61bA

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0xf0f49873C50765239F6f9534Ba13c4fe16eD5f2E

    0x7Ebf48a26253810629C191b56C3212Fd0D211c26

    0x1d6B06C6f7adFd9314BD4C58a6D306261113a1D4

    N/A

    N/A

    0x0818Ad7016138f0A40DFAe30F64a923c2A8F61bA

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0xA5186dec7dC1ec85B42A3cd2Dc8289e248530B07

    0x7Ebf48a26253810629C191b56C3212Fd0D211c26

    0x1d6B06C6f7adFd9314BD4C58a6D306261113a1D4

    N/A

    0x4D417AA04DBb207201a794E5B7381B3cde815281

    0x0818Ad7016138f0A40DFAe30F64a923c2A8F61bA

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    0x1550A8C4F4E5afC67Ea07e8ac590fdcAdB4bBfb1

    0x7Ebf48a26253810629C191b56C3212Fd0D211c26

    0x80D1EE67ffAf7047d3E6EbF7317cF0eAd63FFc78

    N/A

    N/A

    N/A

    0x626e6E3dac82205EA5FfB526092F4DCe525E46a9

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    0xf8cACE7EE4c03Eb4f225434B0709527938D365b4

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    N/A

    ERC20FeeProxy

    0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C

    ERC20Proxy

    0x5f821c20947ff9be22e823edc5b3c709b33121b3

    EthereumFeeProxy

    0xfCFBcfc4f5A421089e3Df45455F7f4985FE2D6a8

    EthereumProxy

    0x322F0037d272E980984F89E94Aae43BD0FC065E6

    ERC20EscrowToPay

    0x7DfD5955a1Ed6Bf74ccF8e24FF53E0a9A7e9F477

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0x399F5EE127ce7432E4921a61b8CF52b0af52cbfE

    ERC20Proxy

    0x88Ecc15fDC2985A7926171B938BB2Cd808A5ba40

    EthereumFeeProxy

    0xe11BF2fDA23bF0A98365e1A4c04A87C9339e8687

    EthereumProxy

    0x171Ee0881407d4c0C11eA1a2FB7D5b4cdED71e6e

    ERC20EscrowToPay

    0x26d4912fA5aC84F185843E19eeEdcc47f4Cc9F1a

    BatchConversionPayments

    0x67818703c92580c0e106e401F253E8A410A66f8B

    ERC20FeeProxy

    0x399F5EE127ce7432E4921a61b8CF52b0af52cbfE

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xe11BF2fDA23bF0A98365e1A4c04A87C9339e8687

    EthereumProxy

    0x171Ee0881407d4c0C11eA1a2FB7D5b4cdED71e6e

    ERC20EscrowToPay

    0x26d4912fA5aC84F185843E19eeEdcc47f4Cc9F1a

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xfCFBcfc4f5A421089e3Df45455F7f4985FE2D6a8

    EthereumProxy

    0x322F0037d272E980984F89E94Aae43BD0FC065E6

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0x1892196E80C4c17ea5100Da765Ab48c1fE2Fb814

    ERC20Proxy

    0xc31323ea7513799e1e112Dc15a05d5b600Cc357e

    EthereumFeeProxy

    0xd9C3889eB8DA6ce449bfFE3cd194d08A436e96f2

    EthereumProxy

    0x090D3583e3f5953e2CC758b146f4Ae11f8224ad7

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    0x1aF3f22685AcdDc788B3730ed415912d8f654420

    ERC20FeeProxy

    0x6e28Cc56C2E64c9250f39Cb134686C87dB196532

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xE9A708db0D30409e39810C44cA240fd15cdA9b1a

    EthereumProxy

    N/A

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    0x0C41700ee1B363DB2ebC1a985f65cAf6eC4b1023

    ERC20FeeProxy

    0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xfCFBcfc4f5A421089e3Df45455F7f4985FE2D6a8

    EthereumProxy

    0x322F0037d272E980984F89E94Aae43BD0FC065E6

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xfCFBcfc4f5A421089e3Df45455F7f4985FE2D6a8

    EthereumProxy

    0x322F0037d272E980984F89E94Aae43BD0FC065E6

    ERC20EscrowToPay

    0x937Db37ffb67083242fbC6AdD472146bF10E01ec

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xfCFBcfc4f5A421089e3Df45455F7f4985FE2D6a8

    EthereumProxy

    0x322F0037d272E980984F89E94Aae43BD0FC065E6

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9

    ERC20Proxy

    N/A

    EthereumFeeProxy

    N/A

    EthereumProxy

    N/A

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    N/A

    ERC20FeeProxy

    0x2171a0dc12a9E5b1659feF2BB20E54c84Fa7dB0C

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xfCFBcfc4f5A421089e3Df45455F7f4985FE2D6a8

    EthereumProxy

    0x322F0037d272E980984F89E94Aae43BD0FC065E6

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0x612cF8a29A9c8965a5fE512b7463165861c07EAa

    ERC20Proxy

    N/A

    EthereumFeeProxy

    N/A

    EthereumProxy

    N/A

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    N/A

    ERC20FeeProxy

    0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xfCFBcfc4f5A421089e3Df45455F7f4985FE2D6a8

    EthereumProxy

    0x322F0037d272E980984F89E94Aae43BD0FC065E6

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0x399F5EE127ce7432E4921a61b8CF52b0af52cbfE

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xe11BF2fDA23bF0A98365e1A4c04A87C9339e8687

    EthereumProxy

    N/A

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    N/A

    ERC20FeeProxy

    0x399F5EE127ce7432E4921a61b8CF52b0af52cbfE

    ERC20Proxy

    0x88Ecc15fDC2985A7926171B938BB2Cd808A5ba40

    EthereumFeeProxy

    0xe11BF2fDA23bF0A98365e1A4c04A87C9339e8687

    EthereumProxy

    0x171Ee0881407d4c0C11eA1a2FB7D5b4cdED71e6e

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    0x02561967c48e87cfB079763F3BEf6424A5A166A7

    ERC20FeeProxy

    0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xfCFBcfc4f5A421089e3Df45455F7f4985FE2D6a8

    EthereumProxy

    0x322F0037d272E980984F89E94Aae43BD0FC065E6

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0xee07ef5B414955188d2A9fF50bdCE784A49031Fc

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xfCFBcfc4f5A421089e3Df45455F7f4985FE2D6a8

    EthereumProxy

    0x322F0037d272E980984F89E94Aae43BD0FC065E6

    ERC20EscrowToPay

    0x4BA012eae4d64da79Bd6bcdBa366803fCe701A4C

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0x399F5EE127ce7432E4921a61b8CF52b0af52cbfE

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xe11BF2fDA23bF0A98365e1A4c04A87C9339e8687

    EthereumProxy

    0x171Ee0881407d4c0C11eA1a2FB7D5b4cdED71e6e

    ERC20EscrowToPay

    0x26d4912fA5aC84F185843E19eeEdcc47f4Cc9F1a

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0xAe23992483FeDA6E718a808Ce824f6864F13B64B

    ERC20Proxy

    N/A

    EthereumFeeProxy

    0xe9cbD1Aa5496628F4302426693Ad63006C56959F

    EthereumProxy

    0x171Ee0881407d4c0C11eA1a2FB7D5b4cdED71e6e

    ERC20EscrowToPay

    0x26d4912fA5aC84F185843E19eeEdcc47f4Cc9F1a

    BatchConversionPayments

    0x3cF63891928B8CeebB81C95426600a18cd59C03f

    ERC20FeeProxy

    0x399F5EE127ce7432E4921a61b8CF52b0af52cbfE

    ERC20Proxy

    0x88Ecc15fDC2985A7926171B938BB2Cd808A5ba40

    EthereumFeeProxy

    0xe11BF2fDA23bF0A98365e1A4c04A87C9339e8687

    EthereumProxy

    0x171Ee0881407d4c0C11eA1a2FB7D5b4cdED71e6e

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    N/A

    ERC20FeeProxy

    pay.reqnetwork.near

    ERC20Proxy

    N/A

    EthereumFeeProxy

    N/A

    EthereumProxy

    N/A

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    N/A

    ERC20FeeProxy

    pay.reqnetwork.testnet

    ERC20Proxy

    N/A

    EthereumFeeProxy

    N/A

    EthereumProxy

    N/A

    ERC20EscrowToPay

    N/A

    BatchConversionPayments

    N/A

    RequestOpenHashSubmitter

    0x268C146Afb4790902Ee26A6D2d3aff968623Ec80

    RequestHashStorage

    0x2256938E8225a998C498bf86B43c1768EE14b90B

    RequestOpenHashSubmitter

    0x899ddc13d5DBc252916ba2D70928518f3C836ba1

    RequestHashStorage

    0xd6c085A4D14e9e171f4aF58F7F48bd81173f167E

    RequestOpenHashSubmitter

    0xa9cEaA10c12dcB33BAbC2D779e37732311504652

    RequestHashStorage

    0x24a66afda3666fb0202f439708ece45c8121a9bb

    DaiBasedREQBurner

    0x2CFa65DcB34311293c6a52F1D7BEB8f4E31E5117

    RequestToken

    0x8f8221afbb33998d8584a2b05749ba73c37a938a

    Burner (Deprecated)

    0x7b3C4D90e8AF6030d66c07F8F815f9505E379d6F

    lockForREQBurn

    0x2171a0dc12a9E5b1659feF2BB20E54c84Fa7dB0C

    POST /v2/request HTTP/1.1
    Host: api.request.network
    Content-Type: application/json
    Accept: */*
    Content-Length: 224
    
    {
      "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
      "amount": "10",
      "invoiceCurrency": "USD",
      "paymentCurrency": "ETH-sepolia-sepolia",
      "isCryptoToFiatAvailable": false,
      "recurrence": {
        "startDate": "2030-01-01",
        "frequency": "DAILY"
      }
    }
    {
      "paymentReference": "0xb3581f0b0f74cc61",
      "requestId": "01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb"
    }
    {
      "hasBeenPaid": true,
      "requestId": "01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb",
      "isListening": false,
      "txHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
    }
    GET /v2/request/{requestId}/pay HTTP/1.1
    Host: api.request.network
    Accept: */*
    
    {
      "transactions": [
        {
          "data": "0xb868980b...00",
          "to": "0x11BF2fDA23bF0A98365e1A4e04A87C9339e8687",
          "value": {
            "type": "BigNumber",
            "hex": "0x038d7ea4c68000"
          }
        }
      ],
      "metadata": {
        "stepsRequired": 1,
        "needsApproval": false,
        "approvalTransactionIndex": null,
        "hasEnoughBalance": true,
        "hasEnoughGas": true
      }
    }
    POST /v2/payouts HTTP/1.1
    Host: api.request.network
    Content-Type: application/json
    Accept: */*
    Content-Length: 213
    
    {
      "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
      "amount": "10",
      "invoiceCurrency": "USD",
      "paymentCurrency": "ETH-sepolia-sepolia",
      "feePercentage": "0.02",
      "feeAddress": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7"
    }
    GET /v2/request/{requestId} HTTP/1.1
    Host: api.request.network
    Accept: */*
    
    PATCH /v2/request/{requestId} HTTP/1.1
    Host: api.request.network
    Accept: */*
    
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    ERC20SwapToPay
    ERC20ConversionProxy
    ETHConversionProxy
    ERC20SwapToConversion
    ERC20TransferableReceivable
    SingleRequestProxyFactory
    ChainlinkConversionPath
    {
      "requestId": "01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb",
      "paymentReference": "0xb3581f0b0f74cc61",
      "transactions": [
        {
          "data": "0xb868980b...00",
          "to": "0x11BF2fDA23bF0A98365e1A4e04A87C9339e8687",
          "value": {
            "type": "BigNumber",
            "hex": "0x038d7ea4c68000"
          }
        }
      ],
      "metadata": {
        "stepsRequired": 1,
        "needsApproval": false,
        "approvalTransactionIndex": null,
        "hasEnoughBalance": true,
        "hasEnoughGas": true
      }
    }