An invoice gets its own address before it costs anything.

ZeroInit derives a NEAR address from the invoice terms, with no transaction. The first payment creates the account and splits the money.

See how it worksReplay the demo run

transactions to issue an invoice
0
transaction to pay it
1
demo split to merchant and tax vault
92 / 8
tests pass
255

The problem

A payment has to be matched to an invoice.

Shared address

Payments to one address are told apart by a memo. If the memo is dropped on the way, the payment matches no invoice.

ZeroInit

Each invoice has its own address, derived from its terms. A payment to that address is a payment of that invoice.

How it works

Two invoices, two ways to pay.

Invoice A is paid with one contract call. Invoice B gets a plain wallet transfer, and a sweep settles it. Both end in the same 92 / 8 split.

  1. NONEXISTENT

    The invoice has an address and no account yet.

    The address comes from the invoice terms and the contract hash. Asked about it, the chain answers UNKNOWN_ACCOUNT. No transaction was needed to get it.

  2. PAID

    The first payment creates the account.

    The payer sends one transaction. It installs the contract at the address, runs it, and splits the money. In the demo run, 1 NEAR came in, 0.92 went to the merchant and 0.08 to the tax vault.

  3. UNINITIALIZED

    A wallet can send money to the address.

    For a NEAR invoice a plain transfer works. The money waits in an account that has no code yet.

  4. PAID

    Anyone can then call sweep.

    sweep installs the contract and settles the waiting money with the same split. The demo run ends with result=PAID.

Details

What each step does.

Issue an invoiceThe merchant computes the address on their own machine. No transaction is sent.Learn more

The address is 0u followed by the Crockford base32 of a SHA3-256 hash. The hash covers the contract's code hash and the invoice terms. With --offline the command makes no RPC call.

The account has no access keys. Its key list is empty and the terms are part of the address.

issue an invoice, offline
$ npm run zi -- invoice new --id INV-2001 \
    --merchant example-zi-merchant.testnet \
    --tax-vault example-zi-taxvault.testnet \
    --tax-bps 800 --amount 0.1 --expires-in 2h --offline

Terms of INV-1111-V33-A, the demo invoice

merchant
zeroinit-merchant.testnet
tax_vault
zeroinit-taxvault.testnet
tax_bps
800
amount
1 NEAR
address
0us40y0dm1767jkzzxynmwjer15h4xy5wc96rg3xv5hfgs8fx421ag
PayOne transaction from the payer installs the contract and calls pay.Learn more

The transaction has two actions. The first installs the contract at the address. The second calls pay with the deposit. The contract sends the tax share to the vault and the rest to the merchant.

The payer covers gas and the protocol's account-creation charge. For a v3.3 NEAR payment that measured 0.0075647 NEAR.

the payment, as recordedPAID
signer    zeroinit-payer.testnet
receiver  0us40y0dm176…fx421ag
actions   UniversalStateInit {raw}
          FunctionCall {"pay", "{}", 100 Tgas, deposit = 1 NEAR}
tx        GFzvAw8t95XhN1JnvSNY4K9sThv7HQahLp9AnJ2SHQLe

Where the 1 NEAR went

0.92 NEAR
zeroinit-merchant.testnet (merchant)
0.08 NEAR
zeroinit-taxvault.testnet (tax vault)
Pay from a walletA plain transfer waits in an uninitialized account until someone calls sweep.Learn more

Money sent to the address with no call lands in an account that has no code. sweep installs the contract and settles with the same split. It does not know who sent the money, so paid_by is null.

If part of the price was sent this way and the payer then pays normally, the parked part counts toward the price and the rest comes back. Nobody is charged twice.

Token invoices need one more step: the 0u id must be registered on the token, with a storage_deposit of 0.00125 NEAR. The CLI does it in the same transaction as the payment; for a transfer from an exchange, anyone can send it first.

Account states

  1. NONEXISTENTaddress computed, nothing on chain
  2. UNINITIALIZEDholds money, no code
  3. INITIALIZED, PAIDafter pay or sweep
the invoice paid by a plain transfer
$ npm run zi -- status INV-1111-V33-B
STATE INITIALIZED status=PAID paid_by=null balance=0 storage=396
Verifyzi verify recomputes the address from the terms and compares it with the chain.Learn more

The contract is a global contract published by its hash. It has no owner and no admin call.

The deployed code is a reproducible build. Rebuilding the source at commit 4116e69 gives the same hash.

zi verify INV-1111-V33-A
PASS code-hash-trusted CrwuvfSLJoHs4GkkqqL22PeHG2BdcxmnMoxX2zcfaVsb
INFO deployment v3.3 (current, deployed on testnet: parked funds count toward the price; reproducible build of commit 7727a1c)
PASS format-matches-deployment zeroinit-invoice/2
PASS id-from-terms 0us40y0dm1767jkzzxynmwjer15h4xy5wc96rg3xv5hfgs8fx421ag
PASS raw-from-terms
PASS doc-fields-from-terms
PASS code-hash-onchain CrwuvfSLJoHs4GkkqqL22PeHG2BdcxmnMoxX2zcfaVsb
PASS terms-onchain
PASS no-access-keys
INFO status=PAID
Contract (v3.3)
CrwuvfSLJoHs4GkkqqL22PeHG2BdcxmnMoxX2zcfaVsb
Demo account
0us40y0dm1767jkzzxynmwjer15h4xy5wc96rg3xv5hfgs8fx421ag
Address kind
0u…, NEP-655, protocol 88
Tests
255 pass, plus 104 on the contract. The recorded demo replays offline.
License
MIT

