@agoric/ertp
Version:
Electronic Rights Transfer Protocol (ERTP). A smart contract framework for exchanging electronic rights
234 lines (209 loc) • 8.19 kB
JavaScript
import { M, matches } from '@agoric/store';
export const BrandShape = M.remotable('Brand');
export const IssuerShape = M.remotable('Issuer');
export const PaymentShape = M.remotable('Payment');
export const PurseShape = M.remotable('Purse');
export const DepositFacetShape = M.remotable('DepositFacet');
const NotifierShape = M.remotable('Notifier');
export const MintShape = M.remotable('Mint');
/**
* When the AmountValue of an Amount fits the NatValueShape, i.e., when it is
* a non-negative bigint, then it represents that many units of the
* fungible asset represented by that amount. The brand of that amount
* should indeed represent a kind of asset consisting of a countable
* set of fungible units.
*/
const NatValueShape = M.nat();
/**
* When the AmountValue of an Amount fits the CopySetValueShape, i.e., when it
* is a CopySet, then it represents the set of those
* keys, where each key represents some individual non-fungible
* item, like a concert ticket, from the non-fungible asset class
* represented by that amount's brand. The amount itself represents
* the set of these items, as opposed to any of the other items
* from the same asset class.
*
* If a given value class represents concert tickets, it seems bizarre
* that we can form amounts of any key. The hard constraint is that
* the code that holds the mint for that asset class---the one associated
* with that brand, only mints the items representing the real units
* of that asset class as defined by it. Anyone else can put together
* an amount expressing, for example, that they "want" some items that
* will never be minted. That want will never be satisfied.
* "You can't always get..."
*/
const CopySetValueShape = M.set();
/**
* When the AmountValue of an Amount fits the SetValueShape, i.e., when it
* is a CopyArray of passable Keys. This representation is deprecated.
*
* @deprecated Please change from using array-based SetValues to CopySet-based
* CopySetValues.
*/
const SetValueShape = M.arrayOf(M.key());
/**
* When the AmountValue of an Amount fits the CopyBagValueShape, i.e., when it
* is a CopyBag, then it represents the bag (multiset) of those
* keys, where each key represents some individual semi-fungible
* item, like a concert ticket, from the semi-fungible asset class
* represented by that amount's brand. The number of times that key
* appears in the bag is the number of fungible units of that key.
* The amount itself represents
* the bag of these items, as opposed to any of the other items
* from the same asset class.
*
* If a given value class represents concert tickets, it seems bizarre
* that we can form amounts of any key. The hard constraint is that
* the code that holds the mint for that asset class---the one associated
* with that brand, only mints the items representing the real units
* of that asset class as defined by it. Anyone else can put together
* an amount expressing, for example, that they "want" some items that
* will never be minted. That want will never be satisfied.
* "You can't always get..."
*/
const CopyBagValueShape = M.bag();
const AmountValueShape = M.or(
NatValueShape,
CopySetValueShape,
SetValueShape,
CopyBagValueShape,
);
export const AmountShape = harden({
brand: BrandShape,
value: AmountValueShape,
});
/**
* Returns true if value is a Nat bigint.
*
* @param {AmountValue} value
* @returns {value is NatValue}
*/
export const isNatValue = value => matches(value, NatValueShape);
harden(isNatValue);
/**
* Returns true if value is a CopySet
*
* @param {AmountValue} value
* @returns {value is CopySetValue}
*/
export const isCopySetValue = value => matches(value, CopySetValueShape);
harden(isCopySetValue);
/**
* Returns true if value is a pass by copy array structure. Does not
* check for duplicates. To check for duplicates, use setMathHelpers.coerce.
*
* @deprecated Please change from using array-based SetValues to CopySet-based
* CopySetValues.
* @param {AmountValue} value
* @returns {value is SetValue}
*/
export const isSetValue = value => matches(value, SetValueShape);
harden(isSetValue);
/**
* Returns true if value is a CopyBag
*
* @param {AmountValue} value
* @returns {value is CopyBagValue}
*/
export const isCopyBagValue = value => matches(value, CopyBagValueShape);
harden(isCopyBagValue);
// One GOOGOLth should be enough decimal places for anybody.
export const MAX_ABSOLUTE_DECIMAL_PLACES = 100;
export const AssetKindShape = M.or('nat', 'set', 'copySet', 'copyBag');
export const DisplayInfoShape = M.partial(
harden({
decimalPlaces: M.and(
M.gte(-MAX_ABSOLUTE_DECIMAL_PLACES),
M.lte(MAX_ABSOLUTE_DECIMAL_PLACES),
),
assetKind: AssetKindShape,
}),
harden({
// Including this empty `rest` ensures that there are no other
// properties beyond those in the `base` record.
}),
);
// //////////////////////// Interfaces /////////////////////////////////////////
export const BrandI = M.interface('Brand', {
isMyIssuer: M.callWhen(M.await(IssuerShape)).returns(M.boolean()),
getAllegedName: M.call().returns(M.string()),
getDisplayInfo: M.call().returns(DisplayInfoShape),
getAmountShape: M.call().returns(M.pattern()),
});
/**
* @param {Pattern} [brandShape]
* @param {Pattern} [assetKindShape]
* @param {Pattern} [amountShape]
*/
export const makeIssuerInterfaces = (
brandShape = BrandShape,
assetKindShape = AssetKindShape,
amountShape = AmountShape,
) => {
const IssuerI = M.interface('Issuer', {
getBrand: M.call().returns(brandShape),
getAllegedName: M.call().returns(M.string()),
getAssetKind: M.call().returns(assetKindShape),
getDisplayInfo: M.call().returns(DisplayInfoShape),
makeEmptyPurse: M.call().returns(PurseShape),
isLive: M.callWhen(M.await(PaymentShape)).returns(M.boolean()),
getAmountOf: M.callWhen(M.await(PaymentShape)).returns(amountShape),
burn: M.callWhen(M.await(PaymentShape))
.optional(M.pattern())
.returns(amountShape),
claim: M.callWhen(M.await(PaymentShape))
.optional(M.pattern())
.returns(PaymentShape),
combine: M.call(M.arrayOf(M.eref(PaymentShape)))
.optional(amountShape)
.returns(M.eref(PaymentShape)),
split: M.callWhen(M.await(PaymentShape), amountShape).returns(
M.arrayOf(PaymentShape),
),
splitMany: M.callWhen(
M.await(PaymentShape),
M.arrayOf(amountShape),
).returns(M.arrayOf(PaymentShape)),
});
const MintI = M.interface('Mint', {
getIssuer: M.call().returns(IssuerShape),
mintPayment: M.call(amountShape).returns(PaymentShape),
});
const PaymentI = M.interface('Payment', {
getAllegedBrand: M.call().returns(brandShape),
});
const PurseI = M.interface('Purse', {
getAllegedBrand: M.call().returns(brandShape),
getCurrentAmount: M.call().returns(amountShape),
getCurrentAmountNotifier: M.call().returns(NotifierShape),
// PurseI does *not* delay `deposit` until `srcPayment` is fulfulled.
// Rather, the semantics of `deposit` require it to provide its
// callers with a strong guarantee that `deposit` messages are
// processed without further delay in the order they arrive.
// PurseI therefore requires that the `srcPayment` argument already
// be a remotable, not a promise.
// PurseI only calls this raw method after validating that
// `srcPayment` is a remotable, leaving it
// to this raw method to validate that this remotable is actually
// a live payment of the correct brand with sufficient funds.
deposit: M.call(PaymentShape).optional(M.pattern()).returns(amountShape),
getDepositFacet: M.call().returns(DepositFacetShape),
withdraw: M.call(amountShape).returns(PaymentShape),
getRecoverySet: M.call().returns(M.setOf(PaymentShape)),
recoverAll: M.call().returns(amountShape),
});
const DepositFacetI = M.interface('DepositFacet', {
receive: PurseI.methodGuards.deposit,
});
const PurseIKit = harden({
purse: PurseI,
depositFacet: DepositFacetI,
});
return harden({
IssuerI,
MintI,
PaymentI,
PurseIKit,
});
};
harden(makeIssuerInterfaces);