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 }