Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(ertp): ERTP walk-thru: Alice buys a ticket from Bob #1257

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions main/glossary/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@ A short form of [agoric-3-proposals](https://github.com/Agoric/agoric-3-proposal

A command line interface for initializing, deploying, and starting Agoric projects, as well as installing dependencies. See the [Agoric CLI documentation](/guides/agoric-cli/) for more information.

## AllegedName
## Alleged

Human-readable name of a type of assets. The alleged name should
not be trusted as an accurate depiction, since it is provided by
the maker of the mint and could be deceptive, but is useful for debugging and double-checking.
<a name="allegedname" />

The AllegedName must be a string.
See [DebugName](#debugname).

## Allocation

Expand Down Expand Up @@ -210,6 +208,20 @@ An [invitation](#invitation) optionally returned by [`E(zoe).startInstance(...)`
creator can use. It is usually used in contracts where the creator immediately
sells something (auctions, swaps, etc.).

## DebugName

A label for debugging / diagnostic purposes. aka Alleged name.
Since debug names may be misleading,
**no code should rely on an Alleged / DebugName for correctness.**

See:

- [Exo tags](/guides/zoe/contract-details#tag-naming-kinds-of-objects),
which are used as debug names.
- [ERTP](/guides/ertp/) where Brand objects, not string names,
are used to reliably identify digital assets.
- [Remotable in @endo/pass-style](https://endojs.github.io/endo/functions/_endo_pass_style.Remotable.html), where labels are bound to objects.

## Deposit Facet

A [facet](#facet) of a [purse](#purse). Anyone with a reference to its deposit facet object can add
Expand Down
Binary file added main/guides/ertp/assets/alice-bob-ticket.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions main/guides/ertp/assets/ertp-interfaces-1.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ classDiagram
}
ertp --> IssuerKit : makeIssuerKit

class IssuerKit {
mint: Mint
issuer: Issuer
brand: Brand
}

class Mint {
getIssuer()
}
Expand Down
2 changes: 1 addition & 1 deletion main/guides/ertp/assets/ertp-interfaces-1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
133 changes: 129 additions & 4 deletions main/guides/ertp/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,134 @@
# ERTP Overview

ERTP (_Electronic Rights Transfer Protocol_)
is Agoric's token standard for transferring tokens and other digital assets in
ERTP (_Electronic Rights Transfer Protocol_) is Agoric's digital asset standard.

ERTP is a uniform way of transferring tokens and other digital assets in JavaScript. All kinds of digital assets can be easily created, but importantly, they can be transferred in exactly the same ways, with exactly the same security properties.

For example, let's suppose Alice wants to buy a concert ticket from Bob for 10 bucks.

::: tip Watch: erights -- credibly transferable ownership (Oct 2024)
_25 minutes on ERTP at a conceptual level._
<br />

[<img src="./assets/alice-bob-ticket.png" alt="diagram of Alice, Bob, Ticket objects" />](https://www.youtube.com/watch?v=O8Bx_Abj9Qc&list=PLzDw4TTug5O1A-tkPJe4HVq0VBPcNOMHm)

:::

## Issuer, Brand, and Mint

We start by using `makeIssuerKit` to make a `Mint`, `Brand`, and `Issuer` for **Bucks**.

<<< @/../snippets/ertp/guide/test-readme.js#importErtp
<<< @/../snippets/ertp/guide/test-readme.js#declareShared
<<< @/../snippets/ertp/guide/test-readme.js#makeBucks

The `bucks.brand` and `bucks.issuer` don't let anyone mint new assets, so sharing
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The `bucks.brand` and `bucks.issuer` don't let anyone mint new assets, so sharing
The `bucks.brand` and `bucks.issuer` don't let people mint new assets, so sharing

"anyone" here triggers thoughts about existential quantifiers. "people", at least to me, ties the abilities more closely to those holding the objects.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as if people hold objects? that hurts my brain. I'll have to think it over.

them widely is normal. We must be careful to guard the`bucksMint`, so we keep it separate.

::: tip see also ZCFMint (TODO)

...

:::

## Amount: Asset Descriptions

Next we combine the Bucks brand with a value to make an `Amount`:

<<< @/../snippets/ertp/guide/test-readme.js#bucksAmount

An Amount is a value labeled with a brand.
Amounts are descriptions of digital assets,
answering the questions "how much" and "of what kind".

Amounts have no economic scarcity or intrinsic value.

:::tip More on Asset Use versus Mention
_See also [The Settlers of Blockchain](https://agoric.com/blog/technology/the-settlers-of-blockchain) by Chris Hibbert, Jun 2021_
:::

## Minting Payments

Next we use the mint to make a `Payment` of 100 bucks for Alice:

<<< @/../snippets/ertp/guide/test-readme.js#bucksPayment100

Likewise, we make a **Tickets** issuer kit make payments of 10 **Tickets** and 100 **Bucks**
for Bob.

<<< @/../snippets/ertp/guide/test-readme.js#amountMathProps
<<< @/../snippets/ertp/guide/test-readme.js#bobPayments

Where Amounts only describe assets, Payments actually convey digital assets/rights.
Sending Payments must be done very carefully.

## Making Purses

Alice is acting as a buyer.
She can make her own empty purses using the shared issuers, which she relies on.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"relies on" only makes sense here as ocap terminology. I didn't find anything helpful at cap-lore.com. Do you have a better link?

I don't know whether it's helpful to bring this up here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rely as in "reliance set" comes from Concurrency Among Strangers.

I'll think about where to work that in.

She depsits some **Bucks** that she is given into her Bucks purse.

<<< @/../snippets/ertp/guide/test-readme.js#aliceBuyer1

Purses also hold digital assets/rights.
**Purses are normally not sent betwen parties.**

## Credible Asset Transfer

To buy a ticket, she withdraws a payment of 10 bucks and make a `buy` request
to some vendor she was given.

<<< @/../snippets/ertp/guide/test-readme.js#aliceBuyer2

The seller has likewise created purses for **Bucks** and **Tickets** and made deposits.
When they get a `buy` request, the argument may be anything, so it's called `allegedPayment`.
But once they deposit it into their Bucks purse, they know it was
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
But once they deposit it into their Bucks purse, they know it was
But once they deposit it into their Bucks purse, ERTP guarantees it must have been

a valid Bucks payment, and they know the amount.
Provided the amount is sufficient, they withdraw a ticket (payment) and return it.

<<< @/../snippets/ertp/guide/test-readme.js#bobSeller

Now our buyer has an `allegedTicket`.
Once she deposits it in her **Tickets** purse, she knows it was
a valid payment and she knows its value. She can check that she
got at least 1 ticket.

<<< @/../snippets/ertp/guide/test-readme.js#aliceBuyer3

To put it all together:

<<< @/../snippets/ertp/guide/test-readme.js#aliceBuysFromBob

## Non-Fungible and Semi-Fungible Assets

::: tip: TODO: Non-Fungible and Semi-Fungible Assets

...

:::

## ERTP Concepts Overview

Each digital asset has Mint, Issuer, and Brand facets:

![ERTP Interfaces 1](./assets/ertp-interfaces-1.svg){ width=200 height=200 }

Use brands to make amounts.

Use a Mint to create Payments. Use an Issuer to make Purses.
Deposit payments into purses and withdraw them back out.

![ERTP makeIssuerKit API](./assets/ertp-interfaces-2.svg)

Fungible and non-fungible kinds of assets are handled uniformly.

![ERTP object relationships](./assets/ertp-interfaces-3.svg)

# Obsolete material

_aside from TODOs above_

token standard for transferring tokens and other digital assets in
JavaScript. Using the [ERTP API](/reference/ertp-api/),
you can easily create and use digital assets, all of which are
transferred exactly the same way and with exactly the same security properties.
Expand All @@ -12,8 +139,6 @@ object, it can call methods on that object. If it doesn't have a
reference, it can't. For more on object capabilities, see
[Chip Morningstar's post](http://habitatchronicles.com/2017/05/what-are-capabilities/).

## ERTP Concepts Overview

### Asset

There are three kinds of assets:
Expand Down
117 changes: 116 additions & 1 deletion snippets/ertp/guide/test-readme.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable import/order -- https://github.com/endojs/endo/issues/1235 */
import { test } from '../../prepare-test-env-ava.js';
// @ts-check

import { E } from '@endo/eventual-send';

Expand All @@ -8,7 +9,9 @@ import { E } from '@endo/eventual-send';
// eslint-disable-next-line import/no-extraneous-dependencies
import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js';

import { AmountMath, makeIssuerKit, AssetKind } from '@agoric/ertp';
// #region importErtp
import { makeIssuerKit, AmountMath, AssetKind } from '@agoric/ertp';
// #endregion importErtp

test('ertp guide readme', async t => {
// #region makeIssuerKit
Expand Down Expand Up @@ -100,3 +103,115 @@ test('ertp guide readme', async t => {

t.truthy(allLive.every(a => a));
});

test('MarkM 2024-10 talk', t => {
/** @type {Record<string, {issuer: Issuer, brand: Brand}>} */

// #region amountMathProps
const { make, isGTE } = AmountMath;
// #endregion amountMathProps

// #region declareShared
const shared = {};
// #endregion declareShared

// #region aliceBuyer1
/** @param {{ bucks: Payment, vendor: any }} some */
const makeBuyer = some => {
const { bucks, tickets } = shared;
const my = {
bucks: bucks.issuer.makeEmptyPurse(),
tickets: tickets.issuer.makeEmptyPurse(),
};
my.bucks.deposit(some.bucks);
// #endregion aliceBuyer1

// #region aliceBuyer2
return harden({
buyTicket() {
const pmt = my.bucks.withdraw(make(bucks.brand, 10n));
const allegedTicket = some.vendor.buy(pmt);
// #endregion aliceBuyer2
// #region aliceBuyer3
const got = my.tickets.deposit(allegedTicket);
t.log('Alice got', got);
isGTE(got, make(tickets.brand, 1n)) || assert.fail();
return got;
},
getBalances: () => ({
bucks: my.bucks.getCurrentAmount(),
tickets: my.tickets.getCurrentAmount(),
}),
});
};
// #endregion aliceBuyer3

// #region bobSeller
/** @param {{ bucks: Payment, tickets: Payment}} some */
const makeSeller = some => {
const { bucks, tickets } = shared;
const my = {
bucks: bucks.issuer.makeEmptyPurse(),
tickets: tickets.issuer.makeEmptyPurse(),
};
my.bucks.deposit(some.bucks);
my.tickets.deposit(some.tickets);

return harden({
/** @param {Payment} allegedPayment */
buy(allegedPayment) {
const amt = my.bucks.deposit(allegedPayment);
isGTE(amt, make(bucks.brand, 10n)) || assert.fail();
t.log('Bob got', amt);
return my.tickets.withdraw(make(tickets.brand, 1n));
},
getBalances: () => ({
bucks: my.bucks.getCurrentAmount(),
tickets: my.tickets.getCurrentAmount(),
}),
});
};
// #endregion bobSeller

// #region makeBucks
const bucksKit = makeIssuerKit('Bucks');
const { mint: bucksMint, ...bucks } = bucksKit;
Object.assign(shared, { bucks });
// #endregion makeBucks

// #region bucksAmount
const bucks100 = AmountMath.make(bucks.brand, 100n);
// #endregion bucksAmount

// #region bucksPayment100
const paymentA = bucksMint.mintPayment(bucks100);
// #endregion bucksPayment100

// #region bobPayments
const { mint: ticketsMint, ...tickets } = makeIssuerKit('Tickets');
Object.assign(shared, { tickets });

const paymentsB = {
bucks: bucksMint.mintPayment(make(bucks.brand, 200n)),
tickets: ticketsMint.mintPayment(make(tickets.brand, 50n)),
};
// #endregion bobPayments

// #region aliceBuysFromBob
const bob = makeSeller(paymentsB);
const alice = makeBuyer({ bucks: paymentA, vendor: bob });

const howMuch = (bv, tv) => ({
bucks: make(bucks.brand, bv),
tickets: make(tickets.brand, tv),
});

t.deepEqual(alice.getBalances(), howMuch(100n, 0n));
t.deepEqual(bob.getBalances(), howMuch(200n, 50n));

alice.buyTicket();

t.deepEqual(alice.getBalances(), howMuch(90n, 1n));
t.deepEqual(bob.getBalances(), howMuch(210n, 49n));
// #endregion aliceBuysFromBob
});
Loading