HIP-336: Approval and Allowance API for Tokens Source

AuthorAlbert Tam
Discussions-Tohttps://github.com/hashgraph/hedera-improvement-proposal/discussions/337
StatusFinal
Needs Council ApprovalYes
Review period endsSun, 14 Feb 2021 07:00:00 +0000
TypeStandards Track
CategoryService
Created2022-01-11
Updated2022-04-11, 2022-05-25

Abstract

Describes the addition of Services APIs to approve and exercise allowances to a third party account. The allowances grant another account the right to transfer hbar, fungible and non-fungible tokens from your account.

Motivation

The current Services CryptoTransfer API allows a user to transfer hbar, fungible and non-fungible tokens but does not permit any other account to act as a delegate or proxy. The addition of the allowance APIs will permit cryptocurrency exchanges and wallets to trade on their customer’s behalf without requiring the customer to sign every transaction in advance.

User stories

As a user, I want to enable another user the ability to spend x tokens (hbar and fungible tokens) from my account so that they can trade on my behalf without requiring that I expose my private key to them.

As a user, I want to enable another user the ability to spend specific non-fungible tokens from my account so that they can trade on my behalf without requiring that I expose my private key to them.

As a user, I want to enable another user the ability to spend all instances of a particular non-fungible token that I currently or may in the future hold in my account so that they can trade on my behalf without requiring that I expose my private key to them.

As a user, I want to modify or remove an allowance I have granted to another user so that I can manage that user’s ability to spend my tokens over time.

As a user that has been granted an allowance, I want to be able to transfer tokens owned by the owner account as if they were my tokens so that I can issue transfers on their behalf without them having to expose their private key.

Specification

Terminology

The following terms will be used repeatedly throughout this section:

  • owner: the account that owns the tokens.
  • spender: the delegate account, the account being granted the power to spend the owner’s tokens.
  • recipient: the receiver of tokens when the spender issues a CryptoTransfer operation.

Add CryptoApproveAllowance API

A new Hedera API will be added under the Crypto Service called CryptoApproveAllowance. This function is called by the token owner to create a series of allowances for a spender account.

message CryptoAllowance {
	AccountID owner = 1;
	AccountID spender = 2;
	int64 amount = 3;
}

message TokenAllowance {
	TokenID tokenId = 1;
	AccountID owner = 2;
	AccountID spender = 3;
	int64 amount = 4;
}

message NftAllowance {
	TokenID tokenId = 1;
	AccountID owner = 2;
	AccountID spender = 3;
	repeated int64 serial_numbers = 4;
	google.protobuf.BoolValue approved_for_all = 5;
	AccountID delegating_spender = 6;
}
	
message CryptoApproveAllowanceTransactionBody {
	repeated CryptoApproval cryptoAllowances = 1;
	repeated NftAllowance nftAllowances = 2;
	repeated TokenAllowance tokenAllowances = 3;
}

This function contains a list of hbar and/or token approval messages. The CryptoAllowance and TokenAllowance messages will set an allowance of amount hbar or fungible tokens that can be transferred from the owner’s account to another account by the spender account. The NftAllowance message sets an allowance for specific NFT serial numbers within the owner’s account. Each of the hbar/token approvals in the list do not need to refer to the same spender. The owner can establish different allowances for various spenders in a single transaction.

The total number of approvals contained within the transaction body cannot exceed 20. This number includes all hbar, fungible and non-fungible token approvals. Note that each NFT serial number counts as a single approval, hence a transaction granting 20 serial numbers to a spender will use all of the approvals permitted for the transaction.

The amount must be specified in terms of the decimals value for the token. If amount is 0 the transaction will remove the spender’s allowance from the owner’s account, thereby freeing an allowance for future use.

A single NFT serial number can only be granted to one spender at a time. If an approval assigns a previously approved NFT serial number to a new user, the old user will have their approval removed. For example, if the following scenario were to occur:

  1. userA approves userB to use NFT T with serial S
  2. userA approves userC to use NFT T with serial S

