diff --git a/packages/cosmic-swingset/lib/ag-solo/init-basedir.js b/packages/cosmic-swingset/lib/ag-solo/init-basedir.js index 7d87131da07..c308f31c53d 100644 --- a/packages/cosmic-swingset/lib/ag-solo/init-basedir.js +++ b/packages/cosmic-swingset/lib/ag-solo/init-basedir.js @@ -42,19 +42,29 @@ export default function initBasedir( // Symlink the wallet. let agWallet; try { - agWallet = path.resolve(__dirname, '../../../wallet-frontend/build'); + agWallet = path.resolve(__dirname, '../../../wallet-frontend'); + let walletBuild = path.join(agWallet, 'build'); if (!fs.existsSync(agWallet)) { const pjs = require.resolve('@agoric/wallet-frontend/package.json'); - agWallet = path.join(path.dirname(pjs), 'build'); - fs.statSync(agWallet); + agWallet = path.dirname(pjs); + walletBuild = path.join(agWallet, 'build'); + fs.statSync(walletBuild); } - fs.symlinkSync(agWallet, `${dstHtmldir}/wallet`); + fs.symlinkSync(walletBuild, `${dstHtmldir}/wallet`); } catch (e) { console.warn( `${agWallet} does not exist; you may want to run 'yarn build' in 'wallet-frontend'`, ); } + const walletDeploy = path.join(agWallet, 'wallet-deploy.js'); + try { + fs.statSync(walletDeploy); + fs.symlinkSync(walletDeploy, path.join(basedir, 'wallet-deploy.js')); + } catch (e) { + console.warn(`${walletDeploy} does not exist; cannot autodeploy wallet`); + } + // Save our version codes. const pj = 'package.json'; fs.copyFileSync(path.join(`${here}/../..`, pj), path.join(dstHtmldir, pj)); diff --git a/packages/cosmic-swingset/lib/ag-solo/start.js b/packages/cosmic-swingset/lib/ag-solo/start.js index f11d2e7c644..12538928a18 100644 --- a/packages/cosmic-swingset/lib/ag-solo/start.js +++ b/packages/cosmic-swingset/lib/ag-solo/start.js @@ -1,6 +1,7 @@ import fs from 'fs'; import path from 'path'; import temp from 'temp'; +import { exec } from 'child_process'; import { promisify } from 'util'; // import { createHash } from 'crypto'; @@ -246,6 +247,7 @@ export default async function start(basedir, argv) { startTimer, } = d; + let hostport; await Promise.all( connections.map(async c => { switch (c.type) { @@ -282,6 +284,7 @@ export default async function start(basedir, argv) { if (broadcastJSON) { throw new Error(`duplicate type=http in connections.json`); } + hostport = `${c.host}:${c.port}`; broadcastJSON = await makeHTTPListener( basedir, c.port, @@ -301,4 +304,31 @@ export default async function start(basedir, argv) { log.info(`swingset running`); swingSetRunning = true; deliverOutbound(); + + if (hostport && fs.existsSync('./wallet-deploy.js')) { + // Install the wallet. + let agoricCli; + try { + agoricCli = require.resolve('.bin/agoric'); + } catch (e) { + // do nothing + console.log(`Cannot find agoric CLI:`, e); + } + // Launch the agoric deploy, letting it synchronize with the chain. + if (agoricCli) { + exec( + `${agoricCli} deploy --hostport=${hostport} ./wallet-deploy.js`, + (err, stdout, stderr) => { + if (err) { + console.warn(err); + return; + } + if (stderr) { + // Report the error. + console.error(stderr); + } + }, + ); + } + } } diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js index a85de7edcac..2aed41b0946 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js @@ -55,17 +55,6 @@ export function buildRootObject(vatPowers) { D(cmdDevice).registerInboundHandler(httpVat); } - async function setupWalletVat(httpObj, httpVat, walletVat) { - await E(httpVat).registerURLHandler(walletVat, '/private/wallet'); - const bridgeURLHandler = await E(walletVat).getBridgeURLHandler(); - await E(httpVat).registerURLHandler( - bridgeURLHandler, - '/private/wallet-bridge', - ); - await E(walletVat).setHTTPObject(httpObj); - await E(walletVat).setPresences(); - } - // Make services that are provided on the real or virtual chain side async function makeChainBundler(vats, timerDevice, vatAdminSvc) { // Create singleton instances. @@ -124,11 +113,39 @@ export function buildRootObject(vatPowers) { }; } + const pursePetnames = { + moola: 'Fun budget', + simolean: 'Nest egg', + }; + + const payments = await E(vats.mints).mintInitialPayments( + issuerNames, + harden([1900, 1900]), + ); + + const paymentInfo = issuerInfo.map( + ({ petname: issuerPetname, issuer }, i) => ({ + issuerPetname, + issuer, + payment: payments[i], + pursePetname: + pursePetnames[issuerPetname] || `${issuerPetname} purse`, + }), + ); + + const faucet = { + // A method to reap the spoils of our on-chain provisioning. + async tapFaucet() { + return paymentInfo; + }, + }; + const bundle = harden({ ...additionalPowers, chainTimerService, sharingService, contractHost, + faucet, ibcport, registrar: registry, registry, @@ -136,15 +153,7 @@ export function buildRootObject(vatPowers) { zoe, }); - const payments = await E(vats.mints).mintInitialPayments( - issuerNames, - harden([1900, 1900]), - ); - - // return payments and issuerInfo separately from the - // bundle so that they can be used to initialize a wallet - // per user - return harden({ payments, issuerInfo, bundle }); + return bundle; }, }); } @@ -225,8 +234,7 @@ export function buildRootObject(vatPowers) { // objects that live in the client's solo vat. Some services should only // be in the DApp environment (or only in end-user), but we're not yet // making a distinction, so the user also gets them. - async function createLocalBundle(vats, userBundle, payments, issuerInfo) { - const { zoe, board } = userBundle; + async function createLocalBundle(vats) { // This will eventually be a vat spawning service. Only needed by dev // environments. const spawner = E(vats.host).makeHost(); @@ -234,38 +242,8 @@ export function buildRootObject(vatPowers) { // Needed for DApps, maybe for user clients. const uploads = E(vats.uploads).getUploads(); - // Wallet for both end-user client and dapp dev client - await E(vats.wallet).startup({ zoe, board }); - const wallet = E(vats.wallet).getWallet(); - await Promise.all( - issuerInfo.map(({ petname, issuer }) => - E(wallet).addIssuer(petname, issuer), - ), - ); - - // Make empty purses. Have some petnames for them. - const pursePetnames = { - moola: 'Fun budget', - simolean: 'Nest egg', - }; - await Promise.all( - issuerInfo.map(({ petname: issuerPetname }) => { - let pursePetname = pursePetnames[issuerPetname]; - if (!pursePetname) { - pursePetname = `${issuerPetname} purse`; - pursePetnames[issuerPetname] = pursePetname; - } - return E(wallet).makeEmptyPurse(issuerPetname, pursePetname); - }), - ); - - // deposit payments - const [moolaPayment, simoleanPayment] = await Promise.all(payments); - - await E(wallet).deposit(pursePetnames.moola, moolaPayment); - await E(wallet).deposit(pursePetnames.simolean, simoleanPayment); - // This will allow dApp developers to register in their api/deploy.js + let walletRegistered = false; const httpRegCallback = { send(obj, connectionHandles) { return E(vats.http).send(obj, connectionHandles); @@ -273,6 +251,20 @@ export function buildRootObject(vatPowers) { registerAPIHandler(handler) { return E(vats.http).registerURLHandler(handler, '/api'); }, + async registerWallet(wallet, handler, bridgeHandler) { + if (walletRegistered) { + throw Error(`Unimplemented multiple wallet registrations`); + } + walletRegistered = true; + await Promise.all([ + E(vats.http).registerURLHandler(handler, '/private/wallet'), + E(vats.http).registerURLHandler( + bridgeHandler, + '/private/wallet-bridge', + ), + E(vats.http).setWallet(wallet), + ]); + }, }; return allComparable( @@ -280,7 +272,6 @@ export function buildRootObject(vatPowers) { comms: vats.comms, uploads, spawner, - wallet, network: vats.network, http: httpRegCallback, vattp: vats.vattp, @@ -354,19 +345,12 @@ export function buildRootObject(vatPowers) { GCI, PROVISIONER_INDEX, ); - const { payments, bundle, issuerInfo } = await E( - demoProvider, - ).getDemoBundle(); - const localBundle = await createLocalBundle( - vats, - bundle, - payments, - issuerInfo, - ); + const localBundle = await createLocalBundle(vats); + await E(vats.http).setPresences(localBundle); + const bundle = await E(demoProvider).getDemoBundle(); await E(vats.http).setPresences(localBundle, bundle, { localTimerService, }); - await setupWalletVat(localBundle.http, vats.http, vats.wallet); break; } case 'two_chain': { @@ -437,18 +421,12 @@ export function buildRootObject(vatPowers) { // addEgress(..., PROVISIONER_INDEX) is called in case two_chain const demoProvider = E(vats.comms).addIngress(GCI, PROVISIONER_INDEX); // Get the demo bundle from the chain-side provider - const b = await E(demoProvider).getDemoBundle('client'); - const { payments, bundle, issuerInfo } = b; - const localBundle = await createLocalBundle( - vats, - bundle, - payments, - issuerInfo, - ); + const localBundle = await createLocalBundle(vats); + await E(vats.http).setPresences(localBundle); + const bundle = await E(demoProvider).getDemoBundle('client'); await E(vats.http).setPresences(localBundle, bundle, { localTimerService, }); - await setupWalletVat(localBundle.http, vats.http, vats.wallet); break; } case 'three_client': { @@ -467,22 +445,14 @@ export function buildRootObject(vatPowers) { await setupCommandDevice(vats.http, devices.command, { client: true, }); - const { payments, bundle, issuerInfo } = await E( - chainBundler, - ).createUserBundle('localuser'); + const localBundle = await createLocalBundle(vats); + await E(vats.http).setPresences(localBundle); + const bundle = await E(chainBundler).createUserBundle('localuser'); // Setup of the Local part ///////////////////////////////////// - const localBundle = await createLocalBundle( - vats, - bundle, - payments, - issuerInfo, - ); await E(vats.http).setPresences(localBundle, bundle, { localTimerService: bundle.chainTimerService, }); - - setupWalletVat(localBundle.http, vats.http, vats.wallet); break; } default: diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/vat-http.js b/packages/cosmic-swingset/lib/ag-solo/vats/vat-http.js index f1349c983ee..4f67363c0a4 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/vat-http.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/vat-http.js @@ -122,6 +122,20 @@ export function buildRootObject(vatPowers) { provisioner = p; }, + setWallet(wallet) { + // This must happen only after the local and agoric objects have been + // installed in setPresences. + // We're guaranteed that because the deployment script that installs + // the wallet only runs after the chain has provisioned us. + exportedToCapTP = { + ...exportedToCapTP, + local: { ...exportedToCapTP.local, wallet }, + wallet, + }; + replObjects.local.wallet = wallet; + replObjects.home.wallet = wallet; + }, + setPresences( privateObjects, decentralObjects = undefined, @@ -132,7 +146,7 @@ export function buildRootObject(vatPowers) { ...privateObjects, // TODO: Remove; replaced by .local ...handyObjects, LOADING: loaded.p, // TODO: Remove; replaced by .agoric.LOADING - agoric: { decentralObjects, LOADING: loaded.p }, + agoric: { ...decentralObjects, LOADING: loaded.p }, local: privateObjects, }; diff --git a/packages/cosmic-swingset/package.json b/packages/cosmic-swingset/package.json index c24a8b59d32..2eb3ffcf3fa 100644 --- a/packages/cosmic-swingset/package.json +++ b/packages/cosmic-swingset/package.json @@ -45,6 +45,7 @@ "@agoric/wallet-frontend": "^0.2.7", "@agoric/weak-store": "^0.0.8", "@agoric/zoe": "^0.7.0", + "agoric": "^0.7.0", "@babel/generator": "^7.6.4", "@iarna/toml": "^2.2.3", "anylogger": "^0.21.0", diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/lib-dehydrate.js b/packages/wallet-frontend/lib/lib-dehydrate.js similarity index 100% rename from packages/cosmic-swingset/lib/ag-solo/vats/lib-dehydrate.js rename to packages/wallet-frontend/lib/lib-dehydrate.js diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/lib-wallet.js b/packages/wallet-frontend/lib/lib-wallet.js similarity index 100% rename from packages/cosmic-swingset/lib/ag-solo/vats/lib-wallet.js rename to packages/wallet-frontend/lib/lib-wallet.js diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/observable.js b/packages/wallet-frontend/lib/observable.js similarity index 100% rename from packages/cosmic-swingset/lib/ag-solo/vats/observable.js rename to packages/wallet-frontend/lib/observable.js diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/pubsub.js b/packages/wallet-frontend/lib/pubsub.js similarity index 100% rename from packages/cosmic-swingset/lib/ag-solo/vats/pubsub.js rename to packages/wallet-frontend/lib/pubsub.js diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/vat-wallet.js b/packages/wallet-frontend/lib/vat-wallet.js similarity index 100% rename from packages/cosmic-swingset/lib/ag-solo/vats/vat-wallet.js rename to packages/wallet-frontend/lib/vat-wallet.js diff --git a/packages/wallet-frontend/lib/wallet.js b/packages/wallet-frontend/lib/wallet.js new file mode 100644 index 00000000000..d528c8303e8 --- /dev/null +++ b/packages/wallet-frontend/lib/wallet.js @@ -0,0 +1,11 @@ +/* global harden */ + +// TODO: Don't just make this an adapter. +import { buildRootObject } from './vat-wallet'; + +function spawn(terms, _inviteMaker) { + const walletVat = buildRootObject(); + return walletVat.startup(terms).then(_ => walletVat); +} + +export default harden(spawn); diff --git a/packages/wallet-frontend/package.json b/packages/wallet-frontend/package.json index 3db10ac7b27..5f2bb8f3d7c 100644 --- a/packages/wallet-frontend/package.json +++ b/packages/wallet-frontend/package.json @@ -13,7 +13,7 @@ "prepublish": "yarn build", "start": "react-scripts start", "test:interactive": "react-scripts test --env=jsdom", - "test": "exit 0", + "test": "tap --no-coverage --jobs=1 --timeout 600 'test/**/test*.js'", "build": "react-scripts build", "eject": "react-scripts eject" }, @@ -30,9 +30,12 @@ ] }, "files": [ - "build" + "build", + "lib", + "wallet-deploy.js" ], "devDependencies": { + "@agoric/install-ses": "^0.2.0", "@material-ui/core": "^4.9.3", "@material-ui/icons": "^4.9.1", "clsx": "^1.0.4", @@ -50,7 +53,9 @@ "react": "^16.12.0", "react-dom": "^16.12.0", "react-scripts": "^3.4.0", - "rimraf": "^3.0.2" + "rimraf": "^3.0.2", + "tap": "^14.10.5", + "tape-promise": "^4.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/cosmic-swingset/test/unitTests/test-lib-dehydrate.js b/packages/wallet-frontend/test/test-lib-dehydrate.js similarity index 99% rename from packages/cosmic-swingset/test/unitTests/test-lib-dehydrate.js rename to packages/wallet-frontend/test/test-lib-dehydrate.js index af8854e6797..3c4477f4f6a 100644 --- a/packages/cosmic-swingset/test/unitTests/test-lib-dehydrate.js +++ b/packages/wallet-frontend/test/test-lib-dehydrate.js @@ -4,7 +4,7 @@ import '@agoric/install-ses'; // calls lockdown() // eslint-disable-next-line import/no-extraneous-dependencies import { test } from 'tape-promise/tape'; -import { makeDehydrator } from '../../lib/ag-solo/vats/lib-dehydrate'; +import { makeDehydrator } from '../lib/lib-dehydrate'; test('makeDehydrator', async t => { try { diff --git a/packages/cosmic-swingset/test/unitTests/test-lib-wallet.js b/packages/wallet-frontend/test/test-lib-wallet.js similarity index 99% rename from packages/cosmic-swingset/test/unitTests/test-lib-wallet.js rename to packages/wallet-frontend/test/test-lib-wallet.js index a5b06a1b8fa..1e352a1b2c7 100644 --- a/packages/cosmic-swingset/test/unitTests/test-lib-wallet.js +++ b/packages/wallet-frontend/test/test-lib-wallet.js @@ -12,8 +12,8 @@ import fakeVatAdmin from '@agoric/zoe/test/unitTests/contracts/fakeVatAdmin'; import { makeRegistrar } from '@agoric/registrar'; import { E } from '@agoric/eventual-send'; -import { makeWallet } from '../../lib/ag-solo/vats/lib-wallet'; -import { makeBoard } from '../../lib/ag-solo/vats/lib-board'; +import { makeWallet } from '../lib/lib-wallet'; +import { makeBoard } from '@agoric/cosmic-swingset/lib/ag-solo/vats/lib-board'; const setupTest = async () => { const pursesStateChangeLog = []; diff --git a/packages/wallet-frontend/wallet-deploy.js b/packages/wallet-frontend/wallet-deploy.js new file mode 100644 index 00000000000..76dce750e3b --- /dev/null +++ b/packages/wallet-frontend/wallet-deploy.js @@ -0,0 +1,63 @@ +// @ts-nocheck +// Agoric wallet deployment script. +// FIXME: This is just hacked together for the legacy wallet. + +import { E } from '@agoric/eventual-send'; + +export default async function deployWallet( + homePromise, + { bundleSource, pathResolve }, +) { + const home = await homePromise; + // console.log('have home', home); + const { + agoric: { board, faucet, zoe }, + local: { http, spawner, wallet: oldWallet }, + } = home; + + if (oldWallet) { + console.log(`You already have a wallet installed.`); + return 0; + } + + // Bundle the wallet sources. + const bundle = await bundleSource(pathResolve(__dirname, './lib/wallet.js')); + + // Install it on the local spawner. + const walletInstall = E(spawner).install(bundle); + + // Wallet for both end-user client and dapp dev client + const walletVat = await E(walletInstall).spawn({ + zoe, + board, + faucet, + http, + }); + + // Get the payments that were given to us by the chain. + const paymentInfo = await E(faucet).tapFaucet(); + + // Claim the payments. + const wallet = await E(walletVat).getWallet(); + await Promise.all( + paymentInfo.map( + async ({ issuerPetname, pursePetname, issuer, payment }) => { + // Create some issuer petnames. + await E(wallet).addIssuer(issuerPetname, issuer); + // Make empty purses. Have some petnames for them. + await E(wallet).makeEmptyPurse(issuerPetname, pursePetname); + // Deposit payments. + const p = await payment; + await E(wallet).deposit(pursePetname, p); + }, + ), + ); + + // Install our handlers. + const bridgeURLHandler = await E(walletVat).getBridgeURLHandler(); + const walletURLHandler = walletVat; + await E(http).registerWallet(wallet, walletURLHandler, bridgeURLHandler); + await E(walletVat).setHTTPObject(http); + await E(walletVat).setPresences(); + console.log('Deployed @agoric/wallet-frontend!'); +}