DZNYE CHAIN connecting…

Contracts: programs on Dznye Chain

Write, deploy and call contracts on Dznye Chain: a JSON language with gas, all-or-nothing calls, tokens, events and contract-to-contract calls, with full examples.

A contract is a small program that lives on the chain. It has its own address, can hold coins and tokens, and keeps its own storage. Nobody holds a key for it: only its code can move what it holds, and its code cannot be changed. That makes contracts good for escrow, crowdfunding, shops, games and markets.

How contracts behave

The language

A contract is JSON. It has functions, and each function's body is an expression. An expression is either a value or a list whose first item names an operation, like ["+", 1, 2].

{
  "init": ["set", "owner", ["caller"]],            // runs once, when deployed (optional)
  "functions": {
    "lock": {                                      // a function that changes things
      "args": ["until"],
      "body": ["seq",
        ["require", [">", ["paid", "DZY"], 0], "send the DZY to lock with this call"],
        ["set", "owner", ["caller"]],
        ["set", "until", ["arg", "until"]]
      ]
    },
    "unlocks": { "view": true, "body": ["get", "until"] }   // a view: free to ask, cannot change anything
  }
}

Values

Whole numbers, text, true, false and null. Fractions do not exist: use the smallest unit (1 DZY is 1,000,000). Times are in milliseconds.

Operations

GroupOperationsNotes
Maths+ - * / % min maxWhole numbers. / rounds toward zero. Dividing by zero fails.
Compare= != < <= > >=Give true or false.
Logicand or notOnly true counts as true.
Textstr (join), len["str","p:",["caller"]] joins into one text.
Flowif seq for["for","i",1,10,body] runs up to 1,000 rounds.
Namesarg let var["let","x",5] then ["var","x"].
Stoprequire fail["require", test, "message"] stops the call, undoing everything, if the test is not true.
The callcaller self now height paid["paid","DZY"] is how much of that coin came with this call. now is the block's time.
Checksisaddress balance["balance","DZY"] is what the contract holds. Add an address for someone else's.
Storageget has set del["get","key",default]. Keys up to 96 characters; texts up to 256.
Coinstransfer["transfer","DZY",to,amount] sends from the contract.
Other contractscall["call",address,"fn",[args],"DZY",amount]: the last two are optional.
Tokensnewtoken mint["newtoken","SYM","Name",decimals,cap] makes a token the contract issues (it needs 1 DZY of its own). ["mint",asset,to,amount] issues it.
Eventsemit["emit", value] announces something. The newest 50 are kept per contract.

Gas and fees

An ordinary step1 gas
Reading storage20 gas
Writing storage100 gas, plus 1 per character
Sending coins200 gas
Calling another contract500 gas
Emitting an event50 gas, plus 1 per character
Making a token2,000 gas

A call asks for a gas limit (10,000 if you say nothing, at most 200,000). It pays the chain's fee once, plus once more for every 1,000 gas of the limit, and unused gas is paid back. If a call fails while running, it still pays for the gas it used, and the failure is written in the contract's events. A call that is badly formed (a missing function, wrong number of arguments) is refused and costs nothing.

Limits

Put a contract on the chain

Putting a contract up costs 1 DZY, paid to the treasury, plus the gas fee. Its address comes from the transaction, and nobody can change its code afterwards.

From the command line

node chain/cli.mjs contract deploy --key me.json --file chain/contracts/vault.json
# Deployed. The contract's address is 9f3c…

From code

import { connect, newKey } from 'https://developers.dznye.com/js/chain/sdk.js';
const dz = connect('https://api.dznye.com');
const vault = await dz.deploy(me, programJson);

Use it

# call a function, sending 5 DZY with it
node chain/cli.mjs contract call --key me.json --contract <address> --fn lock --args '[1900000000000]' --pay 5

# ask a view function (free, nothing changes)
node chain/cli.mjs contract view --contract <address> --fn unlocks

# look at its code, storage, balances and events
node chain/cli.mjs contract show --contract <address>

The command line and the developer kit try a call first and refuse to send one that would fail, so you do not pay gas for a mistake. Wallets and apps can do the same with POST /simulate: see the developer kit.

Ready-made examples

The chain/contracts/ folder has four working contracts you can copy:

A walk-through: a counter

{
  "functions": {
    "bump":  { "body": ["set", "n", ["+", ["get", "n", 0], 1]] },
    "count": { "view": true, "body": ["get", "n", 0] }
  }
}

Deploy it, call bump three times, then ask count and you get 3. Each bump reads the stored number (default 0), adds one and writes it back.

A walk-through: a shop that makes its own tickets

  1. Deploy shop.json. Whoever deploys it is the owner.
  2. Send the shop 1 DZY, which it needs to make its token.
  3. Call open with ["TICKET","Concert ticket",100,250000]: symbol, name, stock of 100, and a price of 0.25 DZY.
  4. Anyone calls buy, sending at least 0.25 DZY with the call, and receives one ticket. When 100 are sold, further buys fail.
  5. The owner calls collect to take the DZY.

Because the shop made the token itself, nobody else, not even the owner, can issue more tickets than the limit. The chain enforces it.

Good habits

Built to be permanent

A contract cannot be deleted or changed after it is deployed. That is what lets people trust it: the rules they read are the rules that run. To release a new version, deploy a new contract and point people to it.