the end result will be that userC will hold an allowance to NFT T serial S while userB will have their allowance for NFT T serial S removed.

In order to grant the spender access to all of the owner’s instances of a particular NFT, the approved_for_all field in the NftAllowance message should be set to true.

If the spender has already been granted approved_for_all for an owner’s NFT and another transaction granting approved_for_all is sent for the same NFT, the transaction will be accepted and fees charged but it will effectively be a no-op. Similarly if multiple transactions to remove approved_for_all on the same NFT are received the effect will be idempotent.

If the spender has been granted approved_for_all for an owner’s NFT, the spender will in turn be permitted to approve an NFT serial number be spent by yet another spender. In other words, an approved_for_all spender effectively has the same privileges as the owner of the NFT. This behavior is consistent with the ERC721 implementation and is included here for backward compatibility of Ethereum smart contracts being ported over to Hedera. For signature checking performance considerations, the spender must set the delegating_spender field to their account and sign the transaction when spending using an approved_for_all privilege. Failure to do so will result in an INVALID_SIGNATURE error. If the spender does not set the delegating_spender field, the transaction will be treated as if it were a regular approval, meaning the owner signature will be required.

For hbar and fungible token approvals, the amounts specified in the transaction will overwrite the current value of the spender’s allowance with the owner. For NFT approvals, each approval will add the specified serial numbers to the spender’s allowance.

It is not an error if the total token amount in the owner’s account is less than the specified allowance amount. If the spender tries to transfer more tokens than exists in the owner’s account that transaction will fail.

It IS an error if an NFT allowance specifies a serial number that the owner does not have possession of.

It is not an error if either the spender or owner’s accounts are frozen or KYC-revoked for the specified token type. If the spender tries to transfer a token under these conditions, the transaction will fail anyway.

It is not an error if the token is paused and an allowance approval is submitted.

Duplicate approvals are permitted although they will count against the 20 approval limit per transaction. In the event of a duplicate approval, the last occurrence of the spender’s approval will be used.

The transaction must be signed by the owner’s account and the account paying the transaction fees for the transaction. If the owner is paying the transaction fees only their signature is required.

Each account is limited to 100 allowances. This limit spans hbar and fungible token allowances and non-fungible token approved_for_all grants. There is no limit on the number of NFT serial number approvals an owner may grant.

The number of allowances set on an account will increase the auto renewal fee for the account. Conversely, removing allowances will decrease the auto renewal fee for the account.

The allowance approval transaction will fail under the following circumstances:

  • The spender account is the same as the owner’s account.
  • The amount will exceed the maximum supply of the token.
  • amount is negative.
  • The owner account is not associated with the specified token.
  • The owner account does not own the specified NFT serial numbers.
  • A serial number for an NFT does not exist.
  • The token referenced in an NFT allowance is fungible.
  • No allowances are specified.

Add CryptoDeleteAllowance API

A new Hedera API will be added under the Crypto Service called CryptoDeleteAllowance. This function is called by the token owner to delete allowances for NFTs only. In order to delete an existing hbar or fungible token allowance the CryptoApproveAllowance API should be used with an amount of 0.


// new message
message NftRemoveAllowance {
    TokenID token_id = 1;
    AccountID owner = 2;
    repeated int64 serial_numbers = 3;
}

// new message
message CryptoDeleteAllowanceTransactionBody {
    repeated NftRemoveAllowance nftAllowances = 2;
}

The NftWipeAllowance message is used to remove a number of NFT serial number allowances regardless of who the spender currently is.

A single CryptoDeleteAllowanceTransactionBody can specify the removal of allowances belonging to multiple owners so long as all parties sign the transaction.

The total number of NFT serial number deletions contained within the transaction body cannot exceed 20.

The transaction will not fail if the owner has not granted any allowances and a deletion is attempted.

It is not an error if the owner’s account is frozen or KYC-revoked for the specified token type.

