Tutorial

Tutorial: your first contract

You lend tools to friends and forget who has what. In this tutorial you write a small contract for it: a loan is created when you lend something, and closed when it comes back. Along the way you meet every part of DHL you need daily: state, actions, validation, queries, merge rules, the Workbench and your accomplice.

Open Data → Workbench: write a contract.

1. Describe the record

Replace the template with:

---
namespace: sam.tools.loan
version: 1.0.0
merge: Lend=exclusive, Return=exclusive
---

# A tool lent to someone, until it comes back.

```dhl
struct LoanRow
    id: Identity
    ref: Text(len=100)
    item: Text(len=200)
    borrower: Text(len=200)

aggregate Loan
    state
        public ref: Text(len=100)
        public owner: Identity
        public item: Text(len=200)
        public borrower: Text(len=200)
        public status: Text(len=20)
        public revision: Int = 0
```

Change sam in the namespace to your own handle. The aggregate is what one loan holds; LoanRow is what a query returns for each loan.

2. Lend

Below, add an action:

Lending something out.

```dhl
action Lend(ref: Text(len=100), owner: Identity, item: Text(len=200), borrower: Text(len=200))
    api authenticated
    authorize dhl
    validate
        state.revision == 0
        context.signer == owner
        item.len > 0
        borrower.len > 0
    apply
        state.ref = ref
        state.owner = owner
        state.item = item
        state.borrower = borrower
        state.status = "out"
        state.revision = 1
```

Read the validation lines aloud: this loan is new; whoever signs is its owner; there is an item; there is a borrower. If any is false, nothing happens.

3. Return

It came back.

```dhl
action Return(owner: Identity)
    api authenticated
    authorize dhl
    validate
        state.status == "out"
        context.signer == owner
        owner == state.owner
    apply
        state.status = "back"
        state.revision = state.revision + 1
```

Only an item that is out can come back, and only its owner can say so.

4. Ask what is out

```dhl
query Out(owner: Identity, after: Text(len=100))
    returns List<LoanRow>
    api authenticated
    dhlq
    sam.tools.loan
    | where owner == $owner and status == "out" and ref > $after
    | sort ref
    | take 100
    | select subject as id, ref, item, borrower
```

Use your namespace on the line after dhlq.

5. Try it

In Steps and queries, write a story with both happy and unhappy moments. "$julien" stands for your own key (julien is the internal handle of the person who set up the home):

{
  "steps": [
    {"action": "Lend", "subject": "drill", "args": {"ref": "drill", "owner": "$julien", "item": "Cordless drill", "borrower": "Ana"}},
    {"action": "Lend", "subject": "ladder", "args": {"ref": "ladder", "owner": "$julien", "item": "Ladder", "borrower": "Ben"}},
    {"action": "Lend", "subject": "drill", "args": {"ref": "drill", "owner": "$julien", "item": "Cordless drill", "borrower": "Chloé"}},
    {"action": "Return", "subject": "drill", "args": {"owner": "$julien"}},
    {"action": "Return", "subject": "drill", "args": {"owner": "$julien"}}
  ],
  "queries": [{"name": "Out", "params": {"owner": "$julien", "after": ""}}]
}

Press Try it. You should see:

✓ Lend drill      {"status": "out", "borrower": "Ana", …}
✓ Lend ladder     {"status": "out", "borrower": "Ben", …}
✗ Lend drill — DHL refused Lend: its rule `state.revision == 0` does not hold
✓ Return drill    {"status": "back", …}
✗ Return drill — DHL refused Return: its rule `state.status == "out"` does not hold
Out  [{"ref": "ladder", "item": "Ladder", "borrower": "Ben", …}]

The drill cannot be lent twice, and cannot come back twice. The ladder is still out. All of this ran on a throwaway node; your real records are untouched.

6. Install it

Press Install… then Install and restart. Mandala restarts in a few seconds. Now:

  • Data lists your contract; run Out, or Lend from its form, with a preview before signing.
  • Your accomplice has Lend, Return and Out as tools. Ask it: "I lent the hedge trimmer to Ben" — it prepares the action and asks you to approve it. "Who has my tools?" — it runs the query.

7. Make it yours

Ideas, each a small change you can try in the Workbench first:

  • Add due: Timestamp? to the state and an argument to Lend, and a query for overdue loans.
  • Give it a UI file (loan.ui.md) with labels and a refusal sentence for each rule (UI files).
  • Once loans exist, changing the state needs an upgrade action (Upgrading a contract).

What you learned

ConceptIn this contract
Stateaggregate Loan
Actions and validationLend, Return, one rule per line
Signercontext.signer == owner
QueriesOut, filtered, sorted, paged
Merge rulesmerge: Lend=exclusive, Return=exclusive
Trying safelyThe Workbench's throwaway node

Next: DHL language reference for everything DHL can say, and DHL best practices.