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 https://github.com/RequestNetwork/quickstart-node.js

Create a request

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

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
});

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.

const { RequestNetwork } = require("@requestnetwork/request-client.js")

const requestClient = new RequestNetwork({
  nodeConnectionConfig: { 
    baseURL: "https://sepolia.gateway.request.network/",
  },
  signatureProvider: epkSignatureProvider,
});

Prepare the Request creation parameters:

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,
  },
};

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.

const request = await requestClient.createRequest(requestCreateParameters);
const confirmedRequestData = await request.waitForConfirmation();

Altogether it looks like this:

https://github.com/RequestNetwork/quickstart-node-js/blob/main/src/createRequest.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));
})();

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:

const { RequestNetwork, Types } = require("@requestnetwork/request-client.js");

const requestClient = new RequestNetwork({
  nodeConnectionConfig: { 
    baseURL: "https://sepolia.gateway.request.network/",
  }
});

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

const request = await requestClient.fromRequestId(
  '019830e9ec0439e53ec41fc627fd1d0293ec4bc61c2a647673ec5aaaa0e6338855',
);
const requestData = request.getData();

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

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,
);

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

const { hasSufficientFunds } = require("@requestnetwork/payment-processor);

const _hasSufficientFunds = await hasSufficientFunds(
  requestData,
  payerAddress,
  {
    provider: provider,
  },
);

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.

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);
}

Finally, pay the request using payRequest()

const { payRequest } = require("@requestnetwork/payment-processor");

const paymentTx = await payRequest(requestData, signer);
await paymentTx.wait(2);

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.

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));
}

Altogether it looks like this:

https://github.com/RequestNetwork/quickstart-node-js/blob/main/src/payRequest.js
(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;
    }
  }
})();

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:

const { RequestNetwork, Types } = require("@requestnetwork/request-client.js");
const requestClient = new RequestNetwork({
  nodeConnectionConfig: { 
    baseURL: "https://sepolia.gateway.request.network/",
  },
});

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.

const identityAddress = "0x519145B771a6e450461af89980e5C17Ff6Fd8A92";
const requests = await requestClient.fromIdentity({
  type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
  value: identityAddress,
});
const requestDatas = requests.map((request) => request.getData());

Altogether it looks like this:

https://github.com/RequestNetwork/quickstart-node-js/blob/main/src/retrieveRequest.js
(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));
})();

Last updated

Was this helpful?

OSZAR »