Skip to main content

Deploying a Token

Tokens are always created by calling the Factory — you never deploy a ResolverProxy on its own. Each asset type has its own entry point:

  • Equity, bonds, and deposit tokens use typed Factory methods that deploy the proxy and run every facet initialiser (metadata, compliance, corporate actions, …), apply regulation data, and mark the token operational.
  • Loans and loans-portfolios have no typed method, so they're created with the generic deployProxy.

The TypeScript deploy helper for each asset lives in scripts/domain/factory/ (deployEquityToken.ts, deployDepositToken.ts, deployLoanToken.ts, deployLoansPortfolioToken.ts, …).

How each asset type is deployed​

AssetFactory methodConfig ID
EquitydeployEquity(EquityData, FactoryRegulationData)EQUITY_CONFIG_ID (1)
Bond (variable rate)deployBond(BondData, FactoryRegulationData)BOND_CONFIG_ID (2)
Bond (fixed rate)deployBond(BondData, FactoryRegulationData)BOND_FIXED_RATE_CONFIG_ID (3)
Bond (KPI-linked rate)deployBond(BondData, FactoryRegulationData)BOND_KPI_LINKED_RATE_CONFIG_ID (4)
Deposit tokendeployDepositToken(DepositTokenData, FactoryRegulationData)DEPOSIT_TOKEN_CONFIG_ID (5)
LoandeployProxy(resolver, configKey, version, rbacs, data)LOAN_CONFIG_ID (6)
Loans portfoliodeployProxy(resolver, configKey, version, rbacs, data)LOANS_PORTFOLIO_CONFIG_ID (7)

The three bond variants share one method (deployBond); the variant is selected by the configuration ID you pass in SecurityData.resolverProxyConfiguration.key — there is no separate deployBondFixedRate / deployBondKpiLinkedRate. All method and struct definitions are in factory/IFactory.sol; the config IDs are in scripts/domain/constants.ts.

Typed methods (equity, bonds, deposit token)​

Each typed method takes a per-asset data struct plus FactoryRegulationData, deploys the proxy, and fully initialises it. The input structs (in IFactory.sol) are:

  • EquityData = SecurityData + EquityDetailsData (dividend type, voting/information/liquidation/… rights, nominal value).
  • BondData = SecurityData + BondDetailsData (currency, nominal value, start & maturity dates) + proceed recipients.
  • DepositTokenData = SecurityData.

SecurityData carries the configuration shared by every asset: the resolver (the BLR), resolverProxyConfiguration (config ID + version), ERC-20 metadata, the initial rbacs, supply cap, compliance / identity-registry addresses, external pause/control/KYC lists, and the feature flags (multi-partition, controllable, whitelist, clearing, …).

import { ethers } from "hardhat";

const factory = await ethers.getContractAt("Factory", FACTORY_ADDRESS);

// equityData: IFactory.EquityData, regulationData: IFactory.FactoryRegulationData
// (see scripts/domain/factory/deployEquityToken.ts for a full, populated example)
const tx = await factory.deployEquity(equityData, regulationData);
const receipt = await tx.wait();
// the new token address is in the EquityDeployed event

deployBond and deployDepositToken follow the same shape with their own structs and the BondDeployed / DepositTokenDeployed events.

Loan & loans portfolio (via deployProxy)​

Loans and loans-portfolios have no typed Factory method, so their deploy helpers call the generic deployProxy with LOAN_CONFIG_ID / LOANS_PORTFOLIO_CONFIG_ID. See deployLoanToken.ts and deployLoansPortfolioToken.ts for the full flow.

The generic deployProxy primitive​

deployProxy deploys a ResolverProxy wired to a configuration and seeds its initial roles. It's the deploy path for loan / loans-portfolio above, and the entry point for any new configuration that doesn't yet have a typed method (see Creating an asset type).

function deployProxy(
IBusinessLogicResolver _resolver,
bytes32 _configKey,
uint256 _version,
IResolverProxy.Rbac[] memory _rbacs,
bytes calldata _data
) external returns (address proxyAddress_);
ParameterTypeDescription
_resolveraddressAddress of the BLR proxy. Must not be zero.
_configKeybytes32ID of the configuration to use.
_versionuint256Configuration version to pin (read the latest via getLatestVersionByConfiguration).
_rbacsRbac[]Initial role assignments for the token. At least one admin must be included.
_databytesAdditional initialisation data forwarded to the proxy ("0x" if unused).

The Rbac struct​

struct Rbac {
bytes32 role; // Role identifier (e.g. DEFAULT_ADMIN_ROLE)
address[] members; // Accounts to assign the role to
}

Example​

import { ethers } from "hardhat";

const factory = await ethers.getContractAt("Factory", FACTORY_ADDRESS);

const rbacs = [
{
role: ethers.ZeroHash, // DEFAULT_ADMIN_ROLE
members: [await deployer.getAddress()],
},
];

// Loan tokens use deployProxy (no typed method):
const version = await blr.getLatestVersionByConfiguration(LOAN_CONFIG_ID);

const tx = await factory.deployProxy(
BLR_PROXY_ADDRESS, // resolver
LOAN_CONFIG_ID, // configKey (bytes32)
version, // pinned configuration version
rbacs,
"0x", // additional init data
);

const receipt = await tx.wait();
// the new proxy address is emitted in the ProxyDeployed event
const event = receipt.logs.find((log) => log.topics[0] === factory.interface.getEvent("ProxyDeployed").topicHash);
const proxyAddress = factory.interface.parseLog(event).args.proxyAddress;
console.log("Token proxy deployed at:", proxyAddress);

Errors​

ErrorCause
EmptyResolver_resolver is the zero address.
NoInitialAdmins_rbacs contains no entry that assigns DEFAULT_ADMIN_ROLE to at least one account.

Event emitted​

event ProxyDeployed(
address indexed proxyAddress,
IBusinessLogicResolver resolver,
bytes32 configKey,
uint256 version,
IResolverProxy.Rbac[] rbac,
bytes data
);