Signature Transfer

Learn Uniswap Permit2 signature-based transfer flows, witness signing, and unordered nonce bitmap protection.

Source Code

Read the implementation on GitHub: SignatureTransfer.sol

Overview

SignatureTransfer enables one-time, signature-based token movement without long-lived ERC-20 allowances. Use it when your integration needs explicit signer authorization per transfer.

Core Entry Points

Entry pointPurpose
permitTransferFromTransfer tokens from an owner through signature validation.
permitWitnessTransferFromTransfer through signature validation and include extra signed context via witness.

Each of these functions is overloaded with a batched version that allows users to transfer multiple tokens with 1 transaction.

Functions

Single permitTransferFrom

Use the permitTransferFrom to transfer just one token.

Function signature

function permitTransferFrom(
        PermitTransferFrom memory permit,
        SignatureTransferDetails calldata transferDetails,
        address owner,
        bytes calldata signature
    ) external

Parameters

ParameterDescription
permitConstruct PermitTransferFrom with the struct below.
transferDetailsRecipient and amount details for the transfer. See struct below.
ownerSigner of the permit message and owner of the tokens.
signatureSignature over permit data. Supports EOA signatures, compact signatures defined by EIP-2098, and contract signatures defined by EIP-1271.
struct PermitTransferFrom {
        TokenPermissions permitted;
        // a unique value for every token owner's signature to prevent signature replays
        uint256 nonce;
        // deadline on the permit signature
        uint256 deadline;
    }

struct TokenPermissions {
        // ERC20 token address
        address token;
        // the maximum amount that can be spent
        uint256 amount;
    }
struct SignatureTransferDetails {
        // recipient address
        address to;
        // spender requested amount
        uint256 requestedAmount;
    }

Batched permitTransferFrom

Use permitTransferFrom with the batched parameters when you want to transfer multiple tokens from an owner.

Function signature

function permitTransferFrom(
        PermitBatchTransferFrom memory permit,
        SignatureTransferDetails[] calldata transferDetails,
        address owner,
        bytes calldata signature
    ) external

Parameters

ParameterDescription
permitConstruct PermitBatchTransferFrom with the struct below.
transferDetailsSpender-parameterized transfer details. Array length must match TokenPermissions[] length in permit. Use requestedAmount = 0 to skip a permitted token transfer.
ownerSigner of the permit message and owner of the tokens.
signatureSignature over permit data. Supports EOA signatures, compact signatures defined by EIP-2098, and contract signatures defined by EIP-1271.
struct PermitBatchTransferFrom {
        // the tokens and corresponding amounts permitted for a transfer
        TokenPermissions[] permitted;
        // a unique value for every token owner's signature to prevent signature replays
        uint256 nonce;
        // deadline on the permit signature
        uint256 deadline;
    }

struct TokenPermissions {
        // ERC20 token address
        address token;
        // the maximum amount that can be spent
        uint256 amount;
    }
struct SignatureTransferDetails {
        // recipient address
        address to;
        // spender requested amount
        uint256 requestedAmount;
    }

Single permitWitnessTransferFrom

Function signature

function permitWitnessTransferFrom(
        PermitTransferFrom memory permit,
        SignatureTransferDetails calldata transferDetails,
        address owner,
        bytes32 witness,
        string calldata witnessTypeString,
        bytes calldata signature
    ) external

Parameters

ParameterDescription
permitSame type as the single permitTransferFrom case.
transferDetailsSame type as the single permitTransferFrom case.
ownerSigner of the permit message and owner of the tokens.
witnessArbitrary signed data used to reconstruct signature data and optionally validate additional context.
witnessTypeStringEIP-712 type string for witness hashing. Must include TokenPermissions and follow EIP-712 struct ordering.
signatureSignature over permit data. Supports EOA signatures, compact signatures defined by EIP-2098, and contract signatures defined by EIP-1271.

Batch permitWitnessTransferFrom

Function signature

function permitWitnessTransferFrom(
        PermitBatchTransferFrom memory permit,
        SignatureTransferDetails[] calldata transferDetails,
        address owner,
        bytes32 witness,
        string calldata witnessTypeString,
        bytes calldata signature
    ) external

Parameters

ParameterDescription
permitSame type as the batched permitTransferFrom case.
transferDetailsSame type as the batched permitTransferFrom case.
ownerSigner of the permit message and owner of the tokens.
witnessArbitrary signed data used to reconstruct signature data and optionally validate additional context.
witnessTypeStringEIP-712 type string for witness hashing. Must include TokenPermissions and follow EIP-712 struct ordering.
signatureSignature over permit data. Supports EOA signatures, compact signatures defined by EIP-2098, and contract signatures defined by EIP-1271.

Example permitWitnessTransferFrom parameters

If an integrating contract would also like the signer to verify information about a trade, an integrating contract may ask the signer to also sign an ExampleTrade object that we define below:

struct ExampleTrade {
	address exampleTokenAddress;
	uint256 exampleMinimumAmountOut;
}

Following EIP-712, the typehash for the data would be defined by:

bytes32 _EXAMPLE_TRADE_TYPEHASH = keccak256('ExampleTrade(address exampleTokenAddress,uint256 exampleMinimumAmountOut)');

The witness that should be passed along with the permit message should be:

 bytes32 witness = keccak256(
            abi.encode(_EXAMPLE_TRADE_TYPEHASH, exampleTrade.exampleTokenAddress, exampleTrade.exampleMinimumAmountOut));

And the witnessTypeString to be passed in should be:

string constant witnessTypeString = "ExampleTrade witness)ExampleTrade(address exampleTokenAddress,uint256 exampleMinimumAmountOut)TokenPermissions(address token,uint256 amount)"

It’s important to note that when hashing multiple typed structs, the ordering of the structs in the type string matters. Referencing EIP-712:

If the struct type references other struct types (and these in turn reference even more struct types), then the set of referenced struct types is collected, sorted by name and appended to the encoding. An example encoding is Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)

Nonce Schema

Instead of using incrementing nonces, we introduce non-monotonic, or unordered nonces with a nonceBitmap.

The nonceBitmap maps an owner's address to a uint248 value, which we will call wordPos which is the index of the desired bitmap. There are 2248 possible indices thus 2248 possible bitmaps where each bitmap holds 256 bits. A bit must be flipped on to prevent replays of users’ signatures. Bits that are dirtied may not be used again.

// nonceBitmap[ownerAddress][wordPosition] retrieves a uint256 bitmap
mapping(address => mapping(uint248 => uint256)) public nonceBitmap;

Users will sign a uint256 nonce value where the first 248 bits correspond to the word position of the bitmap to dirty and the last 8 bits correspond to the actual bit position being flipped on.

uint248 wordPos = uint248(nonce >> 8);
uint8 bitPos = uint8(nonce);
uint256 bitmap = nonceBitmap[ownerAddress][wordPos] ^ (1 << bitPos)

Security Considerations

Integrating contracts must ensure signatures can only be used in the intended caller context.

Common failure mode

A user signs a permit for a router as the permissioned spender. If the router does not validate caller context and spends any valid Permit2 message, an attacker can replay that signature and redirect funds.

  • Bind permit usage to the expected caller context.
  • Pass msg.sender as the owner parameter in permit calls when appropriate.
  • Pass msg.sender as the from parameter in transfer calls when appropriate.

Universal Router applies this pattern by checking msg.sender during routing and using msg.sender in both permit and transfer parameterization.