Borrower is a utility for TON blockchain validators to request loans from hGRAM: Hipo liquid staking protocol.
If you're a node operator, but you don't have enough GRAM to stake, you're at the right place. With Borrower you can request a loan from hGRAM, validate blocks, and earn a reward for your service.
Before moving forward, first read the documentation of hGRAM contract.
TON blockchain is a Proof-of-Stake (PoS) blockchain. That means that in order to create new blocks, you don't have to buy expensive hardware and pay a lot of money for electricity to produce lots of hashes, like Bitcoin miners. Instead, you have to stake your money for a fixed period of time and use a generally available server to participate in block creation.
PoS blockchains reward their validators for their service, and in TON, every new block that is created will generate some GRAM that will be distributed between validators. In addition, transaction fees paid by blockchain users will be distributed between validators.
Now, to start validating, you need access to a large sum of GRAM, like 300,000 GRAM or more. If you don't have access to that, you're at the right place. Borrower helps you in requesting a loan from hGRAM treasury. hGRAM treasury is the place where other users put their GRAM to help validators like you, and in return they want a portion of the rewards. We refer to these users as stakers.
If your loan request is accepted, you'll be given at least the requested amount, so that you can use it to participate in elections for the next round, and assuming you win in the election, your node will start to validate blocks for a fixed period of time, like 18 hours.
To prevent validators from doing nasty things to the blockchain, after a round of validation, there is a period of time (like 9 hours) that validators may be punished. This process is done by other validators, and they might propose to punish a rogue validator. So, in order to validate, you have to bring the maximum possible punishment for your requested loan when asking for it. This way, hGRAM won't have to pay the punishment from stakers' pocket. At the time of writing, the maximum punishment is 101 GRAM.
The treasury is permission-less: anyone can request a loan. When there are more requests than GRAM
to lend, the treasury decides who gets one. This section describes exactly how, so that you can
price a request instead of guessing. The authoritative source is decide_loan_requests and
recover_stake_result in the contract, and its
integration guide.
A request carries two numbers you choose:
loan: how much GRAM you want to borrow. Together with your ownstakeit must reach the network'smin_stake(config 17), though in practice the smallest stake the elector actually elects is much higher than that, and a stake below it earns nothing.min_payment: what you promise the pool for the loan. It is best read as a rate: see Pricing a bid below.
The reward share is not part of the bid. The treasury sets one value for every loan
(reward_share, out of 65535, index 26 of get_treasury_state), and a request that tries to name
its own is refused. This borrower reads it from the treasury, so there is nothing to configure; an
old reward_share or validator_reward_share key left in borrower.yaml is ignored. It needs a
treasury from 21 September 2026 or later, when the share became the protocol's.
- Requests are ranked on efficiency,
min_payment / loan, with both rounded down first:min_paymentto units of about 1.07 GRAM andloanto units of about 1,100 GRAM. On a tie the smaller loan goes first. The borrower logs your bid's efficiency when it sends a request. - The treasury serves requests in that order. A request that does not fit in what is left is skipped, and the next one is tried, so a lower-ranked request still wins if it fits. Rank only decides the order of service.
- Whatever is left after the accepted requests is added to them in proportion to their
loan(the accrued amount), so the pool is always fully lent.
Requests are public the moment they land, and can be replaced until bidding closes at
participate_since (from the treasury's get_times). Replacing a request costs another request fee
and keeps your collateral: the borrower sends only the fee and whatever the posted collateral falls
short of. After every send it checks, two minutes later, that the request is actually standing, and
sends again if the treasury refused it -- at most three times a round, and never within three minutes
of the close.
When a round ends, the pool receives the larger of your min_payment and its contractual share
of the reward, reward × (65535 − reward_share) / 65535. You keep the rest, less the borrower fee
(borrower_fee, index 20: a share of your own contractual reward, at least 1 GRAM, sent to the HPO
burner).
-
A
min_paymentbelow the pool's contractual share is never paid. It costs you nothing and only sets your rank. -
A
min_paymentabove it is paid out of your reward, and if the reward falls short, out of your collateral. The pool never collects more than the reward plus your collateral. -
min_paymentis scaled to the whole stake. When the treasury adds an accrued amount to your loan, it scalesmin_paymentby(loan + accrued) / loan. What you promise is therefore a rate on everything your loan stakes, and the efficiency you rank on is exactly that rate. Price it that way: a bid priced on leftover you expect to receive, but divided by a smallerloan, pays that same inflated rate on the leftover too.This applies from treasury code
f003de4b…, announced on 23 September 2026 and deployed no earlier than 26 September 2026. Before it,min_paymentwas not scaled.One consequence to price for: the elector pays nothing on stake above its cap (
max_factortimes the smallest elected stake), but the treasury scalesmin_paymenton everything it lends you. A loan that ends up taking most of the pool -- because it is the only one accepted -- can pass that cap, and then even amin_paymentat the pool's contractual share binds. If the pool is larger than the cap, keepmin_paymentbelowcap / poolof the break-even figure below.
A worked example with the figures of September 2026: a stake earned about 660 GRAM per 1,000,000
staked per round, and reward_share was 1799, so the pool's contractual share was 97.25% of the
reward and the borrower's 2.75%, of which the borrower fee (50%) burned half. On a 1,000,000 GRAM
loan that is a reward of about 660, of which the pool's share is about 642 and the borrower keeps
about 9. A min_payment of about 651 is where a borrower breaks even: above it the loan costs
money, below it the promise is only a ranking signal. The borrower logs this rate as
GRAM per 1,000,000 lent with every request. Rewards move from round to round (between about 644
and 689 per million over the same period), so a bid priced exactly at break-even loses money in a
round that pays less.
With the request you send collateral of at least min_payment + the maximum punishment for your
stake (currently 101 GRAM) + 1 GRAM for the burn floor, plus the request fee. The borrower computes
and sends this for you, and refuses to send when the wallet cannot cover it. Collateral comes back
with the loan result, less whatever the round took from it.
If your stake is accepted but not elected, it earns nothing and the pool takes your min_payment
(scaled, if the loan accrued) out of your collateral, up to the whole collateral.
Rent a server that has the minimum hardware requirements.
-
Install mytonctrl in full mode, but after the installation don't create any wallets or pools.
mytonctrlis a tool that installs a TON blockchain full node to validate blocks, configures and starts it. It also helps with upgrading the validator software. To install it on Ubuntu, run its install.sh script like this:wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh sudo bash install.sh -m full -d
Note 1: If you want to run the validator on the testnet, don't use the
-dflag and instead add this flag:-c https://ton-blockchain.github.io/testnet-global.config.json.Note 2: Follow mytonctrl installation manual, sections 1 and 2. There is no need to create wallets for other steps of the manual. The borrower needs a sync liteserver, so you may wait at this step for
mytonctrlto get synced.After the installation, run
mytonctrlexecutable. Then run thestatuscommand. Now your node should be syncing or maybe already synced. Just note the "ADNL address of local validator" in the output of thestatuscommand, since you'll need it to configure Borrower.Note 3: If you got an error like "Check
total_wt >= W[a]failed" when running the status command on the testnet, usestatus fastinstead. -
Install Borrower. Either:
-
download a release from the releases page:
borrower-linux-amd64orborrower-linux-arm64, plusSHA256SUMS. Check the download and install it:sha256sum --check --ignore-missing SHA256SUMS install -D -m 755 borrower-linux-amd64 ~/go/bin/borrower ~/go/bin/borrower -version
For a root user that path is
/root/go/bin. The first release is published once the accrual pricing treasury is live; until then, build from source. -
or build from source: install Go 1.26 or later (
snap install go --classic), then runmake install(orgo install) in a clone of this repository.
-
-
Download the
borrower.yamltemplate config file from this repository. Copy it to~/go/binalongside theborrowerexecutable. Then edit it and set your configuration:-
treasury: Address of the treasury contract. -
borrow: Configuration related to each loan request. -
wallet: Your wallet configuration that is used to send loan requests and the needed GRAM amount. -
validator_engine: Configure your validator here, specifically enter your ADNL address from thestatuscommand ofmytonctrl.
Then check the configuration without sending anything. From the directory holding
borrower.yaml:borrower -dry-run
It reads the chain, the validator engine and the wallet once, and logs the loan request it would send: the loan, the
min_payment, the GRAM it would attach, and the bid's rate and efficiency. It stops before the validator engine is configured or the wallet sends anything. It exits 0 when it would send a request (or yours already stands), 2 when a real run would send none -- inactive, wallet too short, bidding closed, a loan undermin_stake-- and 1 on an error. -
-
Install the service file. Copy
borrower.serviceto/etc/systemd/systemand edit it according to your configuration. Then run these one by one:sudo systemctl daemon-reload sudo systemctl enable borrower.service sudo systemctl start borrower.service sudo systemctl status borrower.service
Now the service is installed and will always run. To view its logs use journalctl -u borrower.service or journalctl -u borrower.service -f.
The service file restarts the borrower 10 seconds after it exits, and runs it with the system
directories read-only and without the ability to gain privileges. The borrower writes nothing to disk,
so this costs nothing; the comments in borrower.service say what each line is for.
make lists the targets. The ones that matter:
make testrunsgo vet, checks formatting and runs the tests;make buildbuildsbin/borrowerfor this machine.make dist VERSION=v3.0.0cross-compilesdist/borrower-linux-amd64anddist/borrower-linux-arm64with-trimpath, stamps the version into them (borrower -versionprints it, and the log's first line repeats it), and writesdist/SHA256SUMS.
CI runs the tests on every push and pull request. A release is cut by pushing a tag:
git tag -a v3.0.0 -m "v3.0.0"
git push origin v3.0.0The release workflow refuses a tag that is not on main, runs the tests, builds with make dist
using the newest stable Go, and publishes a GitHub release with the two binaries and SHA256SUMS
(a tag with a -, such as v3.0.0-rc1, is marked as a prerelease). A binary built any other way
reports its version as dev.
MIT