Vyper Permit2 Tutorial

Integrate Permit2 into your contracts

vyper
ape
Author

banteg

Published

April 25, 2024

Permit21 is a contract that allows using Permit signatures2 with any token. You just approve a Permit2 contract once and then you can use it with any contract that integrates it.

It solves a crucial UX problem, but it has seen quite slow adoption. Possibly because its documentation leaves the developers scratching their heads instead of giving them an idea how to integrate Permit2 in their contracts.

This tutorial will teach you how to use Permit2 contract from Vyper and Python using copy and paste method.

Vyper contract

The first snippet defines everything you need to call the Permit2 contract. It defines all the needed structs and the interface you’ll be calling.

struct TokenPermissions:
    token: address
    amount: uint256

struct PermitTransferFrom:
    permitted: TokenPermissions
    nonce: uint256
    deadline: uint256

struct SignatureTransferDetails:
    to: address
    requestedAmount: uint256

interface Permit2:
    def permitTransferFrom(
        permit: PermitTransferFrom,
        transferDetails: SignatureTransferDetails,
        owner: address,
        signature: Bytes[65]
    ): nonpayable

permit2: immutable(Permit2)

@external
def __init__():
    permit2 = Permit2(0x000000000022D473030F116dDEE9F6B43aC78BA3)

Add a new deposit method and use it as follows:

@external
def deposit_permit(amount: uint256, nonce: uint256, deadline: uint256, signature: Bytes[65]) -> uint256:
    permit2.permitTransferFrom(
        PermitTransferFrom({
            permitted: TokenPermissions({token: self.token, amount: amount}),
            nonce: nonce,
            deadline: deadline
        }),
        SignatureTransferDetails({to: self, requestedAmount: amount}),
        msg.sender,
        signature,
    )
    # you have the amount of token here
    return self.vault.deposit(amount, msg.sender)

Note that requestedAmount can be lower than permitted amount, but it cannot exceed it.

Generate a signature

Now that our contract accepts Permit2 signatures, we need to learn how to generate them. I’ll show how to do this with ape3.

$ ape console --network ethereum:mainnet-fork:foundry

Before using Permit2, the user would need to approve it. Luckily one approval is enough to use it with any number of contracts.

dev = accounts.load("harambe")
your_contract = project.YourContract.deploy(sender=dev)

token = Contract("choose your own")
permit2 = Contract("0x000000000022D473030F116dDEE9F6B43aC78BA3")

token.approve(permit2, 2**256-1, sender=dev)

Then you you need a valid EIP-712 signature. We’ll be using eip7124 library maintained by ApeWorX.

You may notice the type you sign slightly differs from the Vyper struct. When verifying, the spender is always set to the calling contract.

from eip712 import EIP712Message, EIP712Type

class TokenPermissions(EIP712Type):
    token: "address"
    amount: "uint256"

class PermitTransferFrom(EIP712Message):
    _name_ = "Permit2"
    _chainId_ = 1
    _verifyingContract_ = "0x000000000022D473030F116dDEE9F6B43aC78BA3"

    permitted: TokenPermissions
    spender: "address"
    nonce: "uint256"
    deadline: "uint256"

In Permit2, nonce is non-monotonic. You need to set it to any unused value. I recommend setting it to the the last block timestamp. The deadline is a unix timestamp as usual.

nonce = chain.blocks[-1].timestamp
deadline = nonce + 86400

permit = PermitTransferFrom(
    TokenPermissions(token, amount),
    your_contract,
    nonce,
    deadline,
)

The signature is accepted in rsv format. There is a handy method to encode it that way.

user = accounts.load('harambe')
signature = user.sign_message(permit).encode_rsv()

tx = your_contract.deposit_permit(amount, nonce, deadline, signature)
tx.show_trace()

Congrats! You’ve just made a permit deposit into your toy vault.