Skip to content

Latest commit

 

History

History
216 lines (178 loc) · 12.2 KB

File metadata and controls

216 lines (178 loc) · 12.2 KB
tip: 1363
title: Payable Token
author: yanghang8612@gmail.com
discussions-to: https://github.com/tronprotocol/tips/issues/892
status: Final
type: Standards Track
category: TRC
created: 2026-06-12
requires: 20, 165

Simple Summary

Defines a token interface for TRC-20 tokens that supports executing recipient code after transfer or transferFrom, or spender code after approve.

Abstract

Standard functions a token contract and contracts working with tokens can implement to make a token Payable.

transferAndCall and transferFromAndCall will call an onTransferReceived on a TRC1363Receiver contract.

approveAndCall will call an onApprovalReceived on a TRC1363Spender contract.

Motivation

There is no way to execute code after a TRC-20 transfer or approval (i.e. making a payment), so to make an action it is required to send another transaction and pay GAS twice.

This proposal wants to make token payments easier and working without the use of any other listener. It allows to make a callback after a transfer or approval in a single transaction.

There are many proposed uses of TRON smart contracts that can accept TRC-20 payments.

Examples could be

  • to create a token payable crowdsale
  • selling services for tokens
  • paying invoices
  • making subscriptions

For these reasons it was named as "Payable Token".

Anyway you can use it for specific utilities or for any other purposes who require the execution of a callback after a transfer or approval received.

This proposal has been inspired by the TRC-721 onTRC721Received and TRC721TokenReceiver behaviours.

Specification

Implementing contracts MUST implement the TRC-1363 interface as well as the TRC-20 and TRC-165 interfaces.

pragma solidity ^0.8.0;

interface TRC1363 /* is TRC20, TRC165 */ {
  /*
   * Note: the TRC-165 identifier for this interface is 0xb0202a11.
   * 0xb0202a11 ===
   *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
   *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
   *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
   *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
   *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
   *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
   */

  /**
   * @notice Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver
   * @param to address The address which you want to transfer to
   * @param value uint256 The amount of tokens to be transferred
   * @return true unless throwing
   */
  function transferAndCall(address to, uint256 value) external returns (bool);

  /**
   * @notice Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver
   * @param to address The address which you want to transfer to
   * @param value uint256 The amount of tokens to be transferred
   * @param data bytes Additional data with no specified format, sent in call to `to`
   * @return true unless throwing
   */
  function transferAndCall(address to, uint256 value, bytes memory data) external returns (bool);

  /**
   * @notice Transfer tokens from one address to another and then call `onTransferReceived` on receiver
   * @param from address The address which you want to send tokens from
   * @param to address The address which you want to transfer to
   * @param value uint256 The amount of tokens to be transferred
   * @return true unless throwing
   */
  function transferFromAndCall(address from, address to, uint256 value) external returns (bool);


  /**
   * @notice Transfer tokens from one address to another and then call `onTransferReceived` on receiver
   * @param from address The address which you want to send tokens from
   * @param to address The address which you want to transfer to
   * @param value uint256 The amount of tokens to be transferred
   * @param data bytes Additional data with no specified format, sent in call to `to`
   * @return true unless throwing
   */
  function transferFromAndCall(address from, address to, uint256 value, bytes memory data) external returns (bool);

  /**
   * @notice Approve the passed address to spend the specified amount of tokens on behalf of msg.sender
   * and then call `onApprovalReceived` on spender.
   * @param spender address The address which will spend the funds
   * @param value uint256 The amount of tokens to be spent
   * @return true unless throwing
   */
  function approveAndCall(address spender, uint256 value) external returns (bool);

  /**
   * @notice Approve the passed address to spend the specified amount of tokens on behalf of msg.sender
   * and then call `onApprovalReceived` on spender.
   * @param spender address The address which will spend the funds
   * @param value uint256 The amount of tokens to be spent
   * @param data bytes Additional data with no specified format, sent in call to `spender`
   * @return true unless throwing
   */
  function approveAndCall(address spender, uint256 value, bytes memory data) external returns (bool);
}

interface TRC20 {
  function totalSupply() external view returns (uint256);
  function balanceOf(address account) external view returns (uint256);
  function transfer(address recipient, uint256 amount) external returns (bool);
  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
  function allowance(address owner, address spender) external view returns (uint256);
  function approve(address spender, uint256 amount) external returns (bool);
  event Transfer(address indexed from, address indexed to, uint256 value);
  event Approval(address indexed owner, address indexed spender, uint256 value);
}