It is not an error if the token is paused and an allowance deletion is submitted.

Duplicate deletions are permitted although they will count against the 20 deletion limit per transaction. In the event of a duplicate deletion, the last occurrence of the spender’s deletion will be used.

The transaction must be signed by the owner’s account and the account paying the transaction fees for the transaction. If the owner is paying the transaction fees only their signature is required.

The deletion transaction will fail under the following circumstances:

  • The owner account is not associated with the specified token.
  • The owner account does not own the specified NFT serial numbers.
  • A serial number for an NFT does not exist.
  • The token referenced is fungible.
  • No allowances are specified.

Change CryptoTransfer to Support Approved Allowances

The existing CryptoTransfer API will be used to support the transfer of tokens by a spender under their approved allowance. The existing protobuf message for CryptoTransfer will be reused with one minor change to the AccountAmount and NftTransfer messages - the addition of an is_approval field to indicate whether the transfer involves an approved transfer or not.

// existing protobuf
message CryptoTransferTransactionBody {
	TransferList transfers = 1;
	repeated TokenTransferList tokenTransfers = 2;
}

// existing protobuf
message TransferList {
	repeated AccountAmount accountAmounts = 1;
}

// changed protobuf
message AccountAmount {
	// existing fields
	AccountID accountID = 1;
	sint64 amount = 2;
	
	// new field
	bool is_approval = 3;
}

// existing protobuf
message TokenTransferList {
	TokenID token = 1;
	repeated AccountAmount transfers = 2;
	repeated NftTransfer nftTransfers = 3;
}

// changed protobuf
message NftTransfer {
	// existing fields
	AccountID senderAccountID = 1;
   AccountID receiverAccountID = 2;
   int64 serialNumber = 3;
   
   // new field
   bool is_approval = 4;
}

To treat a transfer as an allowance transfer, we have to check if the transaction is signed by the debiting account. It is costly to expand signatures and verify this again during the balance adjustments. So to help us avoid this, a new boolean flag is_approval is added to the AccountAmount and NftTransfer messages which denotes that the transfer has to be treated as allowance transfer if it set to true. This gives a lot of flexibility now as a transaction can contain a list of regular transfers and approved transfers, each potentially referencing a different token and/or owner.

The is_approval field must be set to true for transfers that will involve an approved allowance otherwise a regular transfer will be assumed. For hbar and fungible transfers the is_approval field must be set on the debiting AccountAmount while for NFTs the is_approval field is set on the transfer message itself. Multiple owner accounts can be involved in a single transfer by setting is_approval to true for each of the owner AccountAmount messages.

The spender (acting here as the caller) must sign the transaction. The owner of the token (the party that approved the spender’s allowance) is not required to sign the transaction.

The transaction fees will be paid from the caller’s account and not the owner’s. This behavior is similar to regular crypto/token transfers as well as Ethereum-based ERC20 tokens transferred under transferFrom.

Custom fees are charged in exactly the same manner as regular transfers are.

  • If net_of_transfers is enabled for fractional fees, the recipient will receive the complete amount during the transfer; otherwise a portion of the amount will be deducted from the recipient.
  • The limit for multi-level custom fees still remains at 2 with no cycles permitted (other than the case of a token paying fees in its own units).

The spender’s token allowance with the owner will be decreased by the transfer amount and will not include the customFees involved in the transfer. If the transfer amount will decrease the spender’s allowance to zero, this transaction will effectively remove the spender’s allowance from the owner’s account.

If NFTs are transferred, the spender’s claim on each of the serial numbers will be removed from the NFT object itself. Since the exact list of granted serial numbers is not tracked on the owner account, no allowance update needs to be made to the owner account. Note that once an NFT is transferred from the owner’s account, any associated allowance for the pertinent serial numbers will be removed. An NFT approval will only exist so long as the owner maintains possession of the NFT serial number.

