System Smart Contracts
System smart contracts are Hedera API functionality logic presented at reserved address locations on the EVM network. These addresses contain reserved function selectors. When a deployed contract calls these selectors, they execute as though a corresponding system contract exists on the network. Both system and user-deployed contracts live at the same address. If a contract is redeployed, it gets a new address while the original address retains the old bytecode.
System Smart Contract Interfaces
Solidity interfaces provide and define a set of functions that other smart contracts can call. The interfaces for all Hedera systems contracts written in Solidity are maintained in the hedera-smart-contracts
repository.
Note: The following Solidity examples are not production-ready and are intended solely for instructional purposes to guide developers.
The following is a list of available system contracts on Hedera:
➡ Exchange Rate
The exchange rate contract allows you to convert from tinycents to tinybars and from tinybars to tinycents.
0x168
Example ⬇
IExchangeRate.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.4.9 <0.9.0;
interface IExchangeRate {
// Given a value in tinycents (1e-8 US cents or 1e-10 USD), returns the
// equivalent value in tinybars (1e-8 HBAR) at the current exchange rate
// stored in system file 0.0.112.
//
// This rate is a weighted median of the the recent" HBAR-USD exchange
// rate on major exchanges, but should _not_ be treated as a live price
// oracle! It is important primarily because the network will use it to
// compute the tinybar fees for the active transaction.
//
// So a "self-funding" contract can use this rate to compute how much
// tinybar its users must send to cover the Hedera fees for the transaction.
function tinycentsToTinybars(uint256 tinycents) external returns (uint256);
// Given a value in tinybars (1e-8 HBAR), returns the equivalent value in
// tinycents (1e-8 US cents or 1e-10 USD) at the current exchange rate
// stored in system file 0.0.112.
//
// This rate tracks the the HBAR-USD rate on public exchanges, but
// should _not_ be treated as a live price oracle! This conversion is
// less likely to be needed than the above conversion from tinycent to
// tinybars, but we include it for completeness.
function tinybarsToTinycents(uint256 tinybars) external returns (uint256);
}
Reference: HIP-475.
➡ Hedera Token Service
The Hedera Token Service smart contract precompile provides functions to use the native Hedera Token Service in smart contracts. Tokens created using this method can also be managed using the native Hedera Token Service APIs.
0x167
Example ⬇
IHederaTokenService.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.4.9 <0.9.0;
pragma experimental ABIEncoderV2;
interface IHederaTokenService {
/// Transfers cryptocurrency among two or more accounts by making the desired adjustments to their
/// balances. Each transfer list can specify up to 10 adjustments. Each negative amount is withdrawn
/// from the corresponding account (a sender), and each positive one is added to the corresponding
/// account (a receiver). The amounts list must sum to zero. Each amount is a number of tinybars
/// (there are 100,000,000 tinybars in one hbar). If any sender account fails to have sufficient
/// hbars, then the entire transaction fails, and none of those transfers occur, though the
/// transaction fee is still charged. This transaction must be signed by the keys for all the sending
/// accounts, and for any receiving accounts that have receiverSigRequired == true. The signatures
/// are in the same order as the accounts, skipping those accounts that don't need a signature.
/// @custom:version 0.3.0 previous version did not include isApproval
struct AccountAmount {
// The Account ID, as a solidity address, that sends/receives cryptocurrency or tokens
address accountID;
// The amount of the lowest denomination of the given token that
// the account sends(negative) or receives(positive)
int64 amount;
// If true then the transfer is expected to be an approved allowance and the
// accountID is expected to be the owner. The default is false (omitted).
bool isApproval;
}
/// A sender account, a receiver account, and the serial number of an NFT of a Token with
/// NON_FUNGIBLE_UNIQUE type. When minting NFTs the sender will be the default AccountID instance
/// (0.0.0 aka 0x0) and when burning NFTs, the receiver will be the default AccountID instance.
/// @custom:version 0.3.0 previous version did not include isApproval
struct NftTransfer {
// The solidity address of the sender
address senderAccountID;
// The solidity address of the receiver
address receiverAccountID;
// The serial number of the NFT
int64 serialNumber;
// If true then the transfer is expected to be an approved allowance and the
// accountID is expected to be the owner. The default is false (omitted).
bool isApproval;
}
struct TokenTransferList {
// The ID of the token as a solidity address
address token;
// Applicable to tokens of type FUNGIBLE_COMMON. Multiple list of AccountAmounts, each of which
// has an account and amount.
AccountAmount[] transfers;
// Applicable to tokens of type NON_FUNGIBLE_UNIQUE. Multiple list of NftTransfers, each of
// which has a sender and receiver account, including the serial number of the NFT
NftTransfer[] nftTransfers;
}
struct TransferList {
// Multiple list of AccountAmounts, each of which has an account and amount.
// Used to transfer hbars between the accounts in the list.
AccountAmount[] transfers;
}
/// Expiry properties of a Hedera token - second, autoRenewAccount, autoRenewPeriod
struct Expiry {
// The epoch second at which the token should expire; if an auto-renew account and period are
// specified, this is coerced to the current epoch second plus the autoRenewPeriod
int64 second;
// ID of an account which will be automatically charged to renew the token's expiration, at
// autoRenewPeriod interval, expressed as a solidity address
address autoRenewAccount;
// The interval at which the auto-renew account will be charged to extend the token's expiry
int64 autoRenewPeriod;
}
/// A Key can be a public key from either the Ed25519 or ECDSA(secp256k1) signature schemes, where
/// in the ECDSA(secp256k1) case we require the 33-byte compressed form of the public key. We call
/// these public keys <b>primitive keys</b>.
/// A Key can also be the ID of a smart contract instance, which is then authorized to perform any
/// precompiled contract action that requires this key to sign.
/// Note that when a Key is a smart contract ID, it <i>doesn't</i> mean the contract with that ID
/// will actually create a cryptographic signature. It only means that when the contract calls a
/// precompiled contract, the resulting "child transaction" will be authorized to perform any action
/// controlled by the Key.
/// Exactly one of the possible values should be populated in order for the Key to be valid.
struct KeyValue {
// if set to true, the key of the calling Hedera account will be inherited as the token key
bool inheritAccountKey;
// smart contract instance that is authorized as if it had signed with a key
address contractId;
// Ed25519 public key bytes
bytes ed25519;
// Compressed ECDSA(secp256k1) public key bytes
bytes ECDSA_secp256k1;
// A smart contract that, if the recipient of the active message frame, should be treated
// as having signed. (Note this does not mean the <i>code being executed in the frame</i>
// will belong to the given contract, since it could be running another contract's code via
// <tt>delegatecall</tt>. So setting this key is a more permissive version of setting the
// contractID key, which also requires the code in the active message frame belong to the
// the contract with the given id.)
address delegatableContractId;
}
/// A list of token key types the key should be applied to and the value of the key
struct TokenKey {
// bit field representing the key type. Keys of all types that have corresponding bits set to 1
// will be created for the token.
// 0th bit: adminKey
// 1st bit: kycKey
// 2nd bit: freezeKey
// 3rd bit: wipeKey
// 4th bit: supplyKey
// 5th bit: feeScheduleKey
// 6th bit: pauseKey
// 7th bit: ignored
uint keyType;
// the value that will be set to the key type
KeyValue key;
}
/// Basic properties of a Hedera Token - name, symbol, memo, tokenSupplyType, maxSupply,
/// treasury, freezeDefault. These properties are related both to Fungible and NFT token types.
struct HederaToken {
// The publicly visible name of the token. The token name is specified as a Unicode string.
// Its UTF-8 encoding cannot exceed 100 bytes, and cannot contain the 0 byte (NUL).
string name;
// The publicly visible token symbol. The token symbol is specified as a Unicode string.
// Its UTF-8 encoding cannot exceed 100 bytes, and cannot contain the 0 byte (NUL).
string symbol;
// The ID of the account which will act as a treasury for the token as a solidity address.
// This account will receive the specified initial supply or the newly minted NFTs in
// the case for NON_FUNGIBLE_UNIQUE Type
address treasury;
// The memo associated with the token (UTF-8 encoding max 100 bytes)
string memo;
// IWA compatibility. Specified the token supply type. Defaults to INFINITE
bool tokenSupplyType;
// IWA Compatibility. Depends on TokenSupplyType. For tokens of type FUNGIBLE_COMMON - the
// maximum number of tokens that can be in circulation. For tokens of type NON_FUNGIBLE_UNIQUE -
// the maximum number of NFTs (serial numbers) that can be minted. This field can never be changed!
int64 maxSupply;
// The default Freeze status (frozen or unfrozen) of Hedera accounts relative to this token. If
// true, an account must be unfrozen before it can receive the token
bool freezeDefault;
// list of keys to set to the token
TokenKey[] tokenKeys;
// expiry properties of a Hedera token - second, autoRenewAccount, autoRenewPeriod
Expiry expiry;
}
/// Additional post creation fungible and non fungible properties of a Hedera Token.
struct TokenInfo {
/// Basic properties of a Hedera Token
HederaToken token;
/// The number of tokens (fungible) or serials (non-fungible) of the token
int64 totalSupply;
/// Specifies whether the token is deleted or not
bool deleted;
/// Specifies whether the token kyc was defaulted with KycNotApplicable (true) or Revoked (false)
bool defaultKycStatus;
/// Specifies whether the token is currently paused or not
bool pauseStatus;
/// The fixed fees collected when transferring the token
FixedFee[] fixedFees;
/// The fractional fees collected when transferring the token
FractionalFee[] fractionalFees;
/// The royalty fees collected when transferring the token
RoyaltyFee[] royaltyFees;
/// The ID of the network ledger
string ledgerId;
}
/// Additional fungible properties of a Hedera Token.
struct FungibleTokenInfo {
/// The shared hedera token info
TokenInfo tokenInfo;
/// The number of decimal places a token is divisible by
int32 decimals;
}
/// Additional non fungible properties of a Hedera Token.
struct NonFungibleTokenInfo {
/// The shared hedera token info
TokenInfo tokenInfo;
/// The serial number of the nft
int64 serialNumber;
/// The account id specifying the owner of the non fungible token
address ownerId;
/// The epoch second at which the token was created.
int64 creationTime;
/// The unique metadata of the NFT
bytes metadata;
/// The account id specifying an account that has been granted spending permissions on this nft
address spenderId;
}
/// A fixed number of units (hbar or token) to assess as a fee during a transfer of
/// units of the token to which this fixed fee is attached. The denomination of
/// the fee depends on the values of tokenId, useHbarsForPayment and
/// useCurrentTokenForPayment. Exactly one of the values should be set.
struct FixedFee {
int64 amount;
// Specifies ID of token that should be used for fixed fee denomination
address tokenId;
// Specifies this fixed fee should be denominated in Hbar
bool useHbarsForPayment;
// Specifies this fixed fee should be denominated in the Token currently being created
bool useCurrentTokenForPayment;
// The ID of the account to receive the custom fee, expressed as a solidity address
address feeCollector;
}
/// A fraction of the transferred units of a token to assess as a fee. The amount assessed will never
/// be less than the given minimumAmount, and never greater than the given maximumAmount. The
/// denomination is always units of the token to which this fractional fee is attached.
struct FractionalFee {
// A rational number's numerator, used to set the amount of a value transfer to collect as a custom fee
int64 numerator;
// A rational number's denominator, used to set the amount of a value transfer to collect as a custom fee
int64 denominator;
// The minimum amount to assess
int64 minimumAmount;
// The maximum amount to assess (zero implies no maximum)
int64 maximumAmount;
bool netOfTransfers;
// The ID of the account to receive the custom fee, expressed as a solidity address
address feeCollector;
}
/// A fee to assess during a transfer that changes ownership of an NFT. Defines the fraction of
/// the fungible value exchanged for an NFT that the ledger should collect as a royalty. ("Fungible
/// value" includes both â and units of fungible HTS tokens.) When the NFT sender does not receive
/// any fungible value, the ledger will assess the fallback fee, if present, to the new NFT owner.
/// Royalty fees can only be added to tokens of type type NON_FUNGIBLE_UNIQUE.
struct RoyaltyFee {
// A fraction's numerator of fungible value exchanged for an NFT to collect as royalty
int64 numerator;
// A fraction's denominator of fungible value exchanged for an NFT to collect as royalty
int64 denominator;
// If present, the fee to assess to the NFT receiver when no fungible value
// is exchanged with the sender. Consists of:
// amount: the amount to charge for the fee
// tokenId: Specifies ID of token that should be used for fixed fee denomination
// useHbarsForPayment: Specifies this fee should be denominated in Hbar
int64 amount;
address tokenId;
bool useHbarsForPayment;
// The ID of the account to receive the custom fee, expressed as a solidity address
address feeCollector;
}
/**********************
* Direct HTS Calls *
**********************/
/// Performs transfers among combinations of tokens and hbars
/// @param transferList the list of hbar transfers to do
/// @param tokenTransfers the list of token transfers to do
/// @custom:version 0.3.0 the signature of the previous version was cryptoTransfer(TokenTransferList[] memory tokenTransfers)
function cryptoTransfer(TransferList memory transferList, TokenTransferList[] memory tokenTransfers)
external
returns (int64 responseCode);
/// Mints an amount of the token to the defined treasury account
/// @param token The token for which to mint tokens. If token does not exist, transaction results in
/// INVALID_TOKEN_ID
/// @param amount Applicable to tokens of type FUNGIBLE_COMMON. The amount to mint to the Treasury Account.
/// Amount must be a positive non-zero number represented in the lowest denomination of the
/// token. The new supply must be lower than 2^63.
/// @param metadata Applicable to tokens of type NON_FUNGIBLE_UNIQUE. A list of metadata that are being created.
/// Maximum allowed size of each metadata is 100 bytes
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return newTotalSupply The new supply of tokens. For NFTs it is the total count of NFTs
/// @return serialNumbers If the token is an NFT the newly generate serial numbers, othersise empty.
function mintToken(
address token,
int64 amount,
bytes[] memory metadata
)
external
returns (
int64 responseCode,
int64 newTotalSupply,
int64[] memory serialNumbers
);
/// Burns an amount of the token from the defined treasury account
/// @param token The token for which to burn tokens. If token does not exist, transaction results in
/// INVALID_TOKEN_ID
/// @param amount Applicable to tokens of type FUNGIBLE_COMMON. The amount to burn from the Treasury Account.
/// Amount must be a positive non-zero number, not bigger than the token balance of the treasury
/// account (0; balance], represented in the lowest denomination.
/// @param serialNumbers Applicable to tokens of type NON_FUNGIBLE_UNIQUE. The list of serial numbers to be burned.
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return newTotalSupply The new supply of tokens. For NFTs it is the total count of NFTs
function burnToken(
address token,
int64 amount,
int64[] memory serialNumbers
) external returns (int64 responseCode, int64 newTotalSupply);
/// Associates the provided account with the provided tokens. Must be signed by the provided
/// Account's key or called from the accounts contract key
/// If the provided account is not found, the transaction will resolve to INVALID_ACCOUNT_ID.
/// If the provided account has been deleted, the transaction will resolve to ACCOUNT_DELETED.
/// If any of the provided tokens is not found, the transaction will resolve to INVALID_TOKEN_REF.
/// If any of the provided tokens has been deleted, the transaction will resolve to TOKEN_WAS_DELETED.
/// If an association between the provided account and any of the tokens already exists, the
/// transaction will resolve to TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT.
/// If the provided account's associations count exceed the constraint of maximum token associations
/// per account, the transaction will resolve to TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED.
/// On success, associations between the provided account and tokens are made and the account is
/// ready to interact with the tokens.
/// @param account The account to be associated with the provided tokens
/// @param tokens The tokens to be associated with the provided account. In the case of NON_FUNGIBLE_UNIQUE
/// Type, once an account is associated, it can hold any number of NFTs (serial numbers) of that
/// token type
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function associateTokens(address account, address[] memory tokens)
external
returns (int64 responseCode);
/// Single-token variant of associateTokens. Will be mapped to a single entry array call of associateTokens
/// @param account The account to be associated with the provided token
/// @param token The token to be associated with the provided account
function associateToken(address account, address token)
external
returns (int64 responseCode);
/// Dissociates the provided account with the provided tokens. Must be signed by the provided
/// Account's key.
/// If the provided account is not found, the transaction will resolve to INVALID_ACCOUNT_ID.
/// If the provided account has been deleted, the transaction will resolve to ACCOUNT_DELETED.
/// If any of the provided tokens is not found, the transaction will resolve to INVALID_TOKEN_REF.
/// If any of the provided tokens has been deleted, the transaction will resolve to TOKEN_WAS_DELETED.
/// If an association between the provided account and any of the tokens does not exist, the
/// transaction will resolve to TOKEN_NOT_ASSOCIATED_TO_ACCOUNT.
/// If a token has not been deleted and has not expired, and the user has a nonzero balance, the
/// transaction will resolve to TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES.
/// If a <b>fungible token</b> has expired, the user can disassociate even if their token balance is
/// not zero.
/// If a <b>non fungible token</b> has expired, the user can <b>not</b> disassociate if their token
/// balance is not zero. The transaction will resolve to TRANSACTION_REQUIRED_ZERO_TOKEN_BALANCES.
/// On success, associations between the provided account and tokens are removed.
/// @param account The account to be dissociated from the provided tokens
/// @param tokens The tokens to be dissociated from the provided account.
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function dissociateTokens(address account, address[] memory tokens)
external
returns (int64 responseCode);
/// Single-token variant of dissociateTokens. Will be mapped to a single entry array call of dissociateTokens
/// @param account The account to be associated with the provided token
/// @param token The token to be associated with the provided account
function dissociateToken(address account, address token)
external
returns (int64 responseCode);
/// Creates a Fungible Token with the specified properties
/// @param token the basic properties of the token being created
/// @param initialTotalSupply Specifies the initial supply of tokens to be put in circulation. The
/// initial supply is sent to the Treasury Account. The supply is in the lowest denomination possible.
/// @param decimals the number of decimal places a token is divisible by
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenAddress the created token's address
function createFungibleToken(
HederaToken memory token,
int64 initialTotalSupply,
int32 decimals
) external payable returns (int64 responseCode, address tokenAddress);
/// Creates a Fungible Token with the specified properties
/// @param token the basic properties of the token being created
/// @param initialTotalSupply Specifies the initial supply of tokens to be put in circulation. The
/// initial supply is sent to the Treasury Account. The supply is in the lowest denomination possible.
/// @param decimals the number of decimal places a token is divisible by.
/// @param fixedFees list of fixed fees to apply to the token
/// @param fractionalFees list of fractional fees to apply to the token
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenAddress the created token's address
function createFungibleTokenWithCustomFees(
HederaToken memory token,
int64 initialTotalSupply,
int32 decimals,
FixedFee[] memory fixedFees,
FractionalFee[] memory fractionalFees
) external payable returns (int64 responseCode, address tokenAddress);