In order to make token transfers more efficient and user-friendly Credit tokens created in our API implement the EIP-2612 specification.

The EIP-2612 allows for token holders to approve transfers on their behalf by creating an off-chain signed permit. A third party will use this signed permit to execute the transfer in an on-chain transaction. Token holder do not need to spend gas or interact with the blockchain. This is particularly useful for onboarding new users who may not have Ether to pay for gas.

OPENFORMAT provides two endpoint to simplify this process, making it more straightforward.

In order to execute a Credit token transfer you need to follow this steps**:**

  1. Obtain Permit Data: Calling the Request Permit Data endpoint.
  2. Sign the Permit Data: The token holder signs the permit data hash using their wallet.
  3. Create Transfer Transaction: Calling the Transfer Credit Token endpoint with the permit signature.
  4. Sign and Broadcast the Transaction: The unsigned transaction needs to be signed with the RECEIVER ‘s wallet and then you can broadcast it using the Execute Transaction or Execute Transaction and Wait endpoints.

The following code presents a scenario where a Credit token is transferred from a User account(token holder) to your account(receiver).

Javascript - ethers v6
import axios from "axios";
import { ethers } from 'ethers';

// Transfer Credit tokens from a token holder account to a receiver account
async function CreditTransfer() {
  // Define the base URL for interacting with the OPENFORMAT API
  const OPENFORMAT_API_BASE_URL = "https://api.openformat.tech";

  // Specify the API key obtained from OPENFORMAT to authenticate requests
  const OPENFORMAT_API_KEY = "";

  // Setup the API client with headers for content type and authorization using the API key
  const apiClient = axios.create({
    baseURL: OPENFORMAT_API_BASE_URL,
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": OPENFORMAT_API_KEY,
    },
  });

  
  // Initialize a blockchain provider to interact with the Arbitrum Sepolia test network
  const blockchainProvider = new ethers.JsonRpcProvider(
      "https://sepolia-rollup.arbitrum.io/rpc"
    );
    
  // This is your account address that you used to register the app on OPENFORMAT
  const RECEIVER_ADDRESS = "";
  // Your Ethereum private key associated with your account
  const RECEIVER_PRIVATE_KEY = "";
  // Wallet instance with your private key and provider, it will be used to sign transactions
  const receiverWallet = new ethers.Wallet(
    RECEIVER_PRIVATE_KEY,
    blockchainProvider
  );

  // Data of the token holder.
  // In a real case scenario we will only have the holder address. We will pass to the holder the data they need
  // to sign, they will sign it with their wallet and we will get back the signature.
  // Ethereum address of the user that holds the tokens.
  // This account does not need to have any Ether because it will not pay for gas.
  const HOLDER_ADDRESS = "";
  // Private key of user that holds the tokens
  const HOLDER_PRIVATE_KEY = "";
  // Wallet for the token holder, this will be used to sign the permit data
  const holderWallet = new ethers.Wallet(
    HOLDER_PRIVATE_KEY,
    blockchainProvider
  );

  // Other data needed to create a transfer permit:
  const CHAIN_ID = "arbitrum-sepolia"; // Identifier for the chain we are using.
  const TOKEN_ID = ""; // Ethereum address of the Credit token

  const permitData = {
      chain: CHAIN_ID,
      token_id: TOKEN_ID,
      holder_address: HOLDER_ADDRESS,
      receiver_address: RECEIVER_ADDRESS,
      amount: 1,
  };

  // Obtain the permit data that the token holder needs to sign
  let permitResponse = await apiClient
    .post("/v1/credit/permit-data", permitData)
    .then((res) => res.data)
    .catch((err) => console.error("Error requesting permit data:", err));

  // Deadline for the transfer, this is set by OPENFORMAT one hour in the future
  // After this deadline permit data becomes invalid
  const deadline = permitResponse.permitData.deadline;
  // This is the data that has to be signed by the token holder
  const hashToSign = permitResponse.permitData.hashToSign

  // The token holder signs the permit with their wallet
  const signedPermit = holderWallet.signingKey.sign(hashToSign);

  // Transfer data needs to match the used to request the permit
  const transferData = {
    chain: CHAIN_ID,
    token_id: TOKEN_ID,
    holder_address: HOLDER_ADDRESS,
    receiver_address: RECEIVER_ADDRESS,
    amount: 1,
    deadline: Number(deadline),
    signedPermit: {
      r: signedPermit.r,
      s: signedPermit.s,
      v: signedPermit.v,
    }
  };

  // Obtain an unsigned transaction that will execute the token transfer
  let transactionResponse = await apiClient.post("/v1/credit/transfer", transferData)
    .then((res) => res.data)
    .catch((err) => console.log(err))

  // Sign the transaction using the wallet of the receiver(you)
  const signedTx = await receiverWallet.signTransaction(
    transactionResponse.unsignedTransaction
  );

  const executeData = { 
    chain: CHAIN_ID,
    signed_transaction: signedTx 
  };

  // Execute the signed transaction through the OPENFORMAT API and wait for the receipt
  transactionResponse = await apiClient
    .post("/v1/transactions/execute-and-wait", executeData)
    .then((res) => res.data)
    .catch((err) =>
      console.error("Error executing transaction:", err)
    );

  // Log the transaction hash for the executed transaction, linking to the block explorer for the blockchain network
  console.log(
    `Transaction executed, hash: https://sepolia.arbiscan.io/tx/${transactionResponse.transactionHash}`
  );
}

// Execute the CreditTransfer function and catch any unhandled errors
CreditTransfer().catch((err) =>
  console.error("Unhandled error:", err)
);