interface TRC165 {
  function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

A contract that wants to accept token payments via transferAndCall or transferFromAndCall MUST implement the following interface:

/**
 * @title TRC1363Receiver interface
 * @dev Interface for any contract that wants to support `transferAndCall` or `transferFromAndCall`
 *  from TRC1363 token contracts.
 */
interface TRC1363Receiver {
  /*
   * Note: the TRC-165 identifier for this interface is 0x88a7ca5c.
   * 0x88a7ca5c === bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))
   */

  /**
   * @notice Handle the receipt of TRC1363 tokens
   * @dev Any TRC1363 smart contract calls this function on the recipient
   * after a `transfer` or a `transferFrom`. This function MAY throw to revert and reject the
   * transfer. Return of other than the magic value MUST result in the
   * transaction being reverted.
   * Note: the token contract address is always the message sender.
   * @param operator address The address which called `transferAndCall` or `transferFromAndCall` function
   * @param from address The address which are token transferred from
   * @param value uint256 The amount of tokens transferred
   * @param data bytes Additional data with no specified format
   * @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))`
   *  unless throwing
   */
  function onTransferReceived(address operator, address from, uint256 value, bytes memory data) external returns (bytes4);
}

A contract that wants to accept token payments via approveAndCall MUST implement the following interface:

/**
 * @title TRC1363Spender interface
 * @dev Interface for any contract that wants to support `approveAndCall`
 *  from TRC1363 token contracts.
 */
interface TRC1363Spender {
  /*
   * Note: the TRC-165 identifier for this interface is 0x7b04a2d0.
   * 0x7b04a2d0 === bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))
   */

  /**
   * @notice Handle the approval of TRC1363 tokens
   * @dev Any TRC1363 smart contract calls this function on the recipient
   * after an `approve`. This function MAY throw to revert and reject the
   * approval. Return of other than the magic value MUST result in the
   * transaction being reverted.
   * Note: the token contract address is always the message sender.
   * @param owner address The address which called `approveAndCall` function
   * @param value uint256 The amount of tokens to be spent
   * @param data bytes Additional data with no specified format
   * @return `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))`
   *  unless throwing
   */
  function onApprovalReceived(address owner, uint256 value, bytes memory data) external returns (bytes4);
}

Behavior on non-implementing receivers and spenders

transferAndCall and transferFromAndCall MUST revert when to is an externally-owned account (EOA), or a contract that does not implement TRC1363Receiver — that is, one whose onTransferReceived does not return the bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)")) magic value. They MUST NOT fall back to a plain TRC-20 transfer. Likewise, approveAndCall MUST revert when spender is an EOA, or a contract that does not implement TRC1363Spender (i.e. does not return the onApprovalReceived magic value).

This is intentionally stricter than TRC-721 safeTransferFrom, which invokes its receiver callback only for contracts and lets EOA recipients through. The two callbacks serve opposite purposes: TRC-721's is a safety wrapper around a transfer that must be able to reach any holder, so it skips the callback for EOAs; TRC-1363's callback is the feature — a caller uses transferAndCall precisely so the recipient reacts to the payment in the same transaction. A target that cannot run onTransferReceived cannot fulfil that request, and silently completing a plain transfer would defeat the purpose and risk stranding tokens in a contract that never acknowledged them. A caller who instead wants an ordinary transfer to an arbitrary address should use TRC-20 transfer. This matches the established ERC-1363 reference implementation (OpenZeppelin's ERC1363Utils), which reverts for both the EOA (to.code.length == 0) and the non-implementing / wrong-return-value cases.

Rationale

The choice to use transferAndCall, transferFromAndCall and approveAndCall derives from the TRC-20 naming. They want to highlight that they have the same behaviours of transfer, transferFrom and approve with the addition of a callback on receiver or spender.

Backwards Compatibility

This proposal has been inspired also by ERC-223 and ERC-677 but it uses the TRC-721 approach, so it doesn't override the TRC-20 transfer and transferFrom methods and defines the interfaces IDs to be implemented maintaining the TRC-20 backwards compatibility.

Security Considerations

The approveAndCall and transferFromAndCall methods can be affected by the same issue of the standard TRC-20 approve and transferFrom method.

Changing an allowance with the approveAndCall methods brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering.

One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards (EIP-20#issuecomment-263524729).

Copyright

Copyright and related rights waived via CC0.