Skip to content

Commit

Permalink
feat(wallet): Make purses observable (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
jfparadis authored Nov 2, 2019
1 parent a1298dd commit 53e56fe
Showing 1 changed file with 196 additions and 84 deletions.
280 changes: 196 additions & 84 deletions lib/ag-solo/vats/lib-wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,52 @@ export async function makeWallet(
inboxStateChangeHandler(getInboxState());
}

function makeObservablePurse(purse, onFulfilled) {
return {
getName() {
return E(purse).getName();
},
getAssay() {
return E(purse).getAssay();
},
getBalance() {
return E(purse).getBalance();
},
depositExactly(...args) {
return E(purse)
.depositExactly(...args)
.then(result => {
onFulfilled();
return result;
});
},
depositAll(...args) {
return E(purse)
.depositAll(...args)
.then(result => {
onFulfilled();
return result;
});
},
withdraw(...args) {
return E(purse)
.withdraw(...args)
.then(result => {
onFulfilled();
return result;
});
},
withdrawAll(...args) {
return E(purse)
.withdrawAll(...args)
.then(result => {
onFulfilled();
return result;
});
},
};
}

function checkOrder(a0, a1, b0, b1) {
if (a0 === b0 && a1 === b1) {
return true;
Expand All @@ -63,30 +109,14 @@ export async function makeWallet(
}

async function makeOffer(date) {
let offerOk = false;

const {
meta: { purseName0, purseName1, instanceId },
offerRules,
} = dateToOfferRec.get(date);

// TODO balance check
// use unit/ops purse balance doen't include units
// return if purse balance is not > amount to withdraw
// if (!unitOps.includes(purse.getBalance(), unitsToWithdraw) return
// unitOps = assay.getUnitOps()
// if (purse0.getBalance() < extent0) return; // todo message

const instanceHandle = await E(registrar).get(instanceId);

// Get the assays in the contract.
// Get the contract instance.
const {
terms: { assays: contractAssays },
instance,
} = await E(zoe).getInstance(instanceHandle);
const purse0 = nameToPurse.get(purseName0);
const purse1 = nameToPurse.get(purseName1);

// Extract extents and assayIds from the offer rules skeleton.
const {
payoutRules: [
{
Expand All @@ -101,115 +131,197 @@ export async function makeWallet(
],
} = offerRules;

// Find the correcponsing assays by id in the registrar.
const registryAssays = await Promise.all([
E(registrar).get(assayId0),
E(registrar).get(assayId1),
const instanceHandleP = E(registrar).get(instanceId);
const regAssay0P = E(registrar).get(assayId0);
const regAssay1P = E(registrar).get(assayId1);
const purseAssay0P = E(purse0).getAssay();
const purseAssay1P = E(purse1).getAssay();
const purseUnit0P = E(purseAssay0P).makeUnits(extent0 || 0);
const purseUnit1P = E(purseAssay0P).makeUnits(extent1 || 0);

const [
instanceHandle,
regAssay0,
regAssay1,
purseAssay0,
purseAssay1,
purseUnit0,
purseUnit1,
] = await Promise.all([
instanceHandleP,
regAssay0P,
regAssay1P,
purseAssay0P,
purseAssay1P,
purseUnit0P,
purseUnit1P,
]);

// Reconstruct the units.
const units = await Promise.all([
E(registryAssays[0]).makeUnits(extent0 || 0), // use from registry
E(registryAssays[1]).makeUnits(extent1 || 0), // use from registry
E(contractAssays[2]).makeUnits(extent2 || 0), // default from contract
// =====================
// === AWAITING TURN ===
// =====================

const {
terms: {
assays: [contractAssay0, contractAssay1, contractAssay2],
},
instance,
} = await E(zoe).getInstance(instanceHandle);

// =====================
// === AWAITING TURN ===
// =====================

insist(contractAssay0 === regAssay0 && regAssay1 === contractAssay1);

// Check whether we sell on contract assay 0 or 1.
const normal = checkOrder(
purseAssay0,
purseAssay1,
contractAssay0,
contractAssay1,
);

const payment0P = E(purse0).withdraw(normal ? purseUnit0 : purseUnit1);
const contractUnit0P = E(contractAssay0).makeUnits(extent0 || 0);
const contractUnit1P = E(contractAssay1).makeUnits(extent1 || 0);
const contractUnit2P = E(contractAssay2).makeUnits(extent2 || 0);

const [
payment0,
contractUnit0,
contractUnit1,
contractUnit2,
] = await Promise.all([
payment0P,
contractUnit0P,
contractUnit1P,
contractUnit2P,
]);

// Clone the offer rules to make them writable.
// =====================
// === AWAITING TURN ===
// =====================

// Clone the offer rules to have a writable object.
const newOfferRules = JSON.parse(JSON.stringify(offerRules));

// Hydrate by deconstruction.
[
newOfferRules.payoutRules[0].units,
newOfferRules.payoutRules[1].units,
newOfferRules.payoutRules[2].units,
] = units;
// Hydrate with the resolved units.
newOfferRules.payoutRules[0].units = contractUnit0;
newOfferRules.payoutRules[1].units = contractUnit1;
newOfferRules.payoutRules[2].units = contractUnit2;
harden(newOfferRules);

const purse0 = nameToPurse.get(purseName0);
const purse1 = nameToPurse.get(purseName1);
const purseAssays = await Promise.all([
E(purse0).getAssay(),
E(purse1).getAssay(),
const payment = normal
? [payment0, undefined, undefined]
: [undefined, payment0, undefined];

const { escrowReceipt, payout: payoutP } = await E(zoe).escrow(
newOfferRules,
payment,
);

// =====================
// === AWAITING TURN ===
// =====================

// IMPORTANT: payout will resolve only once makeOffer()
// resolves, so we technically only need to await on
// payout. For readability of the code, and to ease any
// eventual debugging, we await on both: it is not
// obvious that both are joined internally, and stumbling
// over a naked non-awaited invocation of E() would appear
// as an error.

const [offerOk, payout] = await Promise.all([
E(instance).makeOffer(escrowReceipt),
payoutP,
]);

try {
const normal = checkOrder(
purseAssays[0],
purseAssays[1],
registryAssays[0],
registryAssays[1],
);

const payment0 = await E(purse0).withdraw(normal ? units[0] : units[1]);
const payments = [
normal ? payment0 : undefined,
normal ? undefined : payment0,
];

const { escrowReceipt, payout } = await E(zoe).escrow(
newOfferRules,
payments,
);

offerOk = await E(instance).makeOffer(escrowReceipt);

if (offerOk) {
const [payout0, payout1] = await payout;
await Promise.all([
E(purse0).depositAll(normal ? payout0 : payout1),
E(purse1).depositAll(normal ? payout1 : payout0),
]);
}
} catch (e) {
// if balance > empty payment has not been claimed.
const recoveryPayment = purseAssays[0].claimAll();
await E(purse0).depositAll(recoveryPayment);
}
// =====================
// === AWAITING TURN ===
// =====================

updatePursesState(purseName0, purse0);
updatePursesState(purseName1, purse1);
const deposit0P = E(purse0).depositAll(payout[normal ? 0 : 1]);
const deposit1P = E(purse1).depositAll(payout[normal ? 1 : 0]);

await Promise.all([deposit0P, deposit1P]);

// =====================
// === AWAITING TURN ===
// =====================

// updatePursesState(purseName0, purse0);
// updatePursesState(purseName1, purse1);

return offerOk;
}

// === API

async function addPurse(purse) {
const purseName = await E(purse).getName();
const purseNameP = E(purse).getName();
const assayP = E(purse).getAssay();
const labelP = E(assayP).getLabel();

const [purseName, assay, { allegedName }] = await Promise.all([
purseNameP,
assayP,
labelP,
]);

// =====================
// === AWAITING TURN ===
// =====================

insist(!nameToPurse.has(purseName))`Purse name already used in wallet.`;

const assay = await E(purse).getAssay();
const { allegedName } = await E(assay).getLabel();
const assayId = await E(registrar).register(allegedName, assay);

nameToPurse.set(purseName, purse);
// =====================
// === AWAITING TURN ===
// =====================

// IMPORTANT: once wrapped, the original purse shoudl never
// be used otherwise the UI state will be out of sync.

const observablePurse = makeObservablePurse(purse, () =>
updatePursesState(purseName, purse),
);

nameToPurse.set(purseName, observablePurse);
nameToAssayId.set(purseName, assayId);

await updatePursesState(purseName, purse);
updatePursesState(purseName, purse);
}

function getPurses() {
return harden([...nameToPurse.values()]);
}

async function addOffer(offerRec) {
function addOffer(offerRec) {
const {
meta: { date },
} = offerRec;
dateToOfferRec.set(date, offerRec);
await updateInboxState(date, offerRec);
updateInboxState(date, offerRec);
}

function declineOffer(date) {
const { meta } = dateToOfferRec.get(date);
// Update status, drop the offerRules
const declineedOfferRec = { meta: { ...meta, status: 'decline' } };
dateToOfferRec.set(date, declineedOfferRec);
updateInboxState(date, declineedOfferRec);
const declinedOfferRec = { meta: { ...meta, status: 'decline' } };
dateToOfferRec.set(date, declinedOfferRec);
updateInboxState(date, declinedOfferRec);
}

async function acceptOffer(date) {
const offerOk = await makeOffer(date);

// =====================
// === AWAITING TURN ===
// =====================

if (!offerOk) return;

const { meta } = dateToOfferRec.get(date);
Expand Down

0 comments on commit 53e56fe

Please sign in to comment.