Giter Site home page Giter Site logo

eth-permit's Introduction

eth-permit

This package simplifies the process of signing permit messages for Ethereum tokens.

What is permit?

Permit is a technique for metatransaction token transfers. Using permit can allow a contract to use a user's tokens without the user first needing to first to send an approve() transaction.

Permit variations

Permit was first introduced in the Multi-Collateral Dai token contract.

The permit technique is being standardized as part of ERC-2612. This standard (which has already been implemented in projects like Uniswap V2) is slightly different than the implementation used by Dai. Therefore, this library provides functions for signing both types of messages.

Usage

Install the package eth-permit using npm or yarn.

Dai-style permits

import { signDaiPermit } from 'eth-permit';

// Sign message using injected provider (ie Metamask).
// You can replace window.ethereum with any other web3 provider.
const result = await signDaiPermit(window.ethereum, tokenAddress, senderAddress, spender);

await token.methods.permit(senderAddress, spender, result.nonce, result.expiry, true, result.v, result.r, result.s).send({
  from: senderAddress,
});

ERC2612-style permits

import { signERC2612Permit } from 'eth-permit';

const value = web3.utils.toWei('1', 'ether');

// Sign message using injected provider (ie Metamask).
// You can replace window.ethereum with any other web3 provider.
const result = await signERC2612Permit(window.ethereum, tokenAddress, senderAddress, spender, value);

await token.methods.permit(senderAddress, spender, value, result.deadline, result.v, result.r, result.s).send({
  from: senderAddress,
});

Ethers Wallet support

The library now supports Ethers.js Wallet signers:

import { signERC2612Permit } from 'eth-permit';

const value = web3.utils.toWei('1', 'ether');

const wallet = new ethers.Wallet(privateKey, new ethers.providers.JsonRpcProvider(rpcUrl));
const senderAddress = await wallet.getAddress();

const result = await signERC2612Permit(wallet, tokenAddress, senderAddress, spender, value);

await token.methods.permit(senderAddress, spender, value, result.deadline, result.v, result.r, result.s).send({
  from: senderAddress,
});

Special consideration when running on test networks

There are setups with dev test networks that fork from the mainnet. While this type of setup has a lot of benefits, it can make some of the interactions difficult. Take, for instance, the DAI deployment on the mainnet. Best practices for utilizing signatures is to include a DOMAIN_SEPARATOR that includes the chainId. When DAI was deployed on the mainnet, part of the DOMAIN_SEPARATOR set the chainId to 1. If you are interacting with that contract on your fork you need to generate a signature with the chainId value set to 1 and then send the transaction with a provider connected to your test netowrk which may have a chainId of 31337 in the case of hardhat.

If all the information (such as nonce and expiry) is not provided to the signDaiPermit or signERC2512Permit functions then queries are made to determine information with the forked chainId so you would need the provider to have the forked chainId. However, a provider that has the mainnet chainId is required to sign the message. Therefor, all information should be passed to the functions and not left to defaults.

import { signDaiPermit } from 'eth-permit';

const max_int = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';

const value = web3.utils.toWei('1', 'ether');

const wallet = new ethers.Wallet(privateKey, new ethers.providers.JsonRpcProvider(rpcUrl));
const senderAddress = await wallet.getAddress();

// find the correct nonce to use with a query to the test network
const nonce = await token.methods.nonces(senderAddress).send({
  from: senderAddress,
});

// create a wallet that will use a mainnet chainId for its provider but does not connect to anything
// it will use the ethers.js _signTypedData to create the signature and not a wallet provider
let mainnetWallet = new ethers.Wallet(privateKey, ethers.getDefaultProvider());

let domain = {
        "name": "Dai Stablecoin",
        "version": "1",
        "chainId": 1,
        "verifyingContract": tokenAddress
    }

const result = await signDaiPermit(mainnetWallet, domain, senderAddress, spender, max_int, nonce);

await token.methods.permit(senderAddress, spender, result.nonce, result.expiry, true, result.v, result.r, result.s).send({
  from: senderAddress,
});

eth-permit's People

Contributors

dmihal avatar lumyo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

eth-permit's Issues

Bug when signing permits on a Ledger wallet

I noticed that there is a bug in splitSignatureToRSV function when attempting to sign permits via a Ledger wallet and then trying to pass the resulting signature to an on-chain Ethereum contract method that consumes it. This is due to how Ledger produces vrs signatures with a canonical v value of {0,1}, while Ethereum's ecrecover accepts a non-standard v value of {27,28}. More on that here and here.

Metamask handles this internally when signing a permit directly through it, since it uses ecsign that normalizes the v value for Ethereum. But the problem is when signing a permit internally on a Ledger and then relay the resulting signature to Metamask, the v value is still {0,1}.

This could be resolved by replacing splitSignatureToRSV by ether's splitSignature, since it handles normalizing the resulting v value to Ethereum (i.e., 0 -> 27, 1 -> 28). Plus, it includes handling other special types signatures that are also not handled in splitSignatureToRSV's implementation (i.e., EIP-2098 signatures).

