> For the complete documentation index, see [llms.txt](https://ston-fi.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ston-fi.gitbook.io/docs/developer-section/omniston/sdk/evm/evm-allowance.md).

# EVM allowance

This document is the main entry point for choosing the right EVM allowance method for an Omniston integration.

It answers two questions:

1. Which allowance method should an integrator use for a given token or product flow?
2. Once that allowance method is chosen, which fields should be sent to Omniston?

This document is about EVM allowance methods and the corresponding Omniston payload fields. For signature shape rules based on wallet kind, see [EVM Signature](/docs/developer-section/omniston/sdk/evm/evm-signature.md). For a dedicated explanation of what wallet kind means, see [EVM Wallet kind](/docs/developer-section/omniston/sdk/evm/evm-wallet-kind.md).

## Supported allowance methods

Omniston EVM swaps can rely on three different allowance methods:

### 1. EIP-2612

Official reference: [ERC-2612](https://eips.ethereum.org/EIPS/eip-2612)

This is a token-native permit-based allowance method defined by the token contract itself.

* the token exposes a `permit(...)` function
* allowance can be created from an EIP-712 signature
* no separate approval transaction is needed for that allowance update

### 2. Permit2

Official reference: [Permit2 overview](https://developers.uniswap.org/docs/protocols/permit2/overview)

This is a generalized permit system provided by the Uniswap Permit2 contracts.

* the token is first approved on-chain to the Permit2 contract
* later spender permissions can be granted by Permit2 signed data
* this is useful when the token does not support EIP-2612 but a signature-based allowance method is still desired

Permit2 is usually a hybrid flow:

* one initial gas-paying token approval to Permit2
* then off-chain permit signatures for later orders

### 3. Native ERC-20 allowance

Official reference: [ERC-20](https://eips.ethereum.org/EIPS/eip-20)

This is the baseline ERC-20 model.

* the wallet sends an on-chain `approve(spender, amount)` transaction
* the spender later uses `transferFrom`
* no off-chain permit is attached to the Omniston order payload

This method is the fallback when no signature-based allowance method is available.

## Choosing an allowance method

There are really two separate decisions:

### Decision A: What does the token support?

* If the token supports `EIP-2612`, you can use token-native permit.
* If it does not support `EIP-2612`, you can still use `Permit2` if your integration supports the Permit2 setup.
* If neither is available or desired, use native ERC-20 `approve`.

### Decision B: What kind of user flow do you want?

Choose `EIP-2612` when:

* the token supports it
* you want the most direct gasless allowance path
* you prefer token-native semantics over an external permit system

Choose `Permit2` when:

* the token does not support `EIP-2612`
* you still want orders to use signatures after initial setup
* you are comfortable requiring a one-time approval to the Permit2 contract

Choose native ERC-20 `approve` when:

* no signature-based path is available
* the integration wants the simplest universal fallback allowance method
* a normal on-chain approval transaction is acceptable

## Allowance methods by wallet kind

Use this matrix as a compatibility shortcut after checking token support. For background on these wallet categories, see [EVM Wallet kind](/docs/developer-section/omniston/sdk/evm/evm-wallet-kind.md).

| Wallet kind     | `EIP-2612`   | `Permit2`                           | Native ERC-20 `approve` |
| --------------- | ------------ | ----------------------------------- | ----------------------- |
| EOA             | yes, gasless | yes, gasless after initial approval | no                      |
| Delegated EOA   | yes, gasless | no                                  | yes                     |
| Contract wallet | no           | no                                  | yes                     |

## How Omniston interprets allowance data

From Omniston’s point of view there are only two payload modes:

### Mode 1: Allowance already in place

Use this when allowance is already available through native ERC-20 approval.

In this case:

* omit `encodedPermitData`
* omit `permitSignature`
* omit `usePermit2`

### Mode 2: Signed permit attached

Use this when the order should carry a signed permit.

In this case:

* send `encodedPermitData`
* send `permitSignature`
* send `usePermit2`

Meaning:

* `usePermit2 = false` means the permit is an `EIP-2612` permit
* `usePermit2 = true` means the permit is a `Permit2` allowance permit

## Omniston payload examples

These examples focus on the allowance-method part of the integration, including permit-based flows where applicable, while keeping the setup code needed to understand each method end to end.

### Example 1: EIP-2612

Not all ERC-20 tokens support EIP-2612, so support needs to be checked for each token individually.

This example uses pUSD on Polygon `0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB`.

```ts
import { encodeAbiParameters, hexToBytes } from "viem";

const ownerAddress = "0x..."; // trader wallet address on the source EVM chain

const tokenAddress = quote.inputAsset.chain.value.kind.value;
const spenderAddress = quote.settlementData.value.srcProtocolContractAddress.chain.value;

const permitTypedData = {
  domain: {
    name: "Polymarket USD",
    version: "1",
    chainId: 137,
    verifyingContract: "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB",
  },
  types: {
    Permit: [
      { name: "owner", type: "address" },
      { name: "spender", type: "address" },
      { name: "value", type: "uint256" },
      { name: "nonce", type: "uint256" },
      { name: "deadline", type: "uint256" },
    ],
  },
  primaryType: "Permit" as const,
  message: {
    owner: ownerAddress,
    spender: spenderAddress,
    value: BigInt(quote.inputUnits),
    nonce: await getContractNonce(ownerAddress, tokenAddress),
    deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
  },
};

const permitTypedDataSignature = await signTypedData({
  ...permitTypedData,
  account: ownerAddress,
});

declare const permitSignature: Uint8Array;
// Derive `permitSignature` from the wallet signature as described in `evm-signature.md`.

const encodedPermitData = hexToBytes(
  encodeAbiParameters(
    [
      { name: "owner", type: "address" },
      { name: "spender", type: "address" },
      { name: "value", type: "uint256" },
      { name: "nonce", type: "uint256" },
      { name: "deadline", type: "uint256" },
    ],
    [
      permitTypedData.message.owner,
      permitTypedData.message.spender,
      permitTypedData.message.value,
      permitTypedData.message.nonce,
      permitTypedData.message.deadline,
    ],
  ),
);

const evmOrderPayload = await omniston.evmBuildOrderPayload({
  quoteId: quote.quoteId,
  ownerSrcAddress: inputWalletAddress,
  traderDstAddress: outputWalletAddress,
  traderDstDiscloseAddress: outputWalletAddress,
  //
  encodedPermitData,
  permitSignature,
  usePermit2: false,
});
```

What this means:

* `encodedPermitData` encodes the signed token-native permit payload
* `permitSignature` is the corresponding permit signature
* `usePermit2: false` tells Omniston that this is an `EIP-2612` permit

How to derive `permitSignature` from the wallet signature depends on wallet kind. See [EVM Signature](/docs/developer-section/omniston/sdk/evm/evm-signature.md).

### Example 2: Permit2

This example uses USDC on Base `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`.

This example uses the Permit2 contract `0x000000000022D473030F116dDEE9F6B43aC78BA3`.

Permit2 usually has two parts in the integration flow:

1. one-time ERC-20 approval to the Permit2 contract
2. later off-chain Permit2 signature attached to the Omniston payload

#### Part 1: Approve the token to Permit2

Before using Permit2 signatures, the token usually needs a one-time ERC-20 approval to the Permit2 contract. That approval is still a regular on-chain transaction, so the wallet needs enough native gas token to submit it.

```ts
import { erc20Abi, maxUint256 } from "viem";

const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";

const ownerAddress = "0x..."; // trader wallet address on the source EVM chain

const tokenAddress = quote.inputAsset.chain.value.kind.value;

const currentAllowance = await readContract(wagmiConfig, {
  address: tokenAddress,
  abi: erc20Abi,
  functionName: "allowance",
  args: [ownerAddress, PERMIT2_ADDRESS],
});

if (currentAllowance < BigInt(quote.inputUnits)) {
  const approveTxHash = await writeContract(walletClient, {
    address: tokenAddress,
    abi: erc20Abi,
    functionName: "approve",
    args: [PERMIT2_ADDRESS, maxUint256],
    account: ownerAddress,
  });

  await waitForTransactionReceipt(wagmiConfig, {
    hash: approveTxHash,
  });
}
```

After this approval exists, later orders can use Permit2 signed data.

#### Part 2: Attach the Permit2 allowance permit to the Omniston payload

```ts
import { encodeAbiParameters, hexToBytes } from "viem";

const ownerAddress = "0x..."; // trader wallet address on the source EVM chain

const tokenAddress = quote.inputAsset.chain.value.kind.value;
const spenderAddress =
  quote.settlementData.value.srcProtocolContractAddress.chain.value;

const permitTypedData = {
  domain: {
    name: "Permit2",
    chainId: 8453,
    verifyingContract: PERMIT2_ADDRESS,
  },
  types: {
    PermitDetails: [
      { name: "token", type: "address" },
      { name: "amount", type: "uint160" },
      { name: "expiration", type: "uint48" },
      { name: "nonce", type: "uint48" },
    ],
    PermitSingle: [
      { name: "details", type: "PermitDetails" },
      { name: "spender", type: "address" },
      { name: "sigDeadline", type: "uint256" },
    ],
  },
  primaryType: "PermitSingle" as const,
  message: {
    details: {
      token: tokenAddress,
      amount: BigInt(quote.inputUnits),
      expiration: Math.floor(Date.now() / 1000) + 3600,
      nonce: await getPermit2Nonce(ownerAddress, tokenAddress, spenderAddress),
    },
    spender: spenderAddress,
    sigDeadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
  },
};

const permitTypedDataSignature = await signTypedData({
  ...permitTypedData,
  account: ownerAddress,
});

declare const permitSignature: Uint8Array;
// Derive `permitSignature` from the wallet signature as described in `evm-signature.md`.

const encodedPermitData = hexToBytes(
  encodeAbiParameters(
    [
      {
        name: "permitSingle",
        type: "tuple",
        components: [
          {
            name: "details",
            type: "tuple",
            components: [
              { name: "token", type: "address" },
              { name: "amount", type: "uint160" },
              { name: "expiration", type: "uint48" },
              { name: "nonce", type: "uint48" },
            ],
          },
          { name: "spender", type: "address" },
          { name: "sigDeadline", type: "uint256" },
        ],
      },
    ],
    [permitTypedData.message],
  ),
);

const evmOrderPayload = await omniston.evmBuildOrderPayload({
  quoteId: quote.quoteId,
  ownerSrcAddress: inputWalletAddress,
  traderDstAddress: outputWalletAddress,
  traderDstDiscloseAddress: outputWalletAddress,
  //
  encodedPermitData,
  permitSignature,
  usePermit2: true,
});
```

What this means:

* `encodedPermitData` encodes the signed Permit2 allowance payload
* `permitSignature` is the corresponding Permit2 signature
* `usePermit2: true` tells Omniston that this is a Permit2-based permit

How to derive `permitSignature` from the wallet signature depends on wallet kind. See [EVM Signature](/docs/developer-section/omniston/sdk/evm/evm-signature.md).

### Example 3: Native ERC-20 approve

Use this when the wallet already granted allowance by a normal on-chain `approve`.

```ts
import { erc20Abi, maxUint256 } from "viem";

const ownerAddress = "0x..."; // trader wallet address on the source EVM chain

const tokenAddress = quote.inputAsset.chain.value.kind.value;
const spenderAddress =
  quote.settlementData.value.srcProtocolContractAddress.chain.value;

const currentAllowance = await readContract(wagmiConfig, {
  address: tokenAddress,
  abi: erc20Abi,
  functionName: "allowance",
  args: [ownerAddress, spenderAddress],
});

if (currentAllowance < BigInt(quote.inputUnits)) {
  const approveTxHash = await writeContract(walletClient, {
    address: tokenAddress,
    abi: erc20Abi,
    functionName: "approve",
    args: [spenderAddress, maxUint256],
    account: ownerAddress,
  });

  await waitForTransactionReceipt(wagmiConfig, {
    hash: approveTxHash,
  });
}

const evmOrderPayload = await omniston.evmBuildOrderPayload({
  quoteId: quote.quoteId,
  ownerSrcAddress: inputWalletAddress,
  traderDstAddress: outputWalletAddress,
  traderDstDiscloseAddress: outputWalletAddress,
});
```

What this means:

* allowance is expected to already exist on-chain
* Omniston does not receive permit data
