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,ReturnandOutas 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 toLend, 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
| Concept | In this contract |
|---|---|
| State | aggregate Loan |
| Actions and validation | Lend, Return, one rule per line |
| Signer | context.signer == owner |
| Queries | Out, filtered, sorted, paged |
| Merge rules | merge: Lend=exclusive, Return=exclusive |
| Trying safely | The Workbench's throwaway node |
Next: DHL language reference for everything DHL can say, and DHL best practices.