This contract is a proxy distributor that distributes TON based on predefined shares in a dictionary that is set during contract initialization and remains unchanged.
The contract does NOT perform distribution in the following cases:
- When the Deploy message with an opcode of
0x7a2f11d8is received - When the contract balance is less than
DISTRIBUTION_BUFFER_LIMIT - When there is insufficient gas for distribution
The dictionary represents a list of recipients with their shares. Dictionary criteria:
- No more than 10 addresses and no fewer than 1 address
- The sum of shares must be exactly 100.00%
- The precision
ROYALTY_PRECISION = 1000means that every percentage value is stored multiplied by1000(so you can express fractions down to 0.001%). For example, a 70 % share is saved as70_000, while 12.345 % becomes12_345.
If the dictionary meets all criteria, the funds are distributed according to the specified shares. If any of the criteria are not met, that contract instance is considered incorrect and all incoming funds will be sent to the trustedBackupAddress.
deploy#7a2f11d8 = Deploy;
_ isInitialized:Bool royaltyDict:(Maybe ^Cell) trustedBackupAddress:MsgAddress totalRoyaltyReceivers:(Maybe uint8) = Storage;
There is only one get method, which allows you to fetch the internal contract state.
Its signature is getStorage(), and it returns a stack with the following struct:
struct Storage {
isInitialized: bool
royaltyDict: RoyaltyDict
trustedBackupAddress: address
totalRoyaltyReceivers: uint8?
}
Field descriptions:
isChecked– validation flag. It MUST be set tofalseat deployment time; once all checks succeed (dictionary size, share sums, minimal lump fee, etc.) the contract flips it totrue. WhileisCheckedisfalse, every incoming TON is forwarded totrustedBackupAddresswithout attempting distribution.royaltyDict– The royalty distribution dictionary (map<address_id, share>). The key isaddress_id(the 256-bit hash of a workchain-0 address), the value is the participant's share encoded asuint32with 1000 precision (e.g. 70 000 = 70 %).trustedBackupAddress– fallback address. Any funds sent to an incorrect contract instance (whenisCheckedisfalse) or other unrecoverable situations will be forwarded here.totalRoyaltyReceivers–uint8?that, after initialization, holds the actual number of receivers defined inroyaltyDict. During deployment this field may be used as an arbitrary contract identifier (for example0). If the value is absent (null), deployment validation has failed.
You can check parsing examples in wrappers.
Get Method description:
getStorage() is a view (get) method that returns the full on-chain state of a particular Distributor instance in a single stack record.
Stack layout that the method returns (top → bottom):
isChecked–0/1bit.royaltyDict– reference to the dictionary cell ornull.trustedBackupAddress– address.totalRoyaltyReceivers–uint8ornull.
Consumers are expected to parse the dictionary according to the key/value format of the royaltyDict, which is described above in the field descriptions.
You can deploy the contract using scripts/deployDistributor.ts. For this, you will need to:
- Modify the
royaltyDictwith desired values where each element consists of:- Address
- Share in percentage multiplied by 1,000 (uint32)
const royaltyDict: RoyaltyDistributionParams = [
{
// should be in workchain 0
address: Address.parse('royalty-receiver-address'),
royaltyPercent: 70_000n, // 70%
},
{
address: Address.parse('another-royalty-address'),
royaltyPercent: 30_000n, // 30%
},
];- Set the
trustedAddressto your desired address
Run:
npm run startAfter deployment, you will receive a message about the contract's correctness.
If you are deploying the contract from a language other than TypeScript, craft the initial data cell according to the schema (TL-B).
Example (pseudo-code):
cell data = beginCell()
.storeBit(0) ; isChecked = false
.storeMaybeCell(royaltyDict)
.storeAddress(trustedBackupAddress)
.storeMaybeUint(null, 8) ; totalRoyaltyReceivers
.endCell();
Any SDK (Python, Go, etc.) that supports cell building can reproduce the snippet above. The only requirement is to construct the royaltyDict using the proper key/value format and store it as a reference.
The contract accepts incoming transfers from any address with any payload. To distribute funds (pay royalty), you should simply send a message with the royalty amount as value to this contract address and it will handle the rest. There is no need to filter out low values, since the contract uses buffer distribution logic.
For instance, if the buffer limit is 1 TON and the contract’s current balance is 0.6 TON, sending an additional 0.2 TON will not trigger distribution immediately—the funds will be cached inside the contract. Once the balance crosses the 1 TON threshold (e.g. after another incoming transfer), the full amount will be distributed according to royaltyDict.
Who pays for gas? During distribution the contract purchases compute-phase gas using the TONs that arrived with the triggering message (msgValue). However, the forward fees (fwdFee) for every outgoing royalty transfer are deducted from the contract’s own balance during the action phase. Although:
• The sender must attach enough value to cover compute gas plus the lump forward fee for each receiver. (≈0.0128 TON) • The royalty recipients receive their shares in full; forward fees are not subtracted from the values they receive.
If msgValue is too small to buy the required gas, the contract simply keeps the funds until a larger top-up arrives (same buffer logic as above).
You can create multiple instances that share the same royaltyDict by adjusting the totalRoyaltyReceivers parameter. This value will be overwritten during contract initialization. The contract itself doesn't have any "admin" custodial functionality, meaning that to change royalty distribution parameters you will need to deploy a new instance and change the royalty receiver address.
contracts- Source code of all the smart contracts in the project and their dependencies.wrappers- Wrapper classes (implementingContractfrom ton-core) for the contracts, including any [de]serialization primitives and compilation functions. Warning: if you change the contract code or its TL-B schema, make sure to update these wrappers manually—they are not generated automatically.tests- Tests for the contracts.scripts- Scripts used by the project, mainly the deployment scripts.utils- Utility helpers used across tests and deployment:gasUtils.ts– functions to parse blockchain config, adjust gas and storage prices, and calculate message forward fees.setup.ts– helpers for unit tests: royalty dictionary generation/validation and on-chain deployment setup for the Distributor contract.tvm11.ts– convenience helper to activate TVM v11 capabilities in the local Sandbox blockchain.
- Node.js >= 18 (LTS recommended)
- npm >= 9 (comes bundled with Node.js)
Install dependencies:
npm installBuild:
npm run buildTest:
npm run testRun scripts / deploy:
npm run startThis project is licensed under the MIT License.