Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/oft-hts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
out
cache

# artifacts; ignore all files except the local contract artifacts.
artifacts/*
31 changes: 31 additions & 0 deletions packages/oft-hts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<p align="center">
<a href="https://layerzero.network">
<img alt="LayerZero" style="max-width: 500px" src="https://d3a2dpnnrypp5h.cloudfront.net/bridge-app/lz.png"/>
</a>
</p>

<h1 align="center">@layerzerolabs/oft-hts</h1>

<!-- The badges section -->
<p align="center">
<!-- Shields.io NPM published package version -->
<a href="https://www.npmjs.com/package/@layerzerolabs/oft-hts"><img alt="NPM Version" src="https://img.shields.io/npm/v/@layerzerolabs/oft-hts"/></a>
<!-- Shields.io NPM downloads -->
<a href="https://www.npmjs.com/package/@layerzerolabs/oft-hts"><img alt="Downloads" src="https://img.shields.io/npm/dm/@layerzerolabs/oft-hts"/></a>
<!-- Shields.io license badge -->
<a href="https://www.npmjs.com/package/@layerzerolabs/oft-hts"><img alt="NPM License" src="https://img.shields.io/npm/l/@layerzerolabs/oft-hts"/></a>
</p>

## Installation

```bash
pnpm install @layerzerolabs/oft-hts
```

```bash
yarn install @layerzerolabs/oft-hts
```

```bash
npm install @layerzerolabs/oft-hts
```
159 changes: 159 additions & 0 deletions packages/oft-hts/contracts/HederaTokenService.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.5.0 <0.9.0;
pragma experimental ABIEncoderV2;

import { IHederaTokenService } from "./IHederaTokenService.sol";

abstract contract HederaTokenService {
// all response codes are defined here https://github.com/hashgraph/hedera-smart-contracts/blob/main/contracts/system-contracts/HederaResponseCodes.sol
int32 constant UNKNOWN_CODE = 21;
int32 constant SUCCESS_CODE = 22;

address constant precompileAddress = address(0x167);
// 90 days in seconds
int32 constant defaultAutoRenewPeriod = 7776000;

modifier nonEmptyExpiry(IHederaTokenService.HederaToken memory token) {
if (token.expiry.second == 0 && token.expiry.autoRenewPeriod == 0) {
token.expiry.autoRenewPeriod = defaultAutoRenewPeriod;
}
_;
}

/// Generic event
event CallResponseEvent(bool, bytes);

/// 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, otherwise empty.
function mintToken(
address token,
int64 amount,
bytes[] memory metadata
)
internal
returns (
int responseCode,
int64 newTotalSupply,
int64[] memory serialNumbers
)
{
(bool success, bytes memory result) = precompileAddress.call(
abi.encodeWithSelector(
IHederaTokenService.mintToken.selector,
token,
amount,
metadata
)
);
(responseCode, newTotalSupply, serialNumbers) = success
? abi.decode(result, (int32, int64, int64[]))
: (HederaTokenService.UNKNOWN_CODE, int64(0), new int64[](0));
}

/// 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
) internal returns (int responseCode, int64 newTotalSupply) {
(bool success, bytes memory result) = precompileAddress.call(
abi.encodeWithSelector(
IHederaTokenService.burnToken.selector,
token,
amount,
serialNumbers
)
);
(responseCode, newTotalSupply) = success
? abi.decode(result, (int32, int64))
: (HederaTokenService.UNKNOWN_CODE, int64(0));
}

/// 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(
IHederaTokenService.HederaToken memory token,
int64 initialTotalSupply,
int32 decimals
)
internal
nonEmptyExpiry(token)
returns (int responseCode, address tokenAddress)
{
(bool success, bytes memory result) = precompileAddress.call{
value: msg.value
}(
abi.encodeWithSelector(
IHederaTokenService.createFungibleToken.selector,
token,
initialTotalSupply,
decimals
)
);

(responseCode, tokenAddress) = success
? abi.decode(result, (int32, address))
: (HederaTokenService.UNKNOWN_CODE, address(0));
}

/// Transfers tokens where the calling account/contract is implicitly the first entry in the token transfer list,
/// where the amount is the value needed to zero balance the transfers. Regular signing rules apply for sending
/// (positive amount) or receiving (negative amount)
/// @param token The token to transfer to/from
/// @param sender The sender for the transaction
/// @param receiver The receiver of the transaction
/// @param amount Non-negative value to send. a negative value will result in a failure.
function transferToken(
address token,
address sender,
address receiver,
int64 amount
) internal returns (int responseCode) {
(bool success, bytes memory result) = precompileAddress.call(
abi.encodeWithSelector(
IHederaTokenService.transferToken.selector,
token,
sender,
receiver,
amount
)
);
responseCode = success
? abi.decode(result, (int32))
: HederaTokenService.UNKNOWN_CODE;
}

/// Operation to update token keys
/// @param token The token address
/// @param keys The token keys
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function updateTokenKeys(address token, IHederaTokenService.TokenKey[] memory keys)
internal returns (int64 responseCode){
(bool success, bytes memory result) = precompileAddress.call(
abi.encodeWithSelector(IHederaTokenService.updateTokenKeys.selector, token, keys));
(responseCode) = success ? abi.decode(result, (int32)) : HederaTokenService.UNKNOWN_CODE;
}

}
167 changes: 167 additions & 0 deletions packages/oft-hts/contracts/IHederaTokenService.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.4.9 <0.9.0;
pragma experimental ABIEncoderV2;

interface IHederaTokenService {
/// 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;
}

/**********************
* Direct HTS Calls *
**********************/

/// 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);

/// 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);

/// Transfers tokens where the calling account/contract is implicitly the first entry in the token transfer list,
/// where the amount is the value needed to zero balance the transfers. Regular signing rules apply for sending
/// (positive amount) or receiving (negative amount)
/// @param token The token to transfer to/from
/// @param sender The sender for the transaction
/// @param recipient The receiver of the transaction
/// @param amount Non-negative value to send. a negative value will result in a failure.
function transferToken(
address token,
address sender,
address recipient,
int64 amount
) external returns (int64 responseCode);

/// Operation to update token keys
/// @param token The token address
/// @param keys The token keys
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function updateTokenKeys(address token, TokenKey[] memory keys) external returns (int64 responseCode);
}
Loading
Loading