Skip to content

Commit

Permalink
service ui
Browse files Browse the repository at this point in the history
  • Loading branch information
mujahidkay authored and rabi-siddique committed Sep 5, 2024
1 parent d8ac30e commit e89f98d
Show file tree
Hide file tree
Showing 16 changed files with 521 additions and 148 deletions.
1 change: 1 addition & 0 deletions contract/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
},
"dependencies": {
"@agoric/ertp": "^0.16.3-u12.0",
"@agoric/time": "^0.3.3-u16.0",
"@agoric/zoe": "^0.26.3-u12.0",
"@endo/far": "^0.2.22",
"@endo/marshal": "^0.8.9",
Expand Down
13 changes: 11 additions & 2 deletions contract/src/offer-up-proposal.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ const publishBrandInfo = async (chainStorage, board, brand) => {
export const startOfferUpContract = async permittedPowers => {
console.error('startOfferUpContract()...');
const {
consume: { board, chainStorage, startUpgradable, zoe },
consume: {
board,
chainStorage,
startUpgradable,
zoe,
chainTimerService: chainTimerServiceP,
},
brand: {
consume: { IST: istBrandP },
// @ts-expect-error dynamic extension to promise space
Expand All @@ -63,9 +69,11 @@ export const startOfferUpContract = async permittedPowers => {

const istIssuer = await istIssuerP;
const istBrand = await istBrandP;
const timerService = await await chainTimerServiceP;

const terms = {
subscriptionPrice: AmountMath.make(istBrand, 500n),
subscriptionPrice: AmountMath.make(istBrand, 10000000n),
timerService,
};

// agoricNames gets updated each time; the promise space only once XXXXXXX
Expand Down Expand Up @@ -106,6 +114,7 @@ const offerUpManifest = {
chainStorage: true, // to publish boardAux info for NFT brand
startUpgradable: true, // to start contract and save adminFacet
zoe: true, // to get contract terms, including issuer/brand
chainTimerService: true,
},
installation: { consume: { offerUp: true } },
issuer: { consume: { IST: true }, produce: { Item: true } },
Expand Down
62 changes: 29 additions & 33 deletions contract/src/offer-up.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*/
// @ts-check

import { Far } from '@endo/far';
import { Far, E } from '@endo/far';
import { AmountMath, AssetKind } from '@agoric/ertp/src/amountMath.js';
import { makeCopyBag, M } from '@endo/patterns';
import { atomicRearrange } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
Expand All @@ -31,6 +31,7 @@ import '@agoric/zoe/exported.js';
* optionally, a maximum number of items sold for that price (default: 3).
*
* @typedef {{
* timerService: any;
* subscriptionPrice: Amount;
* subscriptionPeriod?: string;
* servicesToAvail?: Array<string>;
Expand All @@ -46,16 +47,19 @@ import '@agoric/zoe/exported.js';
*/
export const start = async zcf => {
const {
timerService,
subscriptionPrice,
subscriptionPeriod = 'MONTHLY',

Check failure on line 52 in contract/src/offer-up.contract.js

View workflow job for this annotation

GitHub Actions / unit (18)

'subscriptionPeriod' is assigned a value but never used. Allowed unused vars must match /^_/u
servicesToAvail = ['Netflix', 'Amazon', 'HboMax', 'Disney'],
} = zcf.getTerms();


const subscriptionResources = {}
const subscriptionResources = {};

servicesToAvail.forEach(element => {

Check warning on line 58 in contract/src/offer-up.contract.js

View workflow job for this annotation

GitHub Actions / unit (18)

Prefer for...of instead of Array.forEach
subscriptionResources[element] = [`${element}_Movie_1`, `${element}_Movie_2`]
subscriptionResources[element] = [
`${element}_Movie_1`,
`${element}_Movie_2`,
];
});

/**
Expand Down Expand Up @@ -87,20 +91,18 @@ export const start = async zcf => {

const subscriptions = new Map();


/** @type {OfferHandler} */
const tradeHandler = async (buyerSeat, offerArgs) => {

// @ts-ignore
const userAddress = offerArgs.userAddress;
// @ts-ignore
const serviceType = offerArgs.serviceType;
const currentTimeRecord = await E(timerService).getCurrentTimestamp();



// prepareExpiryTime from time service (current time + 30 days)

const amountObject = AmountMath.make(brand, makeCopyBag([[{ expiryTime: '123', serviceType }, 1n]]))
const amountObject = AmountMath.make(
brand,
makeCopyBag([[{ serviceStarted: currentTimeRecord, serviceType }, 1n]]),
);
const want = { Items: amountObject };

const newSubscription = itemMint.mintGains(want);
Expand Down Expand Up @@ -137,37 +139,31 @@ export const start = async zcf => {
proposalShape,
);

const isSubscriptionValid = (userSubscription) => {
const isSubscriptionValid = userSubscription => {
if (!userSubscription || !userSubscription.value.payload) return false;

if (!userSubscription || !userSubscription.value.payload)
return false
const serviceStarted = userSubscription.value.payload[0][0].serviceStarted;

const expiryTime = userSubscription.value.payload[0][0].expiryTime

// Here we'll check with current time from time service. The expiryTime should be greater than current time
if (!expiryTime || expiryTime !== '123')
return false
// Here we'll check with current time from time service.
if (!serviceStarted || serviceStarted !== '123') return false;
return true;
//
}
//
};

const getSubscriptionResources = (userAddress) => {
const getSubscriptionResources = userAddress => {
const userSubscription = subscriptions.get(userAddress);


const isValidSub = isSubscriptionValid(userSubscription);
if (isValidSub) {
// User has a valid subscription, return the resources
const serviceType = userSubscription.value.payload[0][0].serviceType
return subscriptionResources[serviceType];
} else {
// User doesn't have a valid subscription
return 'Access denied: You do not have a valid subscription.';
}

if (isValidSub) {
// User has a valid subscription, return the resources
const serviceType = userSubscription.value.payload[0][0].serviceType;
return subscriptionResources[serviceType];
} else {
// User doesn't have a valid subscription
return 'Access denied: You do not have a valid subscription.';
}
};


// Mark the publicFacet Far, i.e. reachable from outside the contract
const publicFacet = Far('Items Public Facet', {
makeTradeInvitation,
Expand Down
52 changes: 35 additions & 17 deletions contract/test/test-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

/* eslint-disable import/order -- https://github.com/endojs/endo/issues/1235 */
import { test as anyTest } from './prepare-test-env-ava.js';

import buildZoeManualTimer from '@agoric/zoe/tools/manualTimer.js';
import { createRequire } from 'module';
import { E, Far } from '@endo/far';
import { makePromiseKit } from '@endo/promise-kit';
Expand Down Expand Up @@ -62,7 +62,12 @@ test('Start the contract', async t => {

const money = makeIssuerKit('PlayMoney');
const issuers = { Price: money.issuer };
const terms = { subscriptionPrice: AmountMath.make(money.brand, 500n) };
const timer = buildZoeManualTimer();
const terms = {
subscriptionPrice: AmountMath.make(money.brand, 10000000n),
timerService: timer,
};

t.log('terms:', terms);

/** @type {ERef<Installation<AssetContractFn>>} */
Expand All @@ -77,17 +82,20 @@ test('Start the contract', async t => {
*
* @param {import('ava').ExecutionContext} t
* @param {ZoeService} zoe
* @param {ERef<import('@agoric/zoe/src/zoeService/utils').Instance<AssetContractFn>} instance
* @param {ERef<import('@agoric/zoe/src/zoeService/utils').Instance<AssetContractFn>>} instance
* @param {Purse} purse
*/
const alice = async (t, zoe, instance, purse) => {
const publicFacet = E(zoe).getPublicFacet(instance);
// @ts-expect-error Promise<Instance> seems to work
const terms = await E(zoe).getTerms(instance);
const { issuers, brands, subscriptionPrice } = terms;
const { issuers, brands, subscriptionPrice, timerService } = terms;

const serviceType = 'Netflix'
const choiceBag = makeCopyBag([[{ expiryTime: '123', serviceType }, 1n]]);
const currentTimeRecord = await E(timerService).getCurrentTimestamp();
const serviceType = 'Netflix';
const choiceBag = makeCopyBag([
[{ serviceStarted: currentTimeRecord, serviceType }, 1n],
]);

const proposal = {
give: { Price: subscriptionPrice },
Expand All @@ -100,35 +108,44 @@ const alice = async (t, zoe, instance, purse) => {
const toTrade = E(publicFacet).makeTradeInvitation();

const userAddress = 'agoric123456';

const seat = E(zoe).offer(toTrade, proposal, { Price: pmt }, { userAddress, serviceType });
const seat = E(zoe).offer(
toTrade,
proposal,
{ Price: pmt },
{ userAddress, serviceType },
);
const items = await E(seat).getPayout('Items');

const actual = await E(issuers.Item).getAmountOf(items);
t.log('Alice payout brand', actual.brand);
t.log('Alice payout value', actual.value);
t.deepEqual(actual, proposal.want.Items);

const actualMovies = [`${serviceType}_Movie_1`, `${serviceType}_Movie_2`]
const subscriptionMovies = await E(publicFacet).getSubscriptionResources(userAddress)
const actualMovies = [`${serviceType}_Movie_1`, `${serviceType}_Movie_2`];
const subscriptionMovies =
await E(publicFacet).getSubscriptionResources(userAddress);

t.deepEqual(actualMovies, subscriptionMovies)
t.deepEqual(actualMovies, subscriptionMovies);
};

test('Alice trades: give some play money, want subscription', async t => {
const { zoe, bundle } = t.context;

const money = makeIssuerKit('PlayMoney');
const issuers = { Price: money.issuer };
const terms = { subscriptionPrice: AmountMath.make(money.brand, 500n) };
const timer = buildZoeManualTimer();
const terms = {
subscriptionPrice: AmountMath.make(money.brand, 10000000n),
timerService: timer,
};
/** @type {ERef<Installation<AssetContractFn>>} */
const installation = E(zoe).install(bundle);
const { instance } = await E(zoe).startInstance(installation, issuers, terms);
t.log(instance);
t.is(typeof instance, 'object');

const alicePurse = money.issuer.makeEmptyPurse();
const amountOfMoney = AmountMath.make(money.brand, 500n);
const amountOfMoney = AmountMath.make(money.brand, 10000000n);
const moneyPayment = money.mint.mintPayment(amountOfMoney);
alicePurse.deposit(moneyPayment);
await alice(t, zoe, instance, alicePurse);
Expand All @@ -146,18 +163,19 @@ test('Trade in IST rather than play money', async t => {
const installation = E(zoe).install(bundle);
const feeIssuer = await E(zoe).getFeeIssuer();
const feeBrand = await E(feeIssuer).getBrand();
const subscriptionPrice = AmountMath.make(feeBrand, 500n);
const subscriptionPrice = AmountMath.make(feeBrand, 10000000n);
const timer = buildZoeManualTimer();
return E(zoe).startInstance(
installation,
{ Price: feeIssuer },
{ subscriptionPrice },
{ subscriptionPrice, timerService: timer },
);
};

const { zoe, bundle, bundleCache, feeMintAccess } = t.context;
const { instance } = await startContract({ zoe, bundle });
const { faucet } = makeStableFaucet({ bundleCache, feeMintAccess, zoe });
await alice(t, zoe, instance, await faucet(5n * UNIT6));
await alice(t, zoe, instance, await faucet(10n * UNIT6));
});

test('use the code that will go on chain to start the contract', async t => {
Expand Down Expand Up @@ -231,5 +249,5 @@ test('use the code that will go on chain to start the contract', async t => {
// Now that we have the instance, resume testing as above.
const { feeMintAccess, bundleCache } = t.context;
const { faucet } = makeStableFaucet({ bundleCache, feeMintAccess, zoe });
await alice(t, zoe, instance, await faucet(5n * UNIT6));
await alice(t, zoe, instance, await faucet(10n * UNIT6));
});
4 changes: 0 additions & 4 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@
"vitest": "^1.2.1",
"zustand": "^4.4.1"
},
"resolutions": {
"**/ses": "^1.3.0",
"**/@agoric/xsnap": "0.14.3-dev-9f085d3.0"
},
"prettier": {
"trailingComma": "all",
"arrowParens": "avoid",
Expand Down
12 changes: 8 additions & 4 deletions ui/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,15 @@
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
.selected {
outline: 2px solid #007bff;
}

.card {
padding: 1em;
}


.read-the-docs {
color: #888;
}
Expand All @@ -54,6 +53,11 @@
margin: 10px;
}

.service {
height: 48px;
width: 48px;
}

.trade {
display: flex;
flex-direction: column;
Expand Down
27 changes: 19 additions & 8 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import { subscribeLatest } from '@agoric/notifier';
import { makeCopyBag } from '@agoric/store';
import { Logos } from './components/Logos';
import { Inventory } from './components/Inventory';
import { Trade } from './components/Trade';
import { Subscribe } from './components/Trade';

import { AmountMath } from '@agoric/ertp';

const { entries, fromEntries } = Object;

Expand Down Expand Up @@ -86,15 +88,21 @@ const connectWallet = async () => {
}
};

const makeOffer = (giveValue: bigint, wantChoices: Record<string, bigint>) => {
const makeOffer = (giveValue: bigint, wantChoice: string) => {
const { wallet, offerUpInstance, brands } = useAppStore.getState();
if (!offerUpInstance) throw Error('no contract instance');
if (!(brands && brands.IST && brands.Item))
throw Error('brands not available');

const value = makeCopyBag(entries(wantChoices));
const want = { Items: { brand: brands.Item, value } };
const give = { Price: { brand: brands.IST, value: giveValue } };
const choiceBag = makeCopyBag([
[{ expiryTime: '123', serviceType: wantChoice }, 1n],
]);

// want: { Items: AmountMath.make(brands.Item, choiceBag) }

// const value = makeCopyBag(entries(wantChoices));
const want = { Items: AmountMath.make(brands.Item, choiceBag) };
const give = { Price: AmountMath.make(brands.IST, 10000000n) };

wallet?.makeOffer(
{
Expand All @@ -103,7 +111,10 @@ const makeOffer = (giveValue: bigint, wantChoices: Record<string, bigint>) => {
publicInvitationMaker: 'makeTradeInvitation',
},
{ give, want },
undefined,
{
userAddress: wallet.address,
serviceType: wantChoice,
},
(update: { status: string; data?: unknown }) => {
if (update.status === 'error') {
alert(`Offer error: ${update.data}`);
Expand Down Expand Up @@ -145,10 +156,10 @@ function App() {
return (
<>
<Logos />
<h1>Items Listed on Offer Up</h1>
<h1>All-in-One Subscription Service</h1>

<div className="card">
<Trade
<Subscribe
makeOffer={makeOffer}
istPurse={istPurse as Purse}
walletConnected={!!wallet}
Expand Down
Loading

0 comments on commit e89f98d

Please sign in to comment.