Skip to content
This repository has been archived by the owner on Mar 24, 2023. It is now read-only.

Latest commit

 

History

History
96 lines (73 loc) · 5.33 KB

addtional-feature.md

File metadata and controls

96 lines (73 loc) · 5.33 KB

Additional Feature

Instant Finality

Ethereum requires a transaction to be on-chain (meaning the transaction is included in the latest block) before returning a transaction receipt with final status to users, so they know whether the transaction is a success or not.

Godwoken provides a faster way to confirm transactions. Once a transaction is verified in the mem-pool, the instant transaction receipt will be generated immediately. This feature is called Instant Finality.

If you want to build a low-latency user experience for on-chain interactions in your dApp, you could turn on Instant Finality feature by using the RPC with an additional path or query parameters:

# http
https://example_web3_rpc_url?instant-finality-hack=true
https://example_web3_rpc_url/instant-finality-hack

# websocket
ws://example_web3_rpc_url/ws?instant-finality-hack=true

Note: Environments like Hardhat will swallow the HTTP URL's query parameter, so you might want to use the /instant-finality-hack path to overcome that.

Also notice that under instant-finality-hack mode, there might be some compatibility issues with Ethereum toolchain like ether.js. If you care more about compatibility, please use the bare RPC URL https://example_web3_rpc_url, which is considered to be the most compatible with Ethereum.

Gasless Transaction

The gas fee is preventing new users step into the web3 world. Users must learn to get the native token(CKB or ETH) before playing a blockchain game or exchanging tokens with a DEX. The gasless feature can provide a way for developers to sponsor transaction fees for users to give them a smooth experience.

The gas feature is based on the ERC-4337 solution but way simpler. To use a such feature, users sign and send a special gasless transaction to call a specific smart contract named Entrypoint, then Entrypoint will call another smart contract named Paymaster deployed by developers to check if they are willing to pay the gas fee for this transaction. The special gasless transaction must satisfy the requirements:

  • tx.data must be the call data of calling Entrypoint's handleOp(UserOperation calldata op) function, which contains the target contract and the paymaster address.
  • Must set tx.gasPrice to 0
  • The tx.to must be set to the Entrypoint contract.
struct UserOperation {
    address callContract;           # address of the target contract
    bytes callData;                 # call data of the target contract
    uint256 callGasLimit;           # gas used to execute the call
    uint256 verificationGasLimit;   # gas used to verification
    uint256 maxFeePerGas;           # gas price
    uint256 maxPriorityFeePerGas;   # must equals to maxFeePerGas, reserved for EIP-1559
    bytes paymasterAndData;         # pay master address and extra data
}

More specs can be found here

Example: How to use gasless transaction for your dapp

Let's say we have the entrypoint contract at 0x9a11f47c0729fc56d9c44c059987d40703249569 and as a game developer, we want to pay the gas fee for some whitelist users, so we wrote a paymaster contract just like this one and deployed it at 0x6b019795aa36dd19eb4a4d76f3b9a40239b7c19f.

dapp frontend using ethers.js

      // define UserOp
      const userOp: UserOperationStruct = {
          callContract: realGameContract.address,
          callData: realGameContractCallData,
          callGasLimit: gasToExecuteRealGameContractCallData,
          verificationGasLimit: gasToVerifyPaymaster,
          maxFeePerGas: gasPrice,
          maxPriorityFeePerGas: gasPrice,
          paymasterAndData: "0x6b019795aa36dd19eb4a4d76f3b9a40239b7c19f" 
      }
      
      // 1. construct and send gasless transaction
      const abiCoder = new ethers.utils.AbiCoder();
      const userOp = abiCoder.encode(["tuple(address callContract, bytes callData, uint256 callGasLimit, uint256 verificationGasLimit, uint256 maxFeePerGas, uint256 maxPriorityFeePerGas, bytes paymasterAndData) UserOperation"], [userOp]);
      // first 4 bytes of keccak hash of handleOp((address,bytes,uint256,uint256,uint256,uint256,bytes))
      const fnSelector = "fb4350d8";
      // gasless payload = ENTRYPOINT_HANDLE_OP_SELECTOR + abiEncode(UserOperation)
      const payload = "0x" + fnSelector + userOp.slice(2);

      const gaslessTx = {
        from: whitelistUser.address,
        to: '0x9a11f47c0729fc56d9c44c059987d40703249569',
        data: payload,
        gasPrice: 0,
        gasLimit: 1000000,
        value: 0,
      }

      const signer = new ethers.Wallet("private key");
      const tx = await signer.sendTransaction(gaslessTx);
      await tx.wait();


      // 2. or just use ethers contract factory
      {
        // Send tx with a valid user.
        const EntryPoint = await ethers.getContractFactory("EntryPoint");
        const entrypoint = await EntryPoint.attach('0x9a11f47c0729fc56d9c44c059987d40703249569');
        const tx = await entryPoint.connect(whitelistUser).handleOp(userOp, {gasLimit: 100000, gasPrice: 0});
        await tx.wait();
      }