Skip to main content

Allowance Module v1.0.0 Migration Guide

The Allowance Module v1.0.0 is a security fix release of the Safe Allowance Module. The SDK API is identical to v0.1.1: same methods, same parameters, same types. The underlying contract has been updated with three security fixes.

The new contract address is 0x691f59471Bfd2B7d639DCF74671a2d648ED1E331, replacing the previous 0xAA46724893dedD72658219405185Fb0Fc91e091C. Audited by Certora and Ackee.

For New Projects

No special steps. Install the latest version of AbstractionKit and use the default constructor:

terminal
npm install abstractionkit@0.2.38
import { AllowanceModule } from "abstractionkit";

const allowanceModule = new AllowanceModule(); // uses v1.0.0 by default

Follow the Allowance Guide as-is. The example script works without modification.

For Existing Code

If you use the default constructor (new AllowanceModule()): bump abstractionkit to the latest version. The new address is picked up automatically.

If you were passing the old address explicitly, remove it or switch to the default:

// Before (explicit old address)
const allowanceModule = new AllowanceModule("0xAA46724893dedD72658219405185Fb0Fc91e091C");

// After (use the default)
const allowanceModule = new AllowanceModule();

No other code changes are needed. All method signatures and types remain the same.

For Existing Safes with v0.1.1 Enabled

If your Safe already has the v0.1.1 module enabled, the owner must enable the new v1.0.0 module separately and re-create delegates and allowances on the new contract. The old module can be disabled afterward.

Step 1: Enable the v1.0.0 Module

import { AllowanceModule } from "abstractionkit";

const allowanceModule = new AllowanceModule(); // v1.0.0

const enableModuleMetaTx = allowanceModule.createEnableModuleMetaTransaction(
safeAccountAddress,
);

Step 2: Re-create Delegates

Each delegate must be registered on the new module.

const addDelegateMetaTx = allowanceModule.createAddDelegateMetaTransaction(
delegateAddress,
);

Step 3: Re-create Allowances

Set up the same allowances on the new contract.

// Recurring allowance
const setAllowanceMetaTx = allowanceModule.createRecurringAllowanceMetaTransaction(
delegateAddress,
tokenAddress,
allowanceAmount,
resetPeriodInMinutes,
0n, // start delay
);

Step 4: Disable the Old Module

Disable the v0.1.1 module so it can no longer be used to execute transfers.

const disableOldModuleMetaTx = await safeAccount.createDisableModuleMetaTransaction(
nodeUrl,
"0xAA46724893dedD72658219405185Fb0Fc91e091C", // v0.1.1 address
safeAccountAddress,
);

Step 5: Submit as a Batched UserOperation

All four steps can be batched into a single UserOperation:

const userOp = await safeAccount.createUserOperation(
[
enableModuleMetaTx,
addDelegateMetaTx,
setAllowanceMetaTx,
disableOldModuleMetaTx,
],
nodeUrl,
bundlerUrl,
);

userOp.signature = safeAccount.signUserOperation(
userOp,
[ownerPrivateKey],
chainId,
);

const response = await safeAccount.sendUserOperation(userOp, bundlerUrl);
const receipt = await response.included();

What Changed

The three fixes below are all security improvements. Only the nonce limit introduces a new constraint that could affect application logic.

Nonce Limit: 65,534 Transfers per Delegate/Token

v1.0.0 adds overflow protection on the uint16 nonce. Each delegate can execute at most 65,534 executeAllowanceTransfer calls for a given token on a given Safe. After that, the contract reverts.

Nonce Limit

After 65,534 transfers, the delegate can no longer execute transfers for that token. The workaround is to register a new delegate address. This is unlikely to affect most use cases, but high-frequency automated delegates should account for it.

Failed ERC-20 Transfers Now Revert

In v0.1.1, if an ERC-20 token's transfer() returned false instead of reverting (allowed by the ERC-20 spec), the allowance state was still updated. The delegate's spent amount increased even though no tokens moved.

v1.0.0 fixes this by checking the return data. No developer action needed.

Delegate Collision Safety in removeDelegate

v1.0.0 adds a require(current.delegate == delegate) check in removeDelegate to prevent accidentally removing the wrong delegate when two addresses share the same first 6 bytes (uint48 key collision). No developer action needed.

Compatibility

v1.0.0 uses execTransactionFromModuleReturnData, which was introduced in Safe v1.1.1. This means v1.0.0 is not compatible with Safe v1.0.0.

AbstractionKit targets Safe v1.4.1, so this is not a concern for current users.

Audits & References