incorrect permit signature for version other than "1"

The generated permit is incorrect when EIP712 is instantiated with any version other than "1" because the value is hardcoded in the gerDomain method

const domain: Domain = { name, version: '1', chainId, verifyingContract: tokenAddress };

For example, the current version of the USDC token on Ethereum uses version "2"

    function initializeV2(string calldata newName) external {
        // solhint-disable-next-line reason-string
        require(initialized && _initializedVersion == 0);
        name = newName;
        DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(newName, "2");
        _initializedVersion = 1;
    }

Therefore, using the signERC2612Permit does not work for USDC, i.e. called permit() reverts with EIP2612: invalid signature

eth permit won't work at all in nodejs

Hi, eth permit and get following error

D:\@@MRT@@\driver\node_modules\eth-permit\dist\rpc.js:55
        _provider.send(payload, callback).catch((error) => {
                                         ^

TypeError: Cannot read properties of undefined (reading 'catch')
    at D:\@@MRT@@\driver\node_modules\eth-permit\dist\rpc.js:55:42
    at new Promise (<anonymous>)
    at Object.exports.send (D:\@@MRT@@\driver\node_modules\eth-permit\dist\rpc.js:25:46)
    at Object.exports.call (D:\@@MRT@@\driver\node_modules\eth-permit\dist\rpc.js:109:48)
    at D:\@@MRT@@\driver\node_modules\eth-permit\dist\eth-permit.js:94:38
    at Generator.next (<anonymous>)
    at D:\@@MRT@@\driver\node_modules\eth-permit\dist\eth-permit.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (D:\@@MRT@@\driver\node_modules\eth-permit\dist\eth-permit.js:4:12)
    at exports.signERC2612Permit (D:\@@MRT@@\driver\node_modules\eth-permit\dist\eth-permit.js:88:100)
    at create_transaction (D:\@@MRT@@\driver\scripts\Alice-2st-approve.js:143:26)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async main (D:\@@MRT@@\driver\scripts\Alice-2st-approve.js:228:3)

Calling the external functions with nonce = 0 does not have desired effect

The edge case where setting nonce=0 currently does not override the function to query the contract for the nonce.

This is actually an extremely important edge case as it is the first nonce that you must use for DAI. There is no way that I can see to work around this limitation to interact with a pre-fork deployment of the DAI contract on a test chain fork like what you get with hardhat.

Error Trust wallet Chain ID mismatch

The library works great on all wallets except the trust wallet, it's even strange that I tested the trust on Android and on iPhone, but it only works on android, the error lies in the fact that when trying to call a subscription on the trust wallet, the vallet gives the error Chain ID mismatch, although the wallet is changed to the right one before the permit, via RPC the request is wallet_switchEthereumChain, I would like to know if anyone has the same error, and this is a problem with the library or the wallet itself

photo_2024-03-19 08 46 47
photo_2024-03-19 08 49 11

How can we check whether a token supports permit?

I'm developing a dapp with a smart contract that can receive ERC20 tokens. What's the best way for a dapp (using ethers or web3) to check whether a token has the permit function, so that execution can fall back to using approve if not?

I had just tried signERC2612Permit with WETH on Rinkeby and it succeeded to create signature values, however when the smart contract tries to call permit the transaction is reverted without a reason. I then checked WETH and found that that WETH doesn't have permit.

Should signERC2612Permit check for permit before returning signature values, throwing an error if it doesn't exist?

Add support for local signer

Hey, this looks like a really useful library as getting permit txs right can be confusing!

It seems to me that the library is designed to be used on the client-side in conjunction with a remote signer. However sometimes it is useful to sign transactions from the backend, using e.g. https://github.com/MetaMask/eth-sig-util. It would be useful if there was a function within eth-permit which constructed the typedData and returned it without signing, allowing the calling code to decide how it should be signed.

I think this could be achieved by exporting the createTypedERC2612Data function as public, and similar for the Dai version. What do you think?

How do you supply a deadline & nonce?

I'm using this and it works great. However, what is the default amount that it sets the deadline to?

How can I set my own custom time for the deadline?

Also, when testing on test nets, the nonce always is 0x0, even after signing multiple messages.

When generating the signature, the "v" value is returning 0

Hello, I'm receiving 0 for the "v" value of the signature object. Hence, when passing this to the contract, the transaction is reverting with the msg error:
“execution reverted: ECDSA: invalid signature ‘v’ value”.

What could be causing this issue?

Some specs to generate the signature;

Using Metamask + Hardware Ledger

Error: processing response error

ethers version: 5.5.1
nodejs version: 16
Error: processing response error (body="{\"jsonrpc\":\"2.0\",\"id\":49,\"error\":{\"code\":-32601,\"message\":\"the method eth_signTypedData_v4 does not exist/is not available\"}}", error={"code":-32601}, requestBody="{\"method\":\"eth_signTypedData_v4\",\"params\":[\"0xBaA081282D82602d79EF300Ba29EC3b2466D4723\",\"{\\\"types\\\":{\\\"EIP712Domain\\\":[{\\\"name\\\":\\\"name\\\",\\\"type\\\":\\\"string\\\"},{\\\"name\\\":\\\"version\\\",\\\"type\\\":\\\"string\\\"},{\\\"name\\\":\\\"chainId\\\",\\\"type\\\":\\\"uint256\\\"},{\\\"name\\\":\\\"verifyingContract\\\",\\\"type\\\":\\\"address\\\"}],\\\"Permit\\\":[{\\\"name\\\":\\\"holder\\\",\\\"type\\\":\\\"address\\\"},{\\\"name\\\":\\\"spender\\\",\\\"type\\\":\\\"address\\\"},{\\\"name\\\":\\\"nonce\\\",\\\"type\\\":\\\"uint256\\\"},{\\\"name\\\":\\\"expiry\\\",\\\"type\\\":\\\"uint256\\\"},{\\\"name\\\":\\\"allowed\\\",\\\"type\\\":\\\"bool\\\"}]},\\\"primaryType\\\":\\\"Permit\\\",\\\"domain\\\":{\\\"name\\\":\\\"Dai Stablecoin\\\",\\\"version\\\":\\\"1\\\",\\\"chainId\\\":\\\"0x1\\\",\\\"verifyingContract\\\":\\\"0x6b175474e89094c44da98b954eedeac495271d0f\\\"},\\\"message\\\":{\\\"holder\\\":\\\"0xBaA081282D82602d79EF300Ba29EC3b2466D4723\\\",\\\"spender\\\":\\\"0x5049bE34eE05627aD0C500f11B25d23c02F530b1\\\",\\\"nonce\\\":\\\"0x0000000000000000000000000000000000000000000000000000000000000000\\\",\\\"expiry\\\":\\\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\\\",\\\"allowed\\\":true}}\"],\"id\":49,\"jsonrpc\":\"2.0\"}", requestMethod="POST", url="https://eth.llamarpc.com", code=SERVER_ERROR, version=web/5.7.1) at _Logger.makeError (index.ts:269:28) at _Logger.throwError (index.ts:281:20) at index.ts:341:28 at Generator.next (<anonymous>) at fulfilled (browser-geturl.ts:55:2) ![Screenshot 2023-09-04 020904](https://github.com/dmihal/eth-permit/assets/67296782/ca107808-e056-4b81-a4e8-876ef5cb541a)

Export ERC2612PermitMessage interface

Hi! 👋

Firstly, thanks for your work on this project! 🙂

Today I used patch-package to patch [email protected] for the project I'm working on.

Please consider exporting the interface ERC2612PermitMessage. It would make it easier to strongly-type TypeScript references.

Here is the diff that solved my problem:

diff --git a/node_modules/eth-permit/dist/eth-permit.d.ts b/node_modules/eth-permit/dist/eth-permit.d.ts
index 73dc998..a28e9c7 100644
--- a/node_modules/eth-permit/dist/eth-permit.d.ts
+++ b/node_modules/eth-permit/dist/eth-permit.d.ts
@@ -6,7 +6,7 @@ interface DaiPermitMessage {
     expiry: number | string;
     allowed?: boolean;
 }
-interface ERC2612PermitMessage {
+export interface ERC2612PermitMessage {
     owner: string;
     spender: string;
     value: number | string;

This issue body was partially generated by patch-package.

Appears who is the signer has no effect

Hello there,

I am a bit confused as to the validity of a signer. On synthetix v3 implementation of EIP2612 I authored a test that permit() should reject a non-owner signed digest.

However, that's not the case, and I needed your assistance on this.

Using hardhat, I setup the following test case:

  let holder, spender;

  before('identify signers', async () => {
    [holder, spender] = await ethers.getSigners();
  });

And then use the "spender", who is not the "owner", to sign a digest on the owner's behalf. A case that should throw:

    it('Should reject non owner signature', async () => {
      const { deadline, v, r, s } = await signERC2612Permit(
        spender,
        ERC20.address,
        holder.address,
        spender.address,
        value
      );

      await assertRevert(
        ERC20.permit(holder.address, spender.address, value, deadline, v, r, s),
        'INVALID_PERMIT_SIGNATURE'
      );
    });

However, the contract accepts the signed "v, r, s" and the test fails.

Am I missing something obvious here?

Thank you

'Dai/invalid-permit'

Im using this exact demo code while using Hardhat on a forked Ethereum mainnet node.

const result = await signDaiPermit(window.ethereum, tokenAddress, senderAddress, spender);
await token.methods.permit(senderAddress, spender, result.nonce, result.expiry, true, result.v, result.r, result.s)

I'm able to console log all the variables and I tripled checked everything matches. However no matter what I do I always get the error message 'Dai/invalid-permit'

This is for the DAI token

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.