Skip to content

Commit e89f98d

Browse files
mujahidkayrabi-siddique
authored andcommitted
service ui
1 parent d8ac30e commit e89f98d

16 files changed

+521
-148
lines changed

contract/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
},
4848
"dependencies": {
4949
"@agoric/ertp": "^0.16.3-u12.0",
50+
"@agoric/time": "^0.3.3-u16.0",
5051
"@agoric/zoe": "^0.26.3-u12.0",
5152
"@endo/far": "^0.2.22",
5253
"@endo/marshal": "^0.8.9",

contract/src/offer-up-proposal.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@ const publishBrandInfo = async (chainStorage, board, brand) => {
4141
export const startOfferUpContract = async permittedPowers => {
4242
console.error('startOfferUpContract()...');
4343
const {
44-
consume: { board, chainStorage, startUpgradable, zoe },
44+
consume: {
45+
board,
46+
chainStorage,
47+
startUpgradable,
48+
zoe,
49+
chainTimerService: chainTimerServiceP,
50+
},
4551
brand: {
4652
consume: { IST: istBrandP },
4753
// @ts-expect-error dynamic extension to promise space
@@ -63,9 +69,11 @@ export const startOfferUpContract = async permittedPowers => {
6369

6470
const istIssuer = await istIssuerP;
6571
const istBrand = await istBrandP;
72+
const timerService = await await chainTimerServiceP;
6673

6774
const terms = {
68-
subscriptionPrice: AmountMath.make(istBrand, 500n),
75+
subscriptionPrice: AmountMath.make(istBrand, 10000000n),
76+
timerService,
6977
};
7078

7179
// agoricNames gets updated each time; the promise space only once XXXXXXX
@@ -106,6 +114,7 @@ const offerUpManifest = {
106114
chainStorage: true, // to publish boardAux info for NFT brand
107115
startUpgradable: true, // to start contract and save adminFacet
108116
zoe: true, // to get contract terms, including issuer/brand
117+
chainTimerService: true,
109118
},
110119
installation: { consume: { offerUp: true } },
111120
issuer: { consume: { IST: true }, produce: { Item: true } },

contract/src/offer-up.contract.js

+29-33
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020
// @ts-check
2121

22-
import { Far } from '@endo/far';
22+
import { Far, E } from '@endo/far';
2323
import { AmountMath, AssetKind } from '@agoric/ertp/src/amountMath.js';
2424
import { makeCopyBag, M } from '@endo/patterns';
2525
import { atomicRearrange } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
@@ -31,6 +31,7 @@ import '@agoric/zoe/exported.js';
3131
* optionally, a maximum number of items sold for that price (default: 3).
3232
*
3333
* @typedef {{
34+
* timerService: any;
3435
* subscriptionPrice: Amount;
3536
* subscriptionPeriod?: string;
3637
* servicesToAvail?: Array<string>;
@@ -46,16 +47,19 @@ import '@agoric/zoe/exported.js';
4647
*/
4748
export const start = async zcf => {
4849
const {
50+
timerService,
4951
subscriptionPrice,
5052
subscriptionPeriod = 'MONTHLY',
5153
servicesToAvail = ['Netflix', 'Amazon', 'HboMax', 'Disney'],
5254
} = zcf.getTerms();
5355

54-
55-
const subscriptionResources = {}
56+
const subscriptionResources = {};
5657

5758
servicesToAvail.forEach(element => {
58-
subscriptionResources[element] = [`${element}_Movie_1`, `${element}_Movie_2`]
59+
subscriptionResources[element] = [
60+
`${element}_Movie_1`,
61+
`${element}_Movie_2`,
62+
];
5963
});
6064

6165
/**
@@ -87,20 +91,18 @@ export const start = async zcf => {
8791

8892
const subscriptions = new Map();
8993

90-
9194
/** @type {OfferHandler} */
9295
const tradeHandler = async (buyerSeat, offerArgs) => {
93-
9496
// @ts-ignore
9597
const userAddress = offerArgs.userAddress;
9698
// @ts-ignore
9799
const serviceType = offerArgs.serviceType;
100+
const currentTimeRecord = await E(timerService).getCurrentTimestamp();
98101

99-
100-
101-
// prepareExpiryTime from time service (current time + 30 days)
102-
103-
const amountObject = AmountMath.make(brand, makeCopyBag([[{ expiryTime: '123', serviceType }, 1n]]))
102+
const amountObject = AmountMath.make(
103+
brand,
104+
makeCopyBag([[{ serviceStarted: currentTimeRecord, serviceType }, 1n]]),
105+
);
104106
const want = { Items: amountObject };
105107

106108
const newSubscription = itemMint.mintGains(want);
@@ -137,37 +139,31 @@ export const start = async zcf => {
137139
proposalShape,
138140
);
139141

140-
const isSubscriptionValid = (userSubscription) => {
142+
const isSubscriptionValid = userSubscription => {
143+
if (!userSubscription || !userSubscription.value.payload) return false;
141144

142-
if (!userSubscription || !userSubscription.value.payload)
143-
return false
145+
const serviceStarted = userSubscription.value.payload[0][0].serviceStarted;
144146

145-
const expiryTime = userSubscription.value.payload[0][0].expiryTime
146-
147-
// Here we'll check with current time from time service. The expiryTime should be greater than current time
148-
if (!expiryTime || expiryTime !== '123')
149-
return false
147+
// Here we'll check with current time from time service.
148+
if (!serviceStarted || serviceStarted !== '123') return false;
150149
return true;
151-
//
152-
}
150+
//
151+
};
153152

154-
const getSubscriptionResources = (userAddress) => {
153+
const getSubscriptionResources = userAddress => {
155154
const userSubscription = subscriptions.get(userAddress);
156155

157-
158156
const isValidSub = isSubscriptionValid(userSubscription);
159-
if (isValidSub) {
160-
// User has a valid subscription, return the resources
161-
const serviceType = userSubscription.value.payload[0][0].serviceType
162-
return subscriptionResources[serviceType];
163-
} else {
164-
// User doesn't have a valid subscription
165-
return 'Access denied: You do not have a valid subscription.';
166-
}
167-
157+
if (isValidSub) {
158+
// User has a valid subscription, return the resources
159+
const serviceType = userSubscription.value.payload[0][0].serviceType;
160+
return subscriptionResources[serviceType];
161+
} else {
162+
// User doesn't have a valid subscription
163+
return 'Access denied: You do not have a valid subscription.';
164+
}
168165
};
169166

170-
171167
// Mark the publicFacet Far, i.e. reachable from outside the contract
172168
const publicFacet = Far('Items Public Facet', {
173169
makeTradeInvitation,

contract/test/test-contract.js

+35-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
/* eslint-disable import/order -- https://github.com/endojs/endo/issues/1235 */
77
import { test as anyTest } from './prepare-test-env-ava.js';
8-
8+
import buildZoeManualTimer from '@agoric/zoe/tools/manualTimer.js';
99
import { createRequire } from 'module';
1010
import { E, Far } from '@endo/far';
1111
import { makePromiseKit } from '@endo/promise-kit';
@@ -62,7 +62,12 @@ test('Start the contract', async t => {
6262

6363
const money = makeIssuerKit('PlayMoney');
6464
const issuers = { Price: money.issuer };
65-
const terms = { subscriptionPrice: AmountMath.make(money.brand, 500n) };
65+
const timer = buildZoeManualTimer();
66+
const terms = {
67+
subscriptionPrice: AmountMath.make(money.brand, 10000000n),
68+
timerService: timer,
69+
};
70+
6671
t.log('terms:', terms);
6772

6873
/** @type {ERef<Installation<AssetContractFn>>} */
@@ -77,17 +82,20 @@ test('Start the contract', async t => {
7782
*
7883
* @param {import('ava').ExecutionContext} t
7984
* @param {ZoeService} zoe
80-
* @param {ERef<import('@agoric/zoe/src/zoeService/utils').Instance<AssetContractFn>} instance
85+
* @param {ERef<import('@agoric/zoe/src/zoeService/utils').Instance<AssetContractFn>>} instance
8186
* @param {Purse} purse
8287
*/
8388
const alice = async (t, zoe, instance, purse) => {
8489
const publicFacet = E(zoe).getPublicFacet(instance);
8590
// @ts-expect-error Promise<Instance> seems to work
8691
const terms = await E(zoe).getTerms(instance);
87-
const { issuers, brands, subscriptionPrice } = terms;
92+
const { issuers, brands, subscriptionPrice, timerService } = terms;
8893

89-
const serviceType = 'Netflix'
90-
const choiceBag = makeCopyBag([[{ expiryTime: '123', serviceType }, 1n]]);
94+
const currentTimeRecord = await E(timerService).getCurrentTimestamp();
95+
const serviceType = 'Netflix';
96+
const choiceBag = makeCopyBag([
97+
[{ serviceStarted: currentTimeRecord, serviceType }, 1n],
98+
]);
9199

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

102110
const userAddress = 'agoric123456';
103-
104-
const seat = E(zoe).offer(toTrade, proposal, { Price: pmt }, { userAddress, serviceType });
111+
const seat = E(zoe).offer(
112+
toTrade,
113+
proposal,
114+
{ Price: pmt },
115+
{ userAddress, serviceType },
116+
);
105117
const items = await E(seat).getPayout('Items');
106118

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

112-
const actualMovies = [`${serviceType}_Movie_1`, `${serviceType}_Movie_2`]
113-
const subscriptionMovies = await E(publicFacet).getSubscriptionResources(userAddress)
124+
const actualMovies = [`${serviceType}_Movie_1`, `${serviceType}_Movie_2`];
125+
const subscriptionMovies =
126+
await E(publicFacet).getSubscriptionResources(userAddress);
114127

115-
t.deepEqual(actualMovies, subscriptionMovies)
128+
t.deepEqual(actualMovies, subscriptionMovies);
116129
};
117130

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

121134
const money = makeIssuerKit('PlayMoney');
122135
const issuers = { Price: money.issuer };
123-
const terms = { subscriptionPrice: AmountMath.make(money.brand, 500n) };
136+
const timer = buildZoeManualTimer();
137+
const terms = {
138+
subscriptionPrice: AmountMath.make(money.brand, 10000000n),
139+
timerService: timer,
140+
};
124141
/** @type {ERef<Installation<AssetContractFn>>} */
125142
const installation = E(zoe).install(bundle);
126143
const { instance } = await E(zoe).startInstance(installation, issuers, terms);
127144
t.log(instance);
128145
t.is(typeof instance, 'object');
129146

130147
const alicePurse = money.issuer.makeEmptyPurse();
131-
const amountOfMoney = AmountMath.make(money.brand, 500n);
148+
const amountOfMoney = AmountMath.make(money.brand, 10000000n);
132149
const moneyPayment = money.mint.mintPayment(amountOfMoney);
133150
alicePurse.deposit(moneyPayment);
134151
await alice(t, zoe, instance, alicePurse);
@@ -146,18 +163,19 @@ test('Trade in IST rather than play money', async t => {
146163
const installation = E(zoe).install(bundle);
147164
const feeIssuer = await E(zoe).getFeeIssuer();
148165
const feeBrand = await E(feeIssuer).getBrand();
149-
const subscriptionPrice = AmountMath.make(feeBrand, 500n);
166+
const subscriptionPrice = AmountMath.make(feeBrand, 10000000n);
167+
const timer = buildZoeManualTimer();
150168
return E(zoe).startInstance(
151169
installation,
152170
{ Price: feeIssuer },
153-
{ subscriptionPrice },
171+
{ subscriptionPrice, timerService: timer },
154172
);
155173
};
156174

157175
const { zoe, bundle, bundleCache, feeMintAccess } = t.context;
158176
const { instance } = await startContract({ zoe, bundle });
159177
const { faucet } = makeStableFaucet({ bundleCache, feeMintAccess, zoe });
160-
await alice(t, zoe, instance, await faucet(5n * UNIT6));
178+
await alice(t, zoe, instance, await faucet(10n * UNIT6));
161179
});
162180

163181
test('use the code that will go on chain to start the contract', async t => {
@@ -231,5 +249,5 @@ test('use the code that will go on chain to start the contract', async t => {
231249
// Now that we have the instance, resume testing as above.
232250
const { feeMintAccess, bundleCache } = t.context;
233251
const { faucet } = makeStableFaucet({ bundleCache, feeMintAccess, zoe });
234-
await alice(t, zoe, instance, await faucet(5n * UNIT6));
252+
await alice(t, zoe, instance, await faucet(10n * UNIT6));
235253
});

ui/package.json

-4
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@
4343
"vitest": "^1.2.1",
4444
"zustand": "^4.4.1"
4545
},
46-
"resolutions": {
47-
"**/ses": "^1.3.0",
48-
"**/@agoric/xsnap": "0.14.3-dev-9f085d3.0"
49-
},
5046
"prettier": {
5147
"trailingComma": "all",
5248
"arrowParens": "avoid",

ui/src/App.css

+8-4
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,15 @@
3030
}
3131
}
3232

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

3937
.card {
4038
padding: 1em;
4139
}
4240

41+
4342
.read-the-docs {
4443
color: #888;
4544
}
@@ -54,6 +53,11 @@
5453
margin: 10px;
5554
}
5655

56+
.service {
57+
height: 48px;
58+
width: 48px;
59+
}
60+
5761
.trade {
5862
display: flex;
5963
flex-direction: column;

ui/src/App.tsx

+19-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import { subscribeLatest } from '@agoric/notifier';
1414
import { makeCopyBag } from '@agoric/store';
1515
import { Logos } from './components/Logos';
1616
import { Inventory } from './components/Inventory';
17-
import { Trade } from './components/Trade';
17+
import { Subscribe } from './components/Trade';
18+
19+
import { AmountMath } from '@agoric/ertp';
1820

1921
const { entries, fromEntries } = Object;
2022

@@ -86,15 +88,21 @@ const connectWallet = async () => {
8688
}
8789
};
8890

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

95-
const value = makeCopyBag(entries(wantChoices));
96-
const want = { Items: { brand: brands.Item, value } };
97-
const give = { Price: { brand: brands.IST, value: giveValue } };
97+
const choiceBag = makeCopyBag([
98+
[{ expiryTime: '123', serviceType: wantChoice }, 1n],
99+
]);
100+
101+
// want: { Items: AmountMath.make(brands.Item, choiceBag) }
102+
103+
// const value = makeCopyBag(entries(wantChoices));
104+
const want = { Items: AmountMath.make(brands.Item, choiceBag) };
105+
const give = { Price: AmountMath.make(brands.IST, 10000000n) };
98106

99107
wallet?.makeOffer(
100108
{
@@ -103,7 +111,10 @@ const makeOffer = (giveValue: bigint, wantChoices: Record<string, bigint>) => {
103111
publicInvitationMaker: 'makeTradeInvitation',
104112
},
105113
{ give, want },
106-
undefined,
114+
{
115+
userAddress: wallet.address,
116+
serviceType: wantChoice,
117+
},
107118
(update: { status: string; data?: unknown }) => {
108119
if (update.status === 'error') {
109120
alert(`Offer error: ${update.data}`);
@@ -145,10 +156,10 @@ function App() {
145156
return (
146157
<>
147158
<Logos />
148-
<h1>Items Listed on Offer Up</h1>
159+
<h1>All-in-One Subscription Service</h1>
149160

150161
<div className="card">
151-
<Trade
162+
<Subscribe
152163
makeOffer={makeOffer}
153164
istPurse={istPurse as Purse}
154165
walletConnected={!!wallet}

0 commit comments

Comments
 (0)