Allowance
An allowance is the amount of tokens an owner authorizes a third party (spender) to transfer on their behalf. This mechanism enables smart contracts and decentralized applications to interact with a user's tokens without requiring direct ownership.
The ERC20 Absolute Allowance
In ERC20, spending permission is an absolute allowance stored as allowance(owner, spender). You grant it with approve(spender, amount) , and if you later want a different limit you call approve() again with the new number.
Because these updates happen in separate transactions and miners can reorder transactions (or a watcher can front-run from the public mempool), there's a race:
"while your change to 150 approval is pending, the spender can first call transferFrom to consume the existing 100, and then, after your update lands, still have 150 available, effectively extracting 250."
This is the classic ERC20 allowance race condition: non-atomic transitions between absolute values create a window where old and new allowances can both be honored depending on ordering.
The OP-20 Safe Adjustments
Delta-based
OP-20 replaces ERC20's absolute allowance model with delta-based adjusments: permissions change only via explicit increases or decreases that are applied atomically to the current allowance.
Because you never replace one absolute number with another, there's no window where "old" and "new" approvals can both be valid based on transaction ordering, the contract simply adjusts the live value and emits an event, eliminating the classic race that arises during updates.
Infinite Allowances
OP-20 recognizes infinite allowances for explicitly trusted spenders:
- Once granted, the allowance does not decrement on use.
- This is an opt-in optimization, revocable at any time.
Signature-based Variants
For signature-based allowance documentation, see the Allowance By Signatures section.
Using Allowance Methods in a Contract
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
Address,
Blockchain,
BytesWriter,
ReentrancyGuard,
Revert,
TransferHelper,
SELECTOR_BYTE_LENGTH,
U256_BYTE_LENGTH,
ADDRESS_BYTE_LENGTH
} from '@btc-vision/btc-runtime/runtime';
@final
export class MyContract extends ReentrancyGuard {
public constructor() {
super();
}
...
// This is only to demonstrate how to call another contract method.
// The TransferHelper class already contains an increaseAllowance method ready to be used.
private callIncreaseAllowance(token: Address, owner: Address, spender: Address, amount: u256) : void{
// Encode increaseAllowance Calldata
// 1st parameter: selector (method to call->increaseAllowance)
// 2nd parameter: spender address
// 3rd parameter: amount
const calldata = new BytesWriter(
SELECTOR_BYTE_LENGTH +
ADDRESS_BYTE_LENGTH +
U256_BYTE_LENGTH);
calldata.writeSelector(TransferHelper.INCREASE_ALLOWANCE_SELECTOR);
calldata.writeAddress(spender);
calldata.writeU256(amount);
const response = Blockchain.call(token, calldata);
if (response.data.byteLength > 0){
thow new Revert('Invalid response from increaseAllowance.');
}
}
// This is only to demonstrate how to call another contract method.
// The TransferHelper class already contains a decreaseAllowance method ready to be used.
private callDecreaseAllowance(token: Address, owner: Address, spender: Address, amount: u256) : void{
// Encode decreaseAllowance Calldata
// 1st parameter: selector (method to call->decreaseAllowance)
// 2nd parameter: spender address
// 3rd parameter: amount
const calldata = new BytesWriter(
SELECTOR_BYTE_LENGTH +
ADDRESS_BYTE_LENGTH +
U256_BYTE_LENGTH);
calldata.writeSelector(TransferHelper.DECREASE_ALLOWANCE_SELECTOR);
calldata.writeAddress(spender);
calldata.writeU256(amount);
const response = Blockchain.call(token, calldata);
if (response.data.byteLength > 0){
thow new Revert('Invalid response from decreaseAllowance.');
}
}
}