The transfer transaction will fail under the following circumstances:

  • The accountID sending and receiving the token cannot be the same address.
  • The recipient account does not have an association with the token yet.
    • If the recipient account has auto-association enabled, at least one open slot must be available for the transfer to succeed.
  • The recipient account has receiverSigRequired enabled and the recipient has not signed the transaction.
  • The owner account does not have amount of token available.
  • The transfer amount plus custom fixed fees will exceed the total amount of token available.
  • The spender does not have an allowance established with the owner for the specified token.
  • The amount to transfer exceeds the spender’s available allowance.
  • An NFT serial number is being transferred and the owner does not have possession of it.
  • Either the owner or recipient accounts are frozen for the specified token.
  • The specified token is paused.

Add Spender ID to TokenNftInfo

The current spender that has been approved access to an NFT serial number will be added to the TokenNftInfo message that is returned as part of the TokenGetNftInfoResponse and TokenGetNftInfosResponse.

// existing message
message TokenNftInfo {
	// existing fields
	NftID nftID = 1;
	AccountID accountID = 2;	// current owner
	Timestamp creationTime = 3;
	bytes metadata = 4;
	bytes ledger_id = 5;
 
	// new field
	AccountID spender_id = 6;
}

Only one spender can be assigned to an NFT serial number at a time.

If the NFT serial number has not been approved to spend by an account, the spender_id field will not be included.

Backwards Compatibility

The CryptoApproveAllowance and CryptoDeleteAllowance transactions are new additions to the API and will not have any backward compatibility issues to address.

The CryptoTransfer transaction is not incurring any API/signature changes but the backend implementation is being changed to accommodate the proposed allowance/approval mechanism. Transactions submitted by the spender that seek to utilize the allowance feature before the feature is enabled will fail validation since the owner has not signed the transaction. Once the feature is enabled, regular transfers will still be handled in the same manner and spender approved transfers will also be permitted.

Security Implications

The approved allowances APIs discussed herein were inspired by ERC-20. The attack vector related to the ERC-20 approve() method is not relevant to the Hedera implementation since the former’s vulnerability is the result of possible transaction ordering by the miner while the Hedera hashgraph consensus protocol does not permit reordering of transactions.

Rejected Ideas

Introduce New API for Approved Transfers

The initial proposal introduced a new transaction API for transferring tokens that would support an approved transfer list. This API would only allow a spender to submit approved transfers and did not include regular crypto or token transfers. During design review several Hedera engineers felt that there should only be a single transfer API rather than two. Upon further inspection it was found that the current CryptoTransfer message format could support approved transfers in addition to regular transfers.

Support One Allowance in Approval API

The approval API introduced in the initial proposal only permitted one allowance to be specified via a oneof, either an hbar or a token allowance. During internal design reviews, it was felt that since the oneof was the only field in the CryptoApproveAllowanceTransactionBody message, it would make more sense to separate the CryptoAllowance and TokenAllowance messages into distinct transaction body messages. Additionally, specifying both hbar and token approvals (as lists) within a single transaction permits the caller to specify multiple approvals in bulk rather than issuing many calls.

Maintain List of Approved NFTs in Account

The initial proposal maintained an Account mapping that would track all NFT serial numbers that were approved. Each map entry consisted of an NFT tokenId-spenderAccountId composite key and an array of NFT serial numbers. This approach would consume 16 bytes per NFT serial number in the worst case (assuming a different NFT token per serial number). The current approach does not keep track of the approved NFTs from the Account but rather tracks the spender on the NFT serial number object itself and only incurs a cost of 4 bytes per NFT serial number. The loss of the NFT approval list on the Account is a potential usability issue but this same deficiency is found in the ERC721 implementation so migrating users will not be losing any functionality. Additionally this same query could be served by a mirror node API in the future.

Open Issues

References

Copyright/license

This document is licensed under the Apache License, Version 2.0 – see LICENSE or (https://www.apache.org/licenses/LICENSE-2.0)

Citation

Please cite this document as: