Build a Native project

Use a reviewed template or integrate a custom contract with BondrailEarner.

Monad testnet; not audited. Custom contracts need an individual review before they can raise. Reviewed templates deployed through factory.deployNative need no individual review. Both paths still require valid terms and sufficient history.

Create the account

BondrailFactory.createAccount(asset) creates the issuer's account in the token used for fees, funding and repayment. History starts at the first nonzero booked revenue, before any bond exists.

import { bondrailClient } from '@bondrail/sdk/client';
const bondrail = bondrailClient({ publicClient, walletClient, factory });
const account = await bondrail.createAccount(asset);

Option 1: deploy a reviewed template

The disclosed reviewer approves the hash of creation code with setNativeTemplate(hash, true). An issuer then calls deployNative(account, creationCode, args, salt). args must be ABI-encoded constructor arguments beginning with the account. The factory deploys that code with CREATE2, records templateOf(project), and binds it through bindProjectFromFactory in the same transaction.

import { bondrailFactoryAbi } from '@bondrail/sdk/abis';
import { encodeAbiParameters } from 'viem';

// Example for a reviewed template whose only constructor parameter is the account.
const args = encodeAbiParameters([{ type: 'address' }], [account]);
const { request } = await publicClient.simulateContract({
  address: factory, abi: bondrailFactoryAbi, functionName: 'deployNative',
  args: [account, reviewedCreationCode, args, salt], account: walletClient.account!,
});
const hash = await walletClient.writeContract(request);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== 'success') throw new Error('Template deployment failed');

Use the actual reviewed template's full constructor layout and exact bytecode. No separate per-project approval is needed for this path. Revocation through setNativeTemplate removes template-based approval; an independent nativeApproved(project) approval can still authorize that project.

Option 2: inherit the package in a custom contract

npm i @bondrail/contracts

See the package README for tarballs before publication, forge install, and npm/Git remappings. Both use this import:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {BondrailEarner} from "@bondrail/contracts/src/BondrailEarner.sol";

contract MyProject is BondrailEarner {
    constructor(address account) BondrailEarner(account) {}
    function pay(uint256 gross) external returns (uint256 net) {
        return _collect(msg.sender, gross, gross / 100);
    }
}

The issuer deploys the contract and calls BondrailAccount.bindProject(project) once. The reviewer must then call setNativeApproval(project, true) before createBond can accept it. Inheriting BondrailEarner alone does not prove all fee paths use it. Review must examine alternate fee routes, upgrade/admin powers and caller authentication. The account reference in the base is immutable.

Route each fee and authenticate the payer

_collect(payer, gross, fee) calls account.collect: it pulls from the payer, books the fee and sends the net to the project. Payers approve the account. Use msg.sender or independently authenticated intent; never expose an arbitrary-payer pull to untrusted callers. _collectWithPermit supports an EIP-2612 token; signAccountPermit in @bondrail/sdk/permit signs for the account as spender.

_settleOut(payer, gross, fee) approves and pulls funds from the project, then sends the net to the payer; the recipient approves nothing. Both routes use the account's asset. Tiny percentage fees can round to zero, so choose payment bounds and rounding deliberately.

contracts/src/BondrailEarner.sol
function _collect(address payer, uint256 gross, uint256 fee) internal returns (uint256 net) {
    return _bondrailAccount.collect(payer, gross, fee);
}
contracts/src/BondrailEarner.sol
function _settleOut(address payer, uint256 gross, uint256 fee) internal returns (uint256 net) {
    IERC20(_bondrailAccount.asset()).forceApprove(address(_bondrailAccount), gross);
    return _bondrailAccount.settleOut(payer, gross, fee);
}

During an Active bond, BondrailAccount._checkPayment enforces fee * 10000 >= gross * minFeeBps(). The factory checks review/binding at issuance, deposits, activation and second release. These checks do not prove future demand or stop a business moving users elsewhere. See the trust model and the external Foundry example.

On this page