Extensibility
The Module/Contract Pattern
We use the term modular composition by delegation to describe the practice of having contracts call into module-defined circuits to implement behavior. Rather than inheriting or overriding functionality, a contract delegates responsibility to the module by explicitly invoking its exported circuits.
The idea is that there are two types of compact files: modules and contracts. To minimize risk, boilerplate, and avoid naming clashes, we follow these rules:
Modules
Modules expose functionality through three circuit types:
internal
: private helpers → used to break up logic within the module.public
: composable building blocks → intended for contracts to use in complex flows (_mint
,_burn
).external
: standalone circuits → safe to expose as-is (transfer
,approve
).
Modules must:
- Export only
public
andexternal
circuits. - Prefix
public
circuits with_
(e.g.,FungibleToken._mint
). - Avoid
_
prefix forexternal
circuits (e.g.,FungibleToken.transfer
). - Avoid defining or calling constructors or
initialize()
directly. - Optionally define an
initialize()
circuit for internal setup—but execution must be delegated to the contract.
Note: Compact files must contain only one top-level module and all logic must be defined inside the module declaration.
Contracts
Contracts compose behavior by explicitly invoking the relevant circuits from imported modules. Therefore, contracts:
- Can import from modules.
- Should add prefix to imports (
import "FungibleToken" prefix FungibleToken_;
). - Should re-expose external module circuits through wrapper circuits to control naming and layering. Avoid raw re-exports to prevent name clashes.
- Should implement constructor that calls
initialize
from imported modules. - Must not call initializers outside of the constructor.
This pattern balances modularity with local control, avoids tight coupling, and works within Compact’s language constraints. As Compact matures, this pattern will likely evolve as well.
Example contract implementing modules
/** FungibleTokenMintablePausableOwnableContract */
pragma language_version >= 0.16.0;
import CompactStandardLibrary;
import FungibleToken prefix FungibleToken_;
import Pausable prefix Pausable_;
import Ownable prefix Ownable_;
constructor(
_name: Maybe<Opaque<"string">>,
_symbol: Maybe<Opaque<"string">>,
_decimals:Uint<8>,
_owner: Either<ZswapCoinPublicKey, ContractAddress>
)
FungibleToken_initialize(_name, _symbol, _decimals);
Ownable_initialize(_owner);
/** IFungibleTokenMetadata */
export circuit name(): Opaque<"string">
return FungibleToken_name();
export circuit symbol(): Opaque<"string">
return FungibleToken_symbol();
export circuit decimals(): Uint<8>
return FungibleToken_decimals();
/** IFungibleToken */
export circuit totalSupply(): Uint<128>
return FungibleToken_totalSupply();
export circuit balanceOf(
account: Either<ZswapCoinPublicKey, ContractAddress>
): Uint<128>
return FungibleToken_balanceOf(account);
export circuit allowance(
owner: Either<ZswapCoinPublicKey, ContractAddress>,
spender: Either<ZswapCoinPublicKey, ContractAddress>
): Uint<128>
return FungibleToken_allowance(owner, spender);
export circuit transfer(
to: Either<ZswapCoinPublicKey, ContractAddress>,
value: Uint<128>
): Boolean
Pausable_assertNotPaused();
return FungibleToken_transfer(to, value);
export circuit transferFrom(
from: Either<ZswapCoinPublicKey, ContractAddress>,
to: Either<ZswapCoinPublicKey, ContractAddress>,
value: Uint<128>
): Boolean
Pausable_assertNotPaused();
return FungibleToken_transferFrom(from, to, value);
export circuit approve(
spender: Either<ZswapCoinPublicKey, ContractAddress>,
value: Uint<128>
): Boolean
Pausable_assertNotPaused();
return FungibleToken_approve(spender, value);
/** IMintable */
export circuit mint(
account: Either<ZswapCoinPublicKey, ContractAddress>,
value: Uint<128>
): []
Pausable_assertNotPaused();
Ownable_assertOnlyOwner();
return FungibleToken__mint(account, value);
/** IPausable */
export circuit isPaused(): Boolean
return Pausable_isPaused();
export circuit pause(): []
Ownable_assertOnlyOwner();
return Pausable__pause();
export circuit unpause(): []
Ownable_assertOnlyOwner();
return Pausable__unpause();
/** IOwnable */
export circuit owner(): Either<ZswapCoinPublicKey, ContractAddress>
return Ownable_owner();
export circuit transferOwnership(
newOwner: Either<ZswapCoinPublicKey, ContractAddress>
): []
return Ownable_transferOwnership(newOwner);
export circuit renounceOwnership(): []
return Ownable_renounceOwnership();