/ api / deploy.js
deploy.js
  1  // @ts-check
  2  
  3  // Agoric Dapp api deployment script
  4  
  5  import fs from 'fs';
  6  import { E } from '@endo/eventual-send';
  7  import '@agoric/zoe/exported.js';
  8  
  9  import installationConstants from '../ui/public/conf/installationConstants.js';
 10  
 11  // deploy.js runs in an ephemeral Node.js outside of swingset. The
 12  // spawner runs within ag-solo, so is persistent.  Once the deploy.js
 13  // script ends, connections to any of its objects are severed.
 14  
 15  /**
 16   * @typedef {object} DeployPowers The special powers that `agoric deploy` gives us
 17   * @property {(path: string) => Promise<{ moduleFormat: string, source: string }>} bundleSource
 18   * @property {(path: string) => string} pathResolve
 19   * @property {(path: string, opts?: any) => Promise<any>} installUnsafePlugin
 20   * @typedef {object} Board
 21   * @property {(id: string) => any} getValue
 22   * @property {(value: any) => string} getId
 23   * @property {(value: any) => boolean} has
 24   * @property {() => [string]} ids
 25   */
 26  
 27  const API_PORT = process.env.API_PORT || '8000';
 28  
 29  /**
 30   * @typedef {{ zoe: ZoeService, board: Board, spawner, wallet, uploads, http }} Home
 31   * @param {Promise<Home>} homePromise
 32   * A promise for the references available from REPL home
 33   * @param {DeployPowers} powers
 34   */
 35  export default async function deployApi(
 36    homePromise,
 37    { bundleSource, pathResolve },
 38  ) {
 39    // Let's wait for the promise to resolve.
 40    const home = await homePromise;
 41  
 42    // Unpack the references.
 43    const {
 44      // *** LOCAL REFERENCES ***
 45  
 46      // The spawner persistently runs scripts within ag-solo, off-chain.
 47      spawner,
 48  
 49      // *** ON-CHAIN REFERENCES ***
 50  
 51      // Zoe lives on-chain and is shared by everyone who has access to
 52      // the chain. In this demo, that's just you, but on our testnet,
 53      // everyone has access to the same Zoe.
 54      zoe,
 55  
 56      // The http service allows registered handlers that are executed as part of
 57      // the ag-solo web server.
 58      http,
 59  
 60      // This is a scratch pad specific to the current ag-solo and inaccessible
 61      // from the chain.
 62      uploads: scratch,
 63  
 64      // The board is an on-chain object that is used to make private
 65      // on-chain objects public to everyone else on-chain. These
 66      // objects get assigned a unique string id. Given the id, other
 67      // people can access the object through the board. Ids and values
 68      // have a one-to-one bidirectional mapping. If a value is added a
 69      // second time, the original id is just returned.
 70      board,
 71    } = home;
 72  
 73    // To get the backend of our dapp up and running, first we need to
 74    // grab the installation that our contract deploy script put
 75    // in the public board.
 76    const { INSTALLATION_BOARD_ID, CONTRACT_NAME } = installationConstants;
 77    const installation = await E(board).getValue(INSTALLATION_BOARD_ID);
 78  
 79    // Second, we can use the installation to create a new instance of
 80    // our contract code on Zoe. A contract instance is a running
 81    // program that can take offers through Zoe. Making an instance will
 82    // give us a `creatorFacet` that will let us make invitations we can
 83    // send to users.
 84  
 85    const { creatorFacet, instance, publicFacet } = await E(zoe).startInstance(
 86      installation,
 87    );
 88  
 89    console.log('- SUCCESS! contract instance is running on Zoe');
 90  
 91    console.log('Retrieving Board IDs for issuers and brands');
 92    const invitationIssuerP = E(zoe).getInvitationIssuer();
 93    const invitationBrandP = E(invitationIssuerP).getBrand();
 94  
 95    const tokenIssuer = await E(publicFacet).getTokenIssuer();
 96  
 97    // Set up our token to be used by other dapps (like our card store).
 98    E(scratch).set('faucetTokenIssuer', tokenIssuer);
 99  
100    const tokenBrand = await E(tokenIssuer).getBrand();
101  
102    const invitationIssuer = await invitationIssuerP;
103  
104    const [INSTANCE_BOARD_ID, TOKEN_BRAND_BOARD_ID, TOKEN_ISSUER_BOARD_ID] =
105      await Promise.all([
106        E(board).getId(instance),
107        E(board).getId(tokenBrand),
108        E(board).getId(tokenIssuer),
109      ]);
110  
111    console.log(`-- Contract Name: ${CONTRACT_NAME}`);
112    console.log(`-- INSTANCE_BOARD_ID: ${INSTANCE_BOARD_ID}`);
113    console.log(`-- TOKEN_ISSUER_BOARD_ID: ${TOKEN_ISSUER_BOARD_ID}`);
114    console.log(`-- TOKEN_BRAND_BOARD_ID: ${TOKEN_BRAND_BOARD_ID}`);
115  
116    // We want the handler to run persistently. (Scripts such as this
117    // deploy.js script are ephemeral and all connections to objects
118    // within this script are severed when the script is done running.)
119  
120    const installURLHandler = async () => {
121      // To run the URL handler persistently, we must use the spawner to run
122      // the code on our ag-solo even after the deploy script exits.
123  
124      // Bundle up the handler code
125      const bundle = await bundleSource(pathResolve('./src/handler.js'));
126  
127      // Install it on the spawner
128      const handlerInstall = E(spawner).install(bundle);
129  
130      // Spawn the installed code to create an URL handler.
131      const handler = E(handlerInstall).spawn({
132        creatorFacet,
133        board,
134        http,
135        invitationIssuer,
136      });
137  
138      // Have our ag-solo wait on ws://localhost:8000/api/fungible-faucet for
139      // websocket connections.
140      await E(http).registerURLHandler(handler, '/api/fungible-faucet');
141    };
142  
143    await installURLHandler();
144  
145    const invitationBrand = await invitationBrandP;
146    const INVITE_BRAND_BOARD_ID = await E(board).getId(invitationBrand);
147  
148    const API_URL = process.env.API_URL || `http://127.0.0.1:${API_PORT || 8000}`;
149  
150    // Re-save the constants somewhere where the UI and api can find it.
151    const dappConstants = {
152      INSTANCE_BOARD_ID,
153      INSTALLATION_BOARD_ID,
154      INVITE_BRAND_BOARD_ID,
155      // BRIDGE_URL: 'agoric-lookup:https://local.agoric.com?append=/bridge',
156      brandBoardIds: {
157        Token: TOKEN_BRAND_BOARD_ID,
158      },
159      issuerBoardIds: { Token: TOKEN_ISSUER_BOARD_ID },
160      BRIDGE_URL: 'http://127.0.0.1:8000',
161      API_URL,
162    };
163    const defaultsFile = pathResolve(`../ui/public/conf/defaults.js`);
164    console.log('writing', defaultsFile);
165    const defaultsContents = `\
166  // GENERATED FROM ${pathResolve('./deploy.js')}
167  export default ${JSON.stringify(dappConstants, undefined, 2)};
168  `;
169    await fs.promises.writeFile(defaultsFile, defaultsContents);
170  }