Purses and Payments
There are different kinds of digital assets:
- Currency-like, such as our imaginary Quatloos.
- Goods-like, such as theater tickets or magic weapons for use in a game.
- Abstract rights, such as participation in a particular contract.
In ERTP, digital assets always exist in either a purse or a payment object.
- purse: Holds an amount of same-branded digital assets until part or all of them are withdrawn into a payment. A new purse is created by an issuer and can only hold assets of that issuer's brand.
- payment: Holds a quantity of same-branded digital assets to transfer to another party. A payment is created containing either new assets from a mint or existing assets withdrawn from a purse or transferred from one or more other consumed payments. It can only hold assets of the same brand as its source(s).
Any number of purses or payments can hold assets of any particular brand. Neither a purse nor a payment can ever change their associated brand.
Each purse and payment object contains a specific amount of digital assets, which may be none at all ("empty" in AmountMath terms). In the same way you might have separate bank accounts for different purposes, you can have separate purses for the same brand of digital asset. One of your purses might hold 2 Quatloos while another holds 9000 Quatloos.
When you deposit assets into a purse, they are added to whatever assets already exist there. So a 3 Quatloos deposit into a purse with 8 Quatloos results in a purse with 11 Quatloos.
When adding a payment to a purse, you must add the entire payment. To only add part of a payment, you must first call anIssuer.split() or anIssuer.splitMany() to split it into two or more new payments.
mints create entirely new digital assets and put them in a new payment. You also create a payment by withdrawing assets from a purse, by splitting an existing payment, or by combining multiple payments into one new one. Note the brand of the new payment is the same as the associated brand of its originating mint, purse, or payment.
In ERTP, assets are not transferred directly from one purse to another. Instead, the transfer must be mediated by a payment as demonstrated below. In the Agoric stack, the actual send and receive operations are provided by E().
- Sender:
- Withdraw assets described by an
amountfrom apurse, creating apayment. - Send this
paymentto a recipient.
- Withdraw assets described by an
- Recipient:
- If you don't already have one, create a
pursefor the assetbrandyou'll receive. - Receive the message with the
payment. - Deposit the
paymentinto abrand-appropriatepurse.
- If you don't already have one, create a
Purses
You change a purse's balance by calling either deposit() (to add assets) or withdraw() (to remove assets) on it. A purse can be empty, which for fungible assets means it has a value of 0. For non-fungible assets, such as theater tickets, it doesn't have any tickets.
Unlike payments, purses are not meant to be sent to others. To transfer digital assets, you should withdraw a payment from a purse and send the payment to another party.
You can create a deposit facet for a purse. Deposit facets are either sent to other parties or made publicly known. Any party can deposit a payment into the deposit facet, which deposits it into its associated purse. However, no one can use a deposit facet to either make a withdrawal from its purse or get the purse's balance.
If you have a deposit facet, you make a deposit to its associated purse by calling depositFacet.receive(payment). Note that you add a payment to a purse with a deposit() method, while you add a payment to a depositFacet with a receive() method.
The payment's brand must match that of the purse. Otherwise it throws an error. When sending a deposit facet object to a party, you should tell them what brand it accepts.
The following is a brief description and example of each purse method. For more detail, click the method's name to go to its entry in the ERTP API Reference.
- aPurse.getCurrentAmount()
- Describe the
purse's current balance as an Amount. Note that apursecan be empty. - js
const quatloosPurse = quatloosIssuer.makeEmptyPurse(); // Balance should be 0 Quatloos. const currentBalance = quatloosPurse.getCurrentAmount(); // Deposit a payment of 5 Quatloos quatloosPurse.deposit(quatloosPayment5); // Balance should be 5 Quatloos const newBalance = quatloosPurse.getCurrentAmount();
- Describe the
- aPurse.withdraw(amount)
- Withdraw the
amountof specified digital assets from thispurseinto a newpayment. - js
// Withdraw 3 Quatloos from a purse. const newPayment = quatloosPurse.withdraw(AmountMath.make(brand, 3n));
- Withdraw the
- aPurse.deposit(payment, optAmount)
- Deposit all the contents of
paymentinto thispurse, returning anamountdescribing thepayment. - js
const quatloosPurse = quatloosIssuer.makeEmptyPurse(); const quatloos123 = AmountMath.make(quatloosBrand, 123n); const quatloosPayment = quatloosMint.mintPayment(quatloos123); // Deposit a payment for 123 quatloos into the purse. Ensure that this is the amount you expect. quatloosPurse.deposit(quatloosPayment, quatloos123); const secondPayment = quatloosMint.mintPayment( AmountMath.make(quatloosBrand, 100n), ); // Throws error since secondPayment is 100 Quatloos and quatloos123 is 123 Quatloos t.throws(() => quatloosPurse.deposit(secondPayment, quatloos123), { message: 'amount: {"brand":"[Alleged: quatloos brand]","value":"[100n]"} - Must be: {"brand":"[Alleged: quatloos brand]","value":"[123n]"}', });
- Deposit all the contents of
- aPurse.getDepositFacet()
- Return a deposit-only facet on the
purse. Note that the command to add apayment's assets via aDepositFacetis notdeposit()butreceive()as shown here. - js
const depositOnlyFacet = purse.getDepositFacet(); // Give depositOnlyFacet to someone else. They can pass a payment // that will be deposited: depositOnlyFacet.receive(payment);
- Return a deposit-only facet on the
In addition, the method to create a new, empty, purse is called on an issuer:
- anIssuer.makeEmptyPurse()
- Make and return an empty
pursethat holds assets of thebrandassociated with theissuer. - js
const { issuer: quatloosIssuer } = makeIssuerKit('quatloos'); // The new empty purse contains 0 Quatloos const quatloosPurse = quatloosIssuer.makeEmptyPurse();
- Make and return an empty
Payments
Payments hold digital assets intended to be transferred to another party. They are linear, meaning that either a payment has its full original balance, or it is used up entirely. It is impossible to partially use a payment.
In other words, if you create a payment containing 10 Quatloos, the payment will always either contain 10 Quatloos or it will be deleted from its issuer records and no longer have any value. While a payment can be either combined with others or split into multiple payments, in both cases the original payment(s) are consumed and the results put in one or more new payments.
A payment can be deposited into a purse, split into multiple payments, combined with other payments, or claimed (getting an exclusive payment and revoking access from anyone else).
A payment is often received from other parties, but is not self-verifying and cannot be trusted to provide its own true value. To get the verified balance of a payment, use the getAmountOf(payment) method on the trusted issuer for the payment's brand.
To get the issuer for a brand you didn't create, ask someone you trust. For example, the venue creating tickets for shows can be trusted to give you the tickets' issuer. Or, a friend might have a cryptocurrency they like, and, if you trust them, you might accept that the issuer they give you is valid.
To consume a payment into a new purse:
- Get the
payment's trustedissuer. - Use the
issuerto create an emptypursefor thatbrand. - Deposit the
paymentinto the newpurse.
Payments have only one API method, but many methods for other ERTP components have payments as arguments and effectively operate on a payment. The following is a brief description and example of each payment-related method. For more detail, click the method's name to go to its entry in the ERTP API Reference.
- aPayment.getAllegedBrand()
- Return the
brandindicating the kind of digital asset thispaymentpurports to be and whichissuerto use with it. Becausepaymentsare not trusted, any method calls on them should be treated with suspicion and verified elsewhere. Any successful operation by anissueron apaymentverifies it.
- Return the
Other Objects' Payment-Related Methods
- anIssuer.burn(payment, optAmount)
- Destroy all of the digital assets in the
payment. - js
const amountToBurn = AmountMath.make(quatloosBrand, 10n); const paymentToBurn = quatloosMint.mintPayment(amountToBurn); // burntAmount is 10 quatloos const burntAmount = await quatloosIssuer.burn(paymentToBurn, amountToBurn);
- Destroy all of the digital assets in the
- anIissuer.claim(payment, optAmount)
- Transfer all digital assets from
paymentto a new Payment. - js
const amountToTransfer = AmountMath.make(quatloosBrand, 2n); const originalPayment = quatloosMint.mintPayment(amountToTransfer); const newPayment = await claim( recoveryPurse, originalPayment, amountToTransfer, );
- Transfer all digital assets from
- anIssuer.combine(paymentsArray)
- Combine multiple Payments into one new Payment.
- js
// create an array of 100 payments of 1 unit each const payments = []; for (let i = 0; i < 100; i += 1) { payments.push(quatloosMint.mintPayment(AmountMath.make(quatloosBrand, 1n))); } // combinedPayment equals 100 const combinedPayment = combine(recoveryPurse, harden(payments));
- anIssuer.getAmountOf(payment)
- Describe the
payment's balance as an Amount. - js
const quatloosPayment = quatloosMint.mintPayment( AmountMath.make(quatloosBrand, 100n), ); // returns an amount with a value of 100 and the quatloos brand quatloosIssuer.getAmountOf(quatloosPayment);
- Describe the
- anIssuer.isLive(payment)
- Return
trueif thepaymentwas created by the issuer and is available for use (has not been consumed or burned).
- Return
- anIssuer.split(payment, paymentAmountA)
- Split a single
paymentinto two new Payments. - js
const oldPayment = quatloosMint.mintPayment( AmountMath.make(quatloosBrand, 30n), ); const [paymentA, paymentB] = await split( recoveryPurse, oldPayment, AmountMath.make(quatloosBrand, 10n), ); // paymentA is 10 quatloos, payment B is 20 quatloos.
- Split a single
- anIssuer.splitMany(payment, amountArray)
- Split a single
paymentinto multiple Payments. - js
const oldQuatloosPayment = quatloosMint.mintPayment( AmountMath.make(quatloosBrand, 100n), ); const goodQuatloosAmounts = Array(10).fill( AmountMath.make(quatloosBrand, 10n), ); const arrayOfNewPayments = await splitMany( recoveryPurse, oldQuatloosPayment, harden(goodQuatloosAmounts), );
- Split a single
- aMint.mintPayment(newAmount)
- Create new digital assets of the
mint's associatedbrand. - js
const { mint: quatloosMint, brand: quatloosBrand } = makeIssuerKit( 'quatloos', ); const quatloos1000 = AmountMath.make(quatloosBrand, 1000n); const newPayment = quatloosMint.mintPayment(quatloos1000);
- Create new digital assets of the
- aPurse.deposit(payment, optAmount)
- Deposit all the contents of
paymentintopurse. - js
const quatloosPurse = quatloosIssuer.makeEmptyPurse(); const quatloos123 = AmountMath.make(quatloosBrand, 123n); const quatloosPayment = quatloosMint.mintPayment(quatloos123); // Deposit a payment for 123 quatloos into the purse. Ensure that this is the amount you expect. quatloosPurse.deposit(quatloosPayment, quatloos123); const secondPayment = quatloosMint.mintPayment( AmountMath.make(quatloosBrand, 100n), ); // Throws error since secondPayment is 100 Quatloos and quatloos123 is 123 Quatloos t.throws(() => quatloosPurse.deposit(secondPayment, quatloos123), { message: 'amount: {"brand":"[Alleged: quatloos brand]","value":"[100n]"} - Must be: {"brand":"[Alleged: quatloos brand]","value":"[123n]"}', });
- Deposit all the contents of
- aPurse.getDepositFacet()
- Create and return a new deposit-only facet of the
pursethat allows arbitrary other parties to deposit Payments intopurse. - js
const depositOnlyFacet = purse.getDepositFacet(); // Give depositOnlyFacet to someone else. They can pass a payment // that will be deposited: depositOnlyFacet.receive(payment);
- Create and return a new deposit-only facet of the
- aPurse.withdraw(amount)
- Withdraw the
amountof specified digital assets frompurseinto a newpayment. - js
// Withdraw 3 Quatloos from a purse. const newPayment = quatloosPurse.withdraw(AmountMath.make(brand, 3n));
- Withdraw the
Purse and Payment Example
The following code creates a new purse for the quatloos brand, deposits 10 Quatloos into the purse, withdraws 3 Quatloos from the purse into a payment, and finally returns an amount describing what's currently in the purse, 7 Quatloos.
// Create a purse with a balance of 10 Quatloos
const {
issuer: quatloosIssuer,
mint: quatloosMint,
brand: quatloosBrand,
} = makeIssuerKit('quatloos');
const quatloosPurse = quatloosIssuer.makeEmptyPurse();
const quatloos10 = AmountMath.make(quatloosBrand, 10n);
const quatloosPayment = quatloosMint.mintPayment(quatloos10);
// If the two arguments aren't equal (i.e. both need to be for 10 Quatloos),
// throws an error. But they are both for 10 Quatloos, so no problem.
quatloosPurse.deposit(quatloosPayment, quatloos10);
// Withdraw 3 Quatloos from the purse into a payment
const quatloos3 = AmountMath.make(quatloosBrand, 3n);
const withdrawalPayment = quatloosPurse.withdraw(quatloos3);
// The balance of the withdrawal payment is 3 Quatloos
await quatloosIssuer.getAmountOf(withdrawalPayment);
// The new balance of the purse is 7 Quatloos
quatloosPurse.getCurrentAmount();