Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4b6d9d3
chore: fix lint issues
dadadave80 Aug 8, 2025
ac36312
chore: import hts fee helper as lib
dadadave80 Aug 8, 2025
1011151
chore: remove unused imports
dadadave80 Aug 8, 2025
991106a
chore: import and use fee helper lib to calculate fees
dadadave80 Aug 8, 2025
57aa622
chore: import usdc address for royalty fee fallback
dadadave80 Aug 8, 2025
c867b49
feat: associate token with protocol and user after minting
dadadave80 Aug 8, 2025
186f071
chore: check for errors in private functions
dadadave80 Aug 8, 2025
263eb89
chore: remove created status
dadadave80 Aug 8, 2025
6f319d9
chore: set for sale as the default status
dadadave80 Aug 8, 2025
66f6528
feat: start supply chain facet
dadadave80 Aug 8, 2025
79eb30c
feat: start supply chain lib
dadadave80 Aug 8, 2025
65252db
chore: SafeHTS Library
dadadave80 Aug 8, 2025
e7f2bf8
chore(forge-lint): ignore mixed case function
dadadave80 Aug 8, 2025
72af206
docs: hts diamond libs description
dadadave80 Aug 8, 2025
7f70119
chore: add update token and transfer from functions
dadadave80 Aug 8, 2025
65b29d2
chore: rename Product status and update product struct
dadadave80 Aug 8, 2025
c825f81
chore: use custom error
dadadave80 Aug 8, 2025
f29a8eb
chore: add custom errors
dadadave80 Aug 8, 2025
514f210
chore: SafeViewHTS lib
dadadave80 Aug 8, 2025
e641752
chore: update product logs
dadadave80 Aug 8, 2025
0c9a2d0
chore: default to none
dadadave80 Aug 8, 2025
90c7b06
feat: supply chain lib
dadadave80 Aug 8, 2025
2c775a1
chore use safe hts lib and update logic
dadadave80 Aug 8, 2025
282ad2e
chore: update functions to match lib
dadadave80 Aug 8, 2025
0640be3
feat: supply chain facet
dadadave80 Aug 8, 2025
3d63a98
feat: supply chain storage
dadadave80 Aug 8, 2025
e3c5f85
Merge branch 'main' into feat/supply-chain-logic
dadadave80 Aug 8, 2025
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
113 changes: 113 additions & 0 deletions HTS-LIBS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# hedera-diamond-hts-lib

_Seamless Hedera Token Service (HTS) integration for EIP-2535 Diamond Standard smart contracts._

---

## Overview

**hedera-diamond-hts-lib** solves the challenge of using Hedera's non-EVM HTS precompiles and abstract contracts (like `IHederaTokenService`) within modular, upgradeable Diamond (EIP-2535) architectures. Instead of inheriting abstract contracts (which is incompatible with delegatecall-based facets), this library provides gas-optimized, composable `library` wrappers for all core HTS operations.

These libraries enable facets in a Diamond proxy to perform mint, burn, transfer, associate, dissociate, freeze, KYC, and other token operations directly, using delegatecall-compatible stateless functions. This unlocks full HTS utility in modular, upgradable dApps.

---

## Features

- **Modular HTS Operations:** Each HTS function (mint, burn, transfer, etc.) is available as a standalone library function.
- **Diamond Facet Compatible:** Designed for use inside EIP-2535 facets via delegatecall, with no stateful logic.
- **Full HTS Coverage:** Supports association, dissociation, KYC, freeze, custom fees, NFT and fungible operations, and more.
- **Lightweight & Composable:** No inheritance, minimal overhead, easily composed with other libraries and storage patterns.

---

## Installation

### Prerequisites
- Foundry or hardhat project

### Add to Your Project

#### Option 1: Manual Copy
Copy the `.sol` files from `src/libraries/hts/` into your project's libraries directory.

#### Option 2: forge install
```sh
forge install dadadave80/chronicle
```

---

## Usage Example

Import and use in a facet contract:

```solidity
import {LibHederaTokenService} from "@chronicle/libraries/hts/LibHederaTokenService.sol";

contract ProductsFacet {
function mintProduct(address token, int64 amount) external {
// Calls the Hedera Token Service mint via precompile
(int256 code, int64 newSupply, int64[] memory serials) = LibHederaTokenService.mintToken(token, amount, new bytes[](0));
require(code == 22, "Mint failed"); // 22 = SUCCESS
}
}
```

**Best Practices:**
- Use with [LibDiamond](https://eips.ethereum.org/EIPS/eip-2535) storage patterns—never store state in libraries.
- Use `using LibHederaTokenService for address;` for more ergonomic syntax.
- Combine with access control and event logging as needed.

---

## Project Structure

- `LibHederaTokenService.sol` — Core stateless wrappers for all HTS precompile operations (mint, burn, transfer, associate, etc.)
- `LibSafeHTS.sol` — Revert-on-failure safe wrappers for all HTS calls (throws on non-SUCCESS response).
- `LibFeeHelper.sol` — Utilities for constructing custom fee structs for HTS tokens.
- `LibKeyHelper.sol` — Utilities for managing HTS key types and key assignment.

---

## Limitations / Considerations

- **Delegatecall Overhead:** Calls from facets via delegatecall are slightly more expensive than direct contract calls.
- **No Library State:** All logic must be stateless; use Diamond storage patterns for persistent data.
- **HTS Compatibility:** Only works on Hedera-compatible EVM chains with HTS precompiles available at `0x167`.
- **Error Handling:** Use `LibSafeHTS` for automatic revert on failure, or handle response codes manually with `LibHederaTokenService`.

---

## Testing

- **Unit Tests:**
- Hardhat: `npx hardhat test`
- Foundry: `forge test`
- **Coverage:**
- Hardhat: `npx hardhat coverage`
- Foundry: `forge coverage`

Tests cover all core HTS operations, including edge cases and error handling.

---

## Contributing

- Fork and submit PRs for new HTS methods, optimizations, or bug fixes.
- Adhere to Solidity style guidelines and include unit tests for new features.
- Open issues for feature requests or HTS compatibility questions.

---

## License

MIT

---

## References

- [Hedera Token Service Documentation](https://docs.hedera.com/hedera/smart-contracts/hedera-token-service)
- [EIP-2535 Diamond Standard](https://eips.ethereum.org/EIPS/eip-2535)
- [Hedera Token Service Solidity Interfaces](https://github.com/hashgraph/hedera-smart-contracts)
34 changes: 22 additions & 12 deletions src/facets/ProductsFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,32 @@ import {Product} from "@chronicle-types/ProductStorage.sol";
contract ProductsFacet {
using LibProduct for *;

function addProduct(string calldata _name, string calldata _memo, int64 _price, int64 _initialSupply) external {
_name._addProduct(_memo, _price, _initialSupply);
function addProduct(
string calldata _name,
string calldata _memo,
int64 _price,
int64 _transporterFee,
int64 _initialSupply
) external {
_name._addProduct(_memo, _price, _transporterFee, _initialSupply);
}

function updateProduct(address _tokenAddress, string calldata _name, string calldata _memo, int64 _price)
external
{
_tokenAddress._updateProduct(_name, _memo, _price);
function updateProduct(
address _tokenAddress,
string calldata _name,
string calldata _memo,
int64 _price,
int64 _transporterFee
) external {
_tokenAddress._updateProduct(_name, _memo, _price, _transporterFee);
}

function increaseProductQuantity(address _tokenAddress, int64 _quantity) external {
_tokenAddress._increaseProductQuantity(_quantity);
}

function decreaseProductQuantity(address _tokenAddress, int64 _quantity, int64[] memory _serialNumbers) external {
_tokenAddress._decreaseProductQuantity(_quantity, _serialNumbers);
function decreaseProductQuantity(address _tokenAddress, int64 _quantity) external {
_tokenAddress._decreaseProductQuantity(_quantity);
}

function getProductByTokenAddress(address _tokenAddress) public view returns (Product memory) {
Expand All @@ -45,11 +55,11 @@ contract ProductsFacet {
return LibProduct._getProductsByRange(_start, _end);
}

function getSupplierProductTokenAddresses(address _owner) public view returns (address[] memory) {
return _owner._getSupplierProductTokenAddresses();
function getSupplierProductTokenAddresses(address _supplier) public view returns (address[] memory) {
return _supplier._getSupplierProductTokenAddresses();
}

function getSupplierProducts(address _owner) public view returns (Product[] memory) {
return _owner._getSupplierProducts();
function getSupplierProducts(address _supplier) public view returns (Product[] memory) {
return _supplier._getSupplierProducts();
}
}
62 changes: 62 additions & 0 deletions src/facets/SupplyChainFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import {LibSupplyChain} from "@chronicle/libraries/LibSupplyChain.sol";
import {SupplyChainStatus} from "@chronicle-types/SupplyChainStorage.sol";
import {Product} from "@chronicle-types/ProductStorage.sol";

contract SupplyChainFacet {
using LibSupplyChain for address;

function retailerOrderProduct(address _productToken, int64 _quantity) external payable {
_productToken._retailerOrderProduct(_quantity);
}

function transporterSelectOrder(address _productToken, int64 _quantity) external payable {
_productToken._transporterSelectOrder(_quantity);
}

function transporterUpdateStatus(address _productToken, SupplyChainStatus _status) external {
_productToken._transporterUpdateStatus(_status);
}

function retailerReceiveProduct(address _productToken, int64 _quantity) external payable {
_productToken._retailerReceiveProduct(_quantity);
}

function getActiveDeliveries() external view returns (address[] memory) {
return LibSupplyChain._getAllActiveDeliveriesAddress();
}

function getAllActiveDeliveries() external view returns (Product[] memory) {
return LibSupplyChain._getAllActiveDeliveries();
}

function getRetailerOrders(address _retailer) external view returns (address[] memory) {
return LibSupplyChain._getRetailerOrders(_retailer);
}

function getTransporterOrders(address _transporter) external view returns (address[] memory) {
return LibSupplyChain._getTransporterOrders(_transporter);
}

function getSupplierOrders(address _supplier) external view returns (address[] memory) {
return LibSupplyChain._getSupplierOrders(_supplier);
}

function getProductRetailers(address _productToken) external view returns (address[] memory) {
return LibSupplyChain._getProductRetailers(_productToken);
}

function getProductTransporter(address _productToken) external view returns (address) {
return LibSupplyChain._getProductTransporter(_productToken);
}

function getProductSuppliers(address _productToken) external view returns (address[] memory) {
return LibSupplyChain._getProductSuppliers(_productToken);
}

function getProductSupplyChainStatus(address _productToken) external view returns (SupplyChainStatus) {
return LibSupplyChain._getProductSupplyChainStatus(_productToken);
}
}
13 changes: 8 additions & 5 deletions src/libraries/LibParty.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
} from "@chronicle-types/PartyStorage.sol";
import {LibContext} from "@chronicle/libraries/LibContext.sol";
import {LibOwnableRoles} from "@diamond/libraries/LibOwnableRoles.sol";
/// forge-lint: disable-next-line(unaliased-plain-import)
import "@chronicle-logs/PartyLogs.sol";
/// forge-lint: disable-next-line(unaliased-plain-import)
import "@chronicle/libraries/errors/PartyErrors.sol";

library LibParty {
using EnumerableSet for EnumerableSet.AddressSet;
Expand All @@ -27,9 +30,9 @@ library LibParty {
function _registerParty(string calldata _name, Role _role) internal {
PartyStorage storage $ = _partyStorage();
address sender = LibContext._msgSender();
if ($.parties[sender].frozen) revert("Party frozen");
if (!$.roles[_role].add(sender)) revert("Role already exists");
if (!$.activeParties.add(sender)) revert("Party already exists");
if ($.parties[sender].frozen) revert PartyFrozen(sender);
if (!$.roles[_role].add(sender)) revert RoleAlreadyExists(_role);
if (!$.activeParties.add(sender)) revert PartyAlreadyExists(sender);
Party memory party =
Party({name: _name, addr: sender, role: _role, active: true, frozen: false, rating: Rating.Zero});
$.parties[sender] = party;
Expand All @@ -39,8 +42,8 @@ library LibParty {
function _deactivateParty(Role _role) internal {
PartyStorage storage $ = _partyStorage();
address sender = LibContext._msgSender();
if (!$.activeParties.remove(sender)) revert("Party not active");
if (!$.roles[_role].remove(sender)) revert("Role not found");
if (!$.activeParties.remove(sender)) revert PartyNotActive(sender);
if (!$.roles[_role].remove(sender)) revert RoleNotFound(_role);
delete $.parties[sender].active;
emit PartyDeactivated($.parties[sender]);
}
Expand Down
Loading
Loading