Replay

The recorded demo run, check by check.

These are the checks the CLI makes on fixtures/v33/demo-run.json. Each value is read from that recording. Nothing here uses a wallet, keys or the network.

  1. PASS before-unknown-account INV-1111-V33-A NONEXISTENT
  2. PASS pay-tx-success INV-1111-V33-A GFzvAw8t95XhN1JnvSNY4K9sThv7HQahLp9AnJ2SHQLe []
  3. PASS invoice-paid-after-callback INV-1111-V33-A
  4. PASS split-merchant INV-1111-V33-A 0.92
  5. PASS split-vault INV-1111-V33-A 0.08
  6. PASS transfer-path-uninitialized INV-1111-V33-B 1
  7. PASS sweep-tx-result INV-1111-V33-B 2j8vbU6kcaXu5mmP9ZMnYVMcwsAXmzrit8ZUapQzaxGU result=PAID

INV-1111-V33-A NONEXISTENT

No account yet.

The invoice already has an address. Asking the chain about it returns UNKNOWN_ACCOUNT.

Address
0us40y0dm1767jkzzxynmwjer15h4xy5wc96rg3xv5hfgs8fx421ag
Chain answer
UNKNOWN_ACCOUNT
Recorded call
#6, block 277487669

INV-1111-V33-A PAID

One transaction pays and creates the account.

The payer sends 1 NEAR. The same transaction creates the account and runs the payment.

Transaction
GFzvAw8t95XhN1JnvSNY4K9sThv7HQahLp9AnJ2SHQLe
Signed by
zeroinit-payer.testnet
Actions
UniversalStateInit, FunctionCall
Result
PAID
Recorded call
#10

INV-1111-V33-A PAID

The invoice reads PAID.

After the payment callback, get_invoice on the new account returns PAID and the payer.

status
PAID
paid_by
zeroinit-payer.testnet
paid_amount
1 NEAR
Recorded call
#12

INV-1111-V33-A PAID

92% to the merchant.

The first of two transfers in the payment receipt.

To
zeroinit-merchant.testnet
Amount
0.92 NEAR
Recorded call
#10

INV-1111-V33-A PAID

8% to the tax vault.

The invoice sets tax_bps to 800. That is 8%.

To
zeroinit-taxvault.testnet
Amount
0.08 NEAR
tax_bps
800
Recorded call
#10

INV-1111-V33-B UNINITIALIZED

A wallet sends money to invoice B.

A plain transfer to the address. The balance arrives and no contract is installed yet.

Address
0uzr1k9c7qe0fgtzqh9q14221fke5mtwbn9qsrf1d91s6871aay0z0
Balance
1 NEAR
state
uninitialized
Recorded call
#24

INV-1111-V33-B PAID

A sweep settles invoice B.

The sweep installs the contract and settles the same split. paid_by is null because sweep does not know who sent the money.

Transaction
2j8vbU6kcaXu5mmP9ZMnYVMcwsAXmzrit8ZUapQzaxGU
Signed by
zeroinit-sweeper.testnet
Result
PAID
paid_by
null
Recorded call
#28

Transaction lookups need an archival RPC, https://archival-rpc.testnet.fastnear.com. The default RPC keeps about a day and a half.

Try it from npm

Check the demo invoice on testnet. Read-only, no keys.

npx -y @erhantrk/zeroinit verify INV-1111-V33-A

Add the MCP server for AI agents. It is read-only and never signs.

claude mcp add zeroinit -- npx -y -p @erhantrk/zeroinit zeroinit-mcp

The package is @erhantrk/zeroinit on npm: the CLI, the library and the MCP server.

Run it locally

npm run zi -- replay fixtures/v33/demo-run.json

Source on GitHub. The repository also has an MCP server for AI agents. It is read-only and never signs.

Trust and limits

What can be changed, and what was not tested.

PartWho can change it
Invoice contractNo one: no owner, no admin call.
Invoice accountNo one: it has no access keys.
tUSDC mock tokenThe operator, with a full-access key. A token invoice trusts whoever controls the token contract.
Deployer accountsThe operator, with full-access keys.
  • NEAR testnet only. No mainnet use was tried.
  • Not audited. The audit rounds were AI reviews.
  • The race fixes other than the bad_msg path were not run live. Two token payments at the same moment were: both came back in full.
  • The wallet path is a plain transfer from a wallet. No exchange was tested.
  • Token (NEP-141) invoices need the 0u id registered on the token: one storage_deposit of 0.00125 NEAR. The CLI sends it in the same transaction as the payment; before a transfer from an exchange, someone has to send it first. “Costs nothing until paid” describes issuing.
  • A 0u address has no checksum of its own. The issuer shows a 4-character check code next to it, and zi check tests an address against it. An address pasted straight into a wallet is not checked.
  • Plain-transfer underpayments and overpayments are not refunded: a sweep cannot know who sent them.