@hashgraph/asset-tokenization-contracts
v8.0.0
Published
<div align="center">
Readme
Asset Tokenization Studio - Contracts
Table of Contents
- Description
- Quick Start
- Deployment & Tasks
- Using ATS Deployment Utilities in Downstream Projects
- Test
- Architecture
- ERC-3643 Compatibility
- ⚠️ Force-Cancel Functions — HIGH RISK
Description
The contracts module contains the code of all Solidity smart contracts deployed on Hedera. This package is part of the Asset Tokenization Studio monorepo.
Standards:
- ERC-1400 for security tokens
- Partial ERC-3643 (T-REX) compatibility (v1.15.0+)
Location: packages/ats/contracts within the monorepo
Quick Start
Installation
From the monorepo root:
npm ci # Install all workspace dependencies
npm run ats:contracts:build # Build the contractsFor local development:
cd packages/ats/contracts
npm install
npm run compileBuild
# From monorepo root
npm run ats:contracts:build
# Or build all ATS components
npm run ats:build
# Force recompile
npm run compile:forceERC-3643 compatibility
| function | status | | ---------------------------------------------------------------------------------------------------------------------- | ---------- | | onchainID() external view returns (address) | Done | | version() external view returns (string memory) | Done | | identityRegistry() external view returns (IIdentityRegistry) | Done | | compliance() external view returns (ICompliance) | Done | | paused() external view returns (bool) | Done | | isFrozen(address _userAddress) external view returns (bool) | Done | | getFrozenTokens(address _userAddress) external view returns (uint256) | Done | | setName(string calldata _name) external | Done | | setSymbol(string calldata _symbol) external | Done | | setOnchainID(address _onchainID) external | Done | | pause() external | Done | | unpause() external | Done | | setAddressFrozen(address _userAddress, bool _freeze) external | Done | | freezePartialTokens(address _userAddress, uint256 _amount) external | Done | | unfreezePartialTokens(address _userAddress, uint256 _amount) external | Done | | setIdentityRegistry(address _identityRegistry) external | Done | | setCompliance(address _compliance) external | Done | | forcedTransfer(address _from, address _to, uint256 _amount) external returns (bool) | Done | | mint(address _to, uint256 _amount) external | Done | | burn(address _userAddress, uint256 _amount) external | Done | | recoveryAddress(address _lostWallet, address _newWallet, address _investorOnchainID) external returns (bool) | Done | | batchTransfer(address[] calldata _toList, uint256[] calldata _amounts) external | Done | | batchForcedTransfer(address[] calldata _fromList, address[] calldata _toList, uint256[] calldata _amounts) external | Done | | batchMint(address[] calldata _toList, uint256[] calldata _amounts) external | Done | | batchBurn(address[] calldata _userAddresses, uint256[] calldata _amounts) external | Done | | batchSetAddressFrozen(address[] calldata _userAddresses, bool[] calldata _freeze) external | Done | | batchFreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external | Done | | batchUnfreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external | Done |
Deployment & Tasks
For complete documentation on deployment, tasks, and Hardhat commands, see Scripts README.
The Scripts README contains comprehensive information about:
- 🚀 Deployment workflows - Full system deployment, individual components, network configuration
- 📋 Hardhat tasks - All available tasks with parameters and examples
- 🏗️ Architecture - Framework-agnostic design, domain separation, registry system
- 📚 API Reference - TypeScript APIs for programmatic deployment
- 🔧 Troubleshooting - Common issues and solutions
- 💡 Developer guides - Adding facets, creating asset types
Quick deployment commands:
# Deploy full system to Hardhat network (in-memory, fast)
npm run deploy:hardhat -- --network hardhat
# Deploy to Hedera Testnet (requires .env configuration)
npm run deploy:hardhat -- --network hedera-testnet
# Standalone deployment (~3x faster startup)
npm run deployDeployment Failures & Recovery
If deployment fails or is interrupted, the checkpoint system automatically saves your progress:
# Resume from where it failed
npm run deploy:newBlrThe checkpoint system:
- ✅ Automatically resumes from failures
- ✅ Skips completed steps (saves time and gas)
- ✅ Allows safe interruption (Ctrl+C)
- ✅ Provides detailed failure diagnostics
Checkpoint management:
# List all checkpoints
npm run checkpoint:list -- hedera-testnet
# Show failure details
npm run checkpoint:show -- <checkpoint-id>
# Clean up old checkpoints
npm run checkpoint:cleanup -- hedera-testnet 30For comprehensive checkpoint documentation including troubleshooting, scenarios, and best practices, see the Checkpoint Guide.
Using ATS Deployment Utilities in Downstream Projects
The ATS contracts package exports framework-agnostic deployment file management utilities that can be used by downstream projects (like GBP). These utilities provide standardized file organization, type-safe operations, and zero runtime dependencies on Hardhat or ethers.
Installation
npm install @hashgraph/asset-tokenization-contractsBasic Usage
import {
saveDeploymentOutput,
loadDeployment,
findLatestDeployment,
type SaveDeploymentOptions,
type AtsWorkflowType,
} from "@hashgraph/asset-tokenization-contracts/scripts";
// Save deployment output
const result = await saveDeploymentOutput({
network: "hedera-testnet",
workflow: "newBlr",
data: deploymentOutput,
});
if (result.success) {
console.log(`Saved to: ${result.filepath}`);
// Output: deployments/hedera-testnet/newBlr-2025-12-30T10-30-45.json
}
// Load specific deployment
const deployment = await loadDeployment("hedera-testnet", "newBlr", "2025-12-30T10-30-45");
// Find latest deployment for workflow
const latest = await findLatestDeployment("hedera-testnet", "newBlr");Custom Workflows (Downstream Extension)
Downstream projects can extend ATS workflows with custom types:
import {
saveDeploymentOutput,
registerWorkflowDescriptor,
type AtsWorkflowType,
isSaveSuccess,
} from "@hashgraph/asset-tokenization-contracts/scripts";
// Define custom workflow types
type GbpWorkflowType = AtsWorkflowType | "gbpInfrastructure" | "gbpUpgrade";
// Define custom deployment output types
interface GbpInfrastructureOutput {
timestamp: string;
network: string;
deployer: string;
callableContracts: {
primaryMarketFactory: { address: string; contractId?: string };
bondFactory: { address: string; contractId?: string };
};
summary: {
totalContracts: number;
deploymentTime: number;
success: boolean;
};
}
// Register custom descriptors (optional, for shorter filenames)
registerWorkflowDescriptor("gbpInfrastructure", "gbpInfra");
registerWorkflowDescriptor("gbpUpgrade");
// Use custom workflows with custom output types
const gbpDeploymentOutput: GbpInfrastructureOutput = {
timestamp: new Date().toISOString(),
network: "hedera-testnet",
deployer: "0x...",
callableContracts: {
primaryMarketFactory: { address: "0x...", contractId: "0.0.123" },
bondFactory: { address: "0x...", contractId: "0.0.456" },
},
summary: {
totalContracts: 2,
deploymentTime: 45000,
success: true,
},
};
const result = await saveDeploymentOutput({
network: "hedera-testnet",
workflow: "gbpInfrastructure", // No type assertion needed!
data: gbpDeploymentOutput,
});
// Type-safe result handling
if (isSaveSuccess(result)) {
console.log(`Saved: ${result.filename}`);
// Output: gbpInfra-2025-12-30T16-45-30.json
}Available Utilities
Save Operations
saveDeploymentOutput(options)- Save deployment output with type-safe resultsregisterWorkflowDescriptor(workflow, descriptor?)- Register custom workflow names
Load Operations
loadDeployment(network, workflow, timestamp)- Load specific deploymentfindLatestDeployment(network, workflow)- Find most recent deploymentlistDeploymentsByWorkflow(network, workflow?)- List deployments by workflow
Helper Utilities
getNetworkDeploymentDir(network)- Get network deployment directory pathgenerateDeploymentFilename(workflow, timestamp?)- Generate standardized filenamegetDeploymentsDir()- Get root deployments directory
Type Guards
isSaveSuccess(result)- Type guard for successful savesisSaveFailure(result)- Type guard for failed savesisAtsWorkflow(workflow)- Check if workflow is core ATS workflow
File Structure
Deployments are organized by network subdirectories:
deployments/
├── hedera-testnet/
│ ├── newBlr-2025-12-29T15-22-54.json
│ ├── upgradeConfigurations-2025-12-29T16-30-12.json
│ └── gbpInfra-2025-12-29T17-15-45.json # Custom workflow
└── hedera-mainnet/
└── newBlr-2025-12-28T10-45-33.jsonType Safety
All deployment utilities are fully typed with TypeScript:
import type {
SaveResult,
SaveDeploymentOptions,
LoadDeploymentOptions,
AnyDeploymentOutput,
DeploymentOutputType,
WorkflowType,
AtsWorkflowType,
} from "@hashgraph/asset-tokenization-contracts/scripts";Key Features
✅ Framework-Agnostic - Zero Hardhat/ethers runtime dependencies ✅ Type-Safe - Full TypeScript support with discriminated unions ✅ Extensible - Support for custom workflow types ✅ Well-Tested - Comprehensive unit tests with cross-platform coverage ✅ Organized - Network subdirectories for clean structure ✅ Flexible - Optional custom paths and descriptors
For complete API documentation, see the Scripts README.
Test
The tests are organized into two main categories:
- Contract Tests (
test/contracts/) - Unit tests for Solidity smart contracts - Scripts Tests (
test/scripts/) - Unit and integration tests for TypeScript deployment scripts
Test Structure
test/
├── contracts/
│ ├── unit/ # Contract unit tests (npm test, test:parallel, coverage)
│ └── demo/ # Demo tests (test:demo - explicit only)
│
└── scripts/
├── unit/ # Script unit tests (utilities, infrastructure)
└── integration/ # Script integration tests (deployment, registry operations)Running tests
From monorepo root (recommended):
npm run ats:contracts:testFrom contracts directory:
cd packages/ats/contracts
npm test # Runs contract unit tests only
npm run test:parallel # Runs contract unit tests in parallel
npm run test:scripts # Runs all script testsAvailable test commands:
# Contract Tests
npm test # Contract unit tests only
npm run test:parallel # Contract unit tests (parallel execution)
npm run test:coverage # Contract test coverage
npm run test:coverage:layer1 # Layer 1 coverage
npm run test:factory # Factory tests
npm run test:resolver # Resolver tests
# Script Tests
npm run test:scripts # All script tests
npm run test:scripts:unit # Script unit tests (utilities, infrastructure)
npm run test:scripts:integration # Script integration tests (deployment, registry)
# Demo Tests (explicit only, not included in npm test)
npm run test:demo # Demo tests
npm run test:demo:hedera # Hedera-specific demo testsArchitecture
The ATS contracts implement a 4-layer hierarchical design using the Diamond Pattern (EIP-2535) for maximum upgradeability and modularity.
System Overview
┌─────────────────────────────────────────┐
│ ProxyAdmin │
│ (Manages proxy upgrades) │
└─────────────────────────────────────────┘
│
┌─────────┴─────────┐
│ │
┌───────▼──────────┐ ┌────▼────────────┐
│ BLR Proxy │ │ Factory Proxy │
│ (Facet Registry) │ │ (Token Creator) │
└───────┬──────────┘ └─────────────────┘
│
├─ Business Logic Resolver (BLR)
│ ├─ Facet version management
│ ├─ Configuration management
│ └─ Resolver key → address mapping
│
├─ 46+ Facets (Layers 0-3)
│ ├─ Layer 0: Storage wrappers
│ ├─ Layer 1: Core business logic
│ ├─ Layer 2: Domain features
│ └─ Layer 3: Jurisdiction-specific
│
└─ 2 Configurations
├─ Equity Config (43 facets)
└─ Bond Config (43 facets)Four-Layer Architecture
Layer 0: Storage Wrappers
- Data structures and storage management
- Examples:
ERC1400StorageWrapper,KycStorageWrapper,CapStorageWrapper - Storage isolation per feature for upgradeability
- EIP-1967 storage pattern
Layer 1: Core Business Logic
- ERC-1400/ERC-3643 base implementations
Common.solprovides shared logic for all facets- Access control, validation, and core operations
- Domains: AccessControl, Freeze, Hold, ControlList, CorporateActions
Layer 2: Domain-Specific Features (Facets)
- Bond: Coupon payments, maturity redemption (
Bond.sol,BondRead.sol) - Equity: Dividends, voting, balance adjustments (
Equity.sol) - Scheduled Tasks: Snapshots, balance adjustments, cross-ordered tasks
- Proceed Recipients: Payment distribution logic
- Each facet is independently upgradeable
Layer 3: Jurisdiction-Specific Implementations
- USA-specific features:
bondUSA/,equityUSA/ - Specialized compliance rules per jurisdiction
- Extends Layer 2 features with regulatory requirements
Key Components
Business Logic Resolver (BLR)
- Central registry mapping Business Logic Keys (BLK) to versioned facet addresses
- Manages global version counter across all facets
- Provides configuration management for token types
- Location:
contracts/infrastructure/diamond/BusinessLogicResolver.sol
Diamond Proxy (ResolverProxy)
- EIP-2535 compliant proxy routing function calls to appropriate facets
- Each token is a proxy instance
- Routes via BLR resolution
- Location:
contracts/infrastructure/proxy/ResolverProxy.sol
TREXFactory
- Factory pattern for deploying complete token ecosystems
- Creates tokens with specific configurations (Equity/Bond)
- Handles initialization of all required facets
- Location:
contracts/factory/ERC3643/TREXFactory.sol
Core Facet Categories
ERC1400 Token Standard Facets:
ERC1410ManagementFacet: Token partition managementERC1410ReadFacet: Read-only token state queriesERC1410TokenHolderFacet: Token holder operationsERC20Facet: ERC20 compatibility layerMintFacet: Security token issuanceBurnFacet: Security token redemptionERC1644Facet: Controller operations for forced transfers
ERC3643 (T-REX) Compliance Facets:
ERC3643ManagementFacet: Core operations (mint, burn, forced transfers)ERC3643OperationsFacet: Transfer and compliance operationsERC3643ReadFacet: State queriesERC3643BatchFacet: Gas-efficient bulk operationsFreezeFacet: Partial and full address freezing
Hold & Clearing Facets:
HoldManagementFacet: Hold creation and managementHoldReadFacet: Hold state queriesHoldTokenHolderFacet: Token holder hold operationsClearingHoldCreationFacet: Clearing-specific holdsClearingTransferFacet: Clearing transfersClearingRedeemFacet: Clearing redemptionsClearingActionsFacet: Operation approvalsClearingReadFacet: State queries
Design Patterns
Diamond Pattern Implementation:
- Facets share storage via inheritance
- Function selector routing via fallback
- Versioned facet upgrades
- Configuration-based facet composition
Proxy Pattern:
- Transparent upgradeable proxies (OpenZeppelin)
- ProxyAdmin for upgrade management
- Separate implementation and proxy contracts
Registry Pattern:
- Resolver keys map to facet implementations
- Version management for safe upgrades
- Configuration snapshots for token types
Documentation
For comprehensive architecture documentation and tutorials, see the ATS Developer Guides.
Additional resources:
Security Roles
The platform implements a comprehensive role-based access control system:
Administrative Roles
- Admin Role (
DEFAULT_ADMIN_ROLE): Full administrative control over the security token. Also authorises high-impact instant operations on the Diamond proxy (updateResolver,updateConfig,updateConfigVersion), which take effect in a single transaction with no on-chain timelock or user exit window. This role MUST be held by a multisig or governance contract — never an EOA in production — whose own approval workflow supplies the delay, review, and accountability surface for those actions. - T-REX Owner: Owner of ERC3643 tokens with special privileges for compliance configuration
- Diamond Owner: Contract upgrade and facet management permissions
Operational Roles
- Agent: Can perform mint, burn, and forced transfer operations
- Freeze Manager: Can freeze/unfreeze tokens and addresses
- Controller: Can execute controller transfers and redemptions
- Minter: Can mint new tokens (legacy role, use Agent for ERC3643)
- Locker: Can lock tokens for specified periods
- Control List Manager: Manages whitelist/blacklist entries
- KYC Manager: Manages KYC status for investors
- SSI Manager: Manages self-sovereign identity configurations
- Pause Manager: Can pause/unpause token operations
- Snapshot Manager: Can create token balance snapshots
- Corporate Actions Manager: Can execute dividends, voting rights, etc.
Adding a new facet
For detailed instructions on adding or removing facets, see the Developer Guide in the Scripts documentation.
Reference Deployment (Hedera Testnet)
Note: These contracts were deployed for reference purposes and may not reflect the latest version. For up-to-date deployments, use the deployment scripts with the current codebase version (v1.17.0+). See Scripts README for deployment instructions.
- Network: Hedera Testnet
- Status: Reference deployment (may be outdated)
- Last Known Update: Prior to v1.17.0
Contract Addresses
- ProxyAdmin: 0xE5ebB0990c841857fe43D6e0A8375F2991b265c0
- BLR Proxy: 0xE13eFc5f5d8252958cA787a1F6665C63Fbd02A48
- Factory Proxy: 0x0BC59c70933DA04C8556259BB8E78AbF7db4dC22
🔐 Role Definitions by Layer
This project follows a layered smart contract architecture with role-based access control using AccessControl. Roles are defined in three distinct layers to separate responsibilities and permissions.
🟦 Layer 0:
bytes32 constant _DEFAULT_ADMIN_ROLE = 0x00;
bytes32 constant _CONTROL_LIST_ROLE = 0xca537e1c88c9f52dc5692c96c482841c3bea25aafc5f3bfe96f645b5f800cac3;
bytes32 constant _CORPORATE_ACTION_ROLE = 0x8a139eeb747b9809192ae3de1b88acfd2568c15241a5c4f85db0443a536d77d6;
bytes32 constant _ISSUER_ROLE = 0x4be32e8849414d19186807008dabd451c1d87dae5f8e22f32f5ce94d486da842;
bytes32 constant _DOCUMENTER_ROLE = 0x83ace103a76d3729b4ba1350ad27522bbcda9a1a589d1e5091f443e76abccf41;
bytes32 constant _CONTROLLER_ROLE = 0xa72964c08512ad29f46841ce735cff038789243c2b506a89163cc99f76d06c0f;
bytes32 constant _PAUSER_ROLE = 0x6f65556918c1422809d0d567462eafeb371be30159d74b38ac958dc58864faeb;
bytes32 constant _CAP_ROLE = 0xb60cac52541732a1020ce6841bc7449e99ed73090af03b50911c75d631476571;
bytes32 constant _SNAPSHOT_ROLE = 0x3fbb44760c0954eea3f6cb9f1f210568f5ae959dcbbef66e72f749dbaa7cc2da;
bytes32 constant _LOCKER_ROLE = 0xd8aa8c6f92fe8ac3f3c0f88216e25f7c08b3a6c374b4452a04d200c29786ce88;
bytes32 constant _BOND_MANAGER_ROLE = 0x8e99f55d84328dd46dd7790df91f368b44ea448d246199c88b97896b3f83f65d;
bytes32 constant _PROTECTED_PARTITIONS_ROLE = 0x8e359333991af626d1f6087d9bc57221ef1207a053860aaa78b7609c2c8f96b6;
bytes32 constant _PROTECTED_PARTITIONS_PARTICIPANT_ROLE = 0xdaba153046c65d49da6a7597abc24374aa681e3eee7004426ca6185b3927a3f5;
bytes32 constant _WILD_CARD_ROLE = 0x96658f163b67573bbf1e3f9e9330b199b3ac2f6ec0139ea95f622e20a5df2f46;
bytes32 constant _AGENT_ROLE = 0xc4aed0454da9bde6defa5baf93bb49d4690626fc243d138104e12d1def783ea6;🟨 Layer 1:
bytes32 constant _DEFAULT_ADMIN_ROLE = 0x00;
bytes32 constant _SSI_MANAGER_ROLE = 0x0995a089e16ba792fdf9ec5a4235cba5445a9fb250d6e96224c586678b81ebd0;
bytes32 constant _KYC_ROLE = 0x6fbd421e041603fa367357d79ffc3b2f9fd37a6fc4eec661aa5537a9ae75f93d;
bytes32 constant _CLEARING_ROLE = 0x2292383e7bb988fb281e5195ab88da11e62fec74cf43e8685cff613d6b906450;
bytes32 constant _CLEARING_VALIDATOR_ROLE = 0x7b688898673e16c47810f5da9ce1262a3d7d022dfe27c8ff9305371cd435c619;
bytes32 constant _PAUSE_MANAGER_ROLE = 0xbc36fbd776e95c4811506a63b650c876b4159cb152d827a5f717968b67c69b84;
bytes32 constant _CONTROL_LIST_MANAGER_ROLE = 0x0e625647b832ec7d4146c12550c31c065b71e0a698095568fd8320dd2aa72e75;
bytes32 constant _KYC_MANAGER_ROLE = 0x8ebae577938c1afa7fb3dc7b06459c79c86ffd2ac9805b6da92ee4cbbf080449;
bytes32 constant _INTERNAL_KYC_MANAGER_ROLE = 0x3916c5c9e68488134c2ee70660332559707c133d0a295a25971da4085441522e;
bytes32 constant _FREEZE_MANAGER_ROLE = 0xd0e5294c1fc630933e135c5b668c5d577576754d33964d700bbbcdbfd7e1361b;
bytes32 constant _MATURITY_REDEEMER_ROLE = 0xa0d696902e9ed231892dc96649f0c62b808a1cb9dd1269e78e0adc1cc4b8358c;🟩 Layer 2:
bytes32 constant _ADJUSTMENT_BALANCE_ROLE = 0x6d0d63b623e69df3a6ea8aebd01f360a0250a880cbc44f7f10c49726a80a78a9;🧩 Notes:
- All roles are
bytes32constants derived using:keccak256("security.token.standard.role.<roleName>")(replace<roleName>with the actual role string)
⚠️ Force-Cancel Functions — HIGH RISK
🚨 THIS SYSTEM DOES NOT PERFORM ROLLBACKS
Force cancel sets a disabled flag. It does not undo any on-chain state that was already written. Balances, snapshots, and coupon listings that executed before the cancel are permanent. If you call force cancel after execution has already occurred, your token will be in an inconsistent state with no recovery path other than a full token migration.
How the task queue works
When a scheduled task fails, the entire triggering transaction reverts and the queue blocks at that task. Because the execution date has already passed by the time the block is discovered, the standard cancel* functions refuse to act (they enforce a date guard and will revert). forceCancel* bypasses that guard and is the only way to unblock the queue.
The right time to call force cancel is before the blocked task has actually executed. Once it has run, calling force cancel changes a status flag but cannot reverse what happened on-chain.
Cancellation functions by action type
| Action type | Normal cancel — before execution date | Force cancel — after execution date, queue blocked |
| ------------------ | -------------------------------------- | -------------------------------------------------- |
| Balance Adjustment | cancelScheduledBalanceAdjustment(id) | forceCancelScheduledBalanceAdjustment(id) |
| Dividend | cancelDividend(id) | forceCancelDividend(id) |
| Voting | cancelVoting(id) | forceCancelVoting(id) |
| Coupon | cancelCoupon(id) | forceCancelCoupon(id) |
| Amortization | cancelAmortization(id) | forceCancelAmortization(id) |
What gets permanently written per action type
Balance Adjustment — on execution, balances, total supply, max supply, and decimals are rescaled permanently. No rollback exists.
- Task blocked, not yet executed → force cancel is safe. External systems in virtual/KPI mode may have already projected the adjusted balance; cancelling creates a discrepancy for them.
- Task already executed → force cancel marks the action cancelled but the rescaling is permanent. The token state and the "cancelled" status will contradict each other.
Dividend / Voting — a snapshot is taken at the record date. The snapshot ID is stored on-chain and is not cleared by cancellation.
- Task blocked, snapshot not yet taken → force cancel is safe. The snapshot never fires.
- Snapshot already taken → force cancel only stops further steps. The snapshot and its ID remain on-chain. Notify any external system that already read it and began processing payments or vote tallies.
Amortization — same pattern as dividends, plus token hold management. Risks are identical.
Coupon — fires two on-chain operations at different times: a snapshot at the fixing date and a coupon listing (an append to the ordered payment list, with no removal mechanism).
- Task blocked, fixing date not yet passed → force cancel is safe.
- Coupon listing already fired → the coupon ID is permanently in the ordered list. Force cancel stops the execution step but the listing cannot be removed.
- Execution date already passed → snapshot taken, coupon listed, payments may already be distributed. None of these can be undone.
Rules for safe use
- Use regular
cancel*functions when possible. They include the necessary checks to ensure consistency. - Grant
ROLE_CORPORATE_ACTION_FORCE_CANCELonly to multisig accounts. Never a single EOA in any production or pre-production environment. - Document every use. Record the action ID, the reason, and the authorising signatures in your governance log. Inform all external systems that may be relying on the action's data.
📚 Documentation
For more information about the project, see the Documentation.
