diff --git a/.github/workflows/npm-package.yml b/.github/workflows/npm-package.yml new file mode 100644 index 0000000..ca5f711 --- /dev/null +++ b/.github/workflows/npm-package.yml @@ -0,0 +1,19 @@ +name: Publish Package to NPM Registry +on: + release: + types: [published] +jobs: + npm-package: + runs-on: ubuntu-latest-16-core + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '18' + registry-url: 'https://registry.npmjs.org' + scope: '@topos-protocol' + - run: npm ci + - run: npm run build + - run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-pipeline.yml b/.github/workflows/release-pipeline.yml new file mode 100644 index 0000000..96957aa --- /dev/null +++ b/.github/workflows/release-pipeline.yml @@ -0,0 +1,20 @@ +name: Release Pipeline + +on: + release: + types: [created] + +jobs: + notification: + runs-on: ubuntu-latest-16-core + steps: + - name: Send Slack notification + uses: slackapi/slack-github-action@v1.23.0 + with: + payload: | + { + "repository": "${{ github.repository }}", + "version": "${{ github.ref }}" + } + env: + SLACK_WEBHOOK_URL: ${{ vars.RELEASE_PIPELINE_SLACK_WEBHOOK_URL }} diff --git a/README.md b/README.md index 49d8a84..d86089c 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,14 @@ ### Requirements +- Git - NodeJS (tested with 16+) - Docker -- Git (with SSH access to [dapp-frontend-cross-subnet](github.com/toposware/dapp-frontend-cross-subnet/), [executor-service](https://github.com/toposware/executor-service), and [full-msg-protocol-infra](https://github.com/toposware/full-msg-protocol-infra)) ### [Optional] Install the package globally ``` -$ npm install -g @topos-network/topos-playground +$ npm install -g @topos-protocol/topos-playground ``` ### Run the CLI @@ -35,5 +35,19 @@ $ topos-playground [start|clean] Otherwise, you can use `npx` to abstract the installation ``` -$ npx @topos-network/topos-plaground [start|clean] +$ npx @topos-protocol/topos-plaground [start|clean] +``` + +## Development + +### Build + +``` +npm run build +``` + +### Rum + +``` +node dist/main [start|clean] ``` diff --git a/package.json b/package.json index c0700ce..83e2750 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@topos-network/topos-playground", - "version": "0.0.1-alpha12", + "name": "@topos-protocol/topos-playground", + "version": "0.0.1", "description": "CLI to run local Topos devnets with subnets, a TCE network, and apps", "author": "Sébastien Dan ", "license": "MIT", @@ -9,7 +9,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/topos-network/topos-playground.git" + "url": "git+https://github.com/topos-protocol/topos-playground.git" }, "scripts": { "build": "nest build", diff --git a/src/app.module.ts b/src/app.module.ts index f092d9a..b69c34e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,11 +1,10 @@ import { Module } from '@nestjs/common' -import { SecretQuestions } from './questions/secrets.questions' import { CleanCommand } from './commands/clean.command' import { StartCommand } from './commands/start.command' import { ReactiveSpawn } from './ReactiveSpawn' @Module({ - providers: [ReactiveSpawn, SecretQuestions, CleanCommand, StartCommand], + providers: [ReactiveSpawn, CleanCommand, StartCommand], }) export class AppModule {} diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 2c99fd8..644f515 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -1,14 +1,17 @@ import { randomUUID } from 'crypto' -import { readFile, stat, writeFile } from 'fs' +import { readFile, stat } from 'fs' import { Command, CommandRunner, InquirerService } from 'nest-commander' import { concatAll, tap } from 'rxjs/operators' import { defer, Observable, of } from 'rxjs' import { Next, ReactiveSpawn } from '../ReactiveSpawn' -import { SecretAnswers } from '../questions/secrets.questions' import { workingDir } from 'src/constants' import { createLoggerFile, loggerConsole } from 'src/loggers' +const INFRA_REF = 'v0.1.3' +const FRONTEND_REF = 'v0.1.0-alpha3' +const EXECUTOR_SERVICE_REF = 'v0.1.1' + @Command({ name: 'start', description: 'Run everything' }) export class StartCommand extends CommandRunner { private _workingDir = workingDir @@ -32,8 +35,8 @@ export class StartCommand extends CommandRunner { this._createWorkingDirectoryIfInexistant(), this._cloneGitRepositories(), this._copyEnvFiles(), - this._verifySecrets(), - this._runFullMsgProtocolInfra(), + this._runLocalERC20MessagingInfra(), + this._retrieveAndWriteContractAddressesToEnv(), this._runRedis(), this._runExecutorService(), this._rundDappFrontendService() @@ -132,19 +135,19 @@ export class StartCommand extends CommandRunner { return of( defer(() => of(this._log('Cloning repositories...'))), this._cloneGitRepository( - 'topos-network', + 'topos-protocol', 'local-erc20-messaging-infra', - '0.1.0-alpha' + INFRA_REF ), this._cloneGitRepository( - 'topos-network', + 'topos-protocol', 'dapp-frontend-erc20-messaging', - '0.1.0-alpha' + FRONTEND_REF ), this._cloneGitRepository( - 'topos-network', + 'topos-protocol', 'executor-service', - '0.1.0-alpha' + EXECUTOR_SERVICE_REF ), defer(() => of(this._log(''))) ).pipe(concatAll()) @@ -205,13 +208,17 @@ export class StartCommand extends CommandRunner { '.env.executor-service', `${this._workingDir}/executor-service` ), + this._copyEnvFile('.env.secrets', `${this._workingDir}`, `.env.secrets`), defer(() => of(this._log(''))) ).pipe(concatAll()) } - private _copyEnvFile(localEnvFileName: string, destinationDirectory: string) { + private _copyEnvFile( + localEnvFileName: string, + destinationDirectory: string, + destinationFileName = '.env' + ) { return new Observable((subscriber) => { - const destinationFileName = '.env' const destinationFilePath = `${destinationDirectory}/${destinationFileName}` stat(destinationFilePath, (error) => { @@ -241,63 +248,7 @@ export class StartCommand extends CommandRunner { }) } - private _verifySecrets() { - return new Observable((subscriber) => { - this._log(`Setting secrets...`) - - const path = `${this._workingDir}/.env.secrets` - - stat(path, (error) => { - if (error) { - this._log(`No secrets file have been found so one will be created`) - this._log(``) - this._askForSecrets().subscribe(subscriber) - } else { - this._log(`✅ A local secrets file has been found and will be used`) - this._log(``) - subscriber.complete() - } - }) - }) - } - - private _askForSecrets() { - return new Observable((subscriber) => { - this.inquirer - .ask('secrets-questions', undefined) - .then( - ({ - privateKey, - tokenDeployerSalt, - toposCoreSalt, - toposCoreProxySalt, - toposMessagingSalt, - subnetRegistratorSalt, - auth0ClientId, - auth0ClientSecret, - }) => { - writeFile( - `${this._workingDir}/.env.secrets`, - `export PRIVATE_KEY=${privateKey} - export TOKEN_DEPLOYER_SALT=${tokenDeployerSalt} - export TOPOS_CORE_SALT=${toposCoreSalt} - export TOPOS_CORE_PROXY_SALT=${toposCoreProxySalt} - export TOPOS_MESSAGING_SALT=${toposMessagingSalt} - export SUBNET_REGISTRATOR_SALT=${subnetRegistratorSalt} - export AUTH0_CLIENT_ID=${auth0ClientId} - export AUTH0_CLIENT_SECRET=${auth0ClientSecret} - `, - () => { - this._log(``) - subscriber.complete() - } - ) - } - ) - }) - } - - private _runFullMsgProtocolInfra() { + private _runLocalERC20MessagingInfra() { const secretsFilePath = `${this._workingDir}/.env.secrets` const executionPath = `${this._workingDir}/local-erc20-messaging-infra` @@ -310,6 +261,34 @@ export class StartCommand extends CommandRunner { ).pipe(concatAll()) } + private _retrieveAndWriteContractAddressesToEnv() { + const frontendEnvFilePath = `${this._workingDir}/dapp-frontend-erc20-messaging/packages/frontend/.env` + const executorServiceEnvFilePath = `${this._workingDir}/executor-service/.env` + + return of( + defer(() => of(this._log(`Retrieving contract addresses...`))), + this._spawn.reactify( + `docker cp contracts-topos:/contracts/.env ${this._workingDir}/.env.addresses` + ), + this._spawn.reactify( + `source ${this._workingDir}/.env.addresses \ + && echo "VITE_SUBNET_REGISTRATOR_CONTRACT_ADDRESS=$SUBNET_REGISTRATOR_CONTRACT_ADDRESS" >> ${frontendEnvFilePath} \ + && echo "VITE_TOPOS_CORE_CONTRACT_ADDRESS=$TOPOS_CORE_PROXY_CONTRACT_ADDRESS" >> ${frontendEnvFilePath} \ + && echo "VITE_ERC20_MESSAGING_CONTRACT_ADDRESS=$ERC20_MESSAGING_CONTRACT_ADDRESS" >> ${frontendEnvFilePath} \ + && echo "SUBNET_REGISTRATOR_CONTRACT_ADDRESS=$SUBNET_REGISTRATOR_CONTRACT_ADDRESS" >> ${executorServiceEnvFilePath} \ + && echo "TOPOS_CORE_CONTRACT_ADDRESS=$TOPOS_CORE_PROXY_CONTRACT_ADDRESS" >> ${executorServiceEnvFilePath}` + ), + defer(() => + of( + this._log( + `✅ Contract addresses were retrieved and written to env files` + ), + this._log(``) + ) + ) + ).pipe(concatAll()) + } + private _runRedis() { const containerName = 'redis-stack-server' diff --git a/src/constants.ts b/src/constants.ts index 9c1e2bd..7f873c7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,3 +1,3 @@ -import {homedir} from 'os' +import { homedir } from 'os' export const workingDir = `${homedir()}/topos-playground` diff --git a/src/env/.env.dapp-frontend b/src/env/.env.dapp-frontend index ff5266a..51154cc 100644 --- a/src/env/.env.dapp-frontend +++ b/src/env/.env.dapp-frontend @@ -1,8 +1,10 @@ VITE_EXECUTOR_SERVICE_ENDPOINT=http://localhost:3000 -VITE_SUBNET_REGISTRATOR_CONTRACT_ADDRESS=0xC9109ADccD6fDF867d52abf1Daf049E59fd9D82e -VITE_TOPOS_CORE_CONTRACT_ADDRESS=0x1D7b9f9b1FF6cf0A3BEB0F84fA6F8628E540E97F -VITE_TOPOS_MESSAGING_CONTRACT_ADDRESS=0xefacaF6E2C5744600F4f88BcEab6D776EeE65c98 VITE_TOPOS_SUBNET_ENDPOINT=localhost:10002 VITE_TRACING_OTEL_COLLECTOR_ENDPOINT=https://otel-collector.telemetry.devnet-1.toposware.com/v1/traces VITE_TRACING_SERVICE_NAME=cross-subnet-message VITE_TRACING_SERVICE_VERSION=0.1.0 + +# Addresses +VITE_TOPOS_CORE_CONTRACT_ADDRESS=$TOPOS_CORE_PROXY_CONTRACT_ADDRESS +VITE_ERC20_MESSAGING_CONTRACT_ADDRESS=$ERC20_MESSAGING_CONTRACT_ADDRESS +VITE_SUBNET_REGISTRATOR_CONTRACT_ADDRESS=$SUBNET_REGISTRATOR_CONTRACT_ADDRESS diff --git a/src/env/.env.executor-service b/src/env/.env.executor-service index 73ee645..c5dc91d 100644 --- a/src/env/.env.executor-service +++ b/src/env/.env.executor-service @@ -3,6 +3,3 @@ AUTH0_ISSUER_URL=https://dev-z8x4rhzfosi03fqx.us.auth0.com/ REDIS_HOST=localhost REDIS_PORT=6379 TOPOS_SUBNET_ENDPOINT=localhost:10002 -SUBNET_REGISTRATOR_CONTRACT_ADDRESS=0xC9109ADccD6fDF867d52abf1Daf049E59fd9D82e -TOPOS_CORE_CONTRACT_ADDRESS=0x1D7b9f9b1FF6cf0A3BEB0F84fA6F8628E540E97F -TOPOS_MESSAGING_CONTRACT_ADDRESS=0xefacaF6E2C5744600F4f88BcEab6D776EeE65c98 diff --git a/src/env/.env.secrets b/src/env/.env.secrets new file mode 100644 index 0000000..b6c4d1f --- /dev/null +++ b/src/env/.env.secrets @@ -0,0 +1,8 @@ +export PRIVATE_KEY=0xd7e2e00b43c12cf17239d4755ed744df6ca70a933fc7c8bbb7da1342a5ff2e38 +export TOKEN_DEPLOYER_SALT=m1Ln9uF9MGZ2PcR +export TOPOS_CORE_SALT=dCyN8VZz5sXgqMO +export TOPOS_CORE_PROXY_SALT=aRV8Mp9o4xRpLbF +export ERC20_MESSAGING_SALT=ho37cJbGkgI6vnp +export SUBNET_REGISTRATOR_SALT=azsRlXyGu0ty291 +export AUTH0_CLIENT_ID=xVF6EuPDaazQchfjFpGAdcJUpHk2W5I2 +export AUTH0_CLIENT_SECRET=-CrwnrgSx1EaP_oaKAFXFdqrIvA4WK8Pcpd5xC4o3ZfYB4H4V4FPHfEbqpu4KZN8 diff --git a/src/questions/secrets.questions.ts b/src/questions/secrets.questions.ts deleted file mode 100644 index b01640b..0000000 --- a/src/questions/secrets.questions.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { utils } from 'ethers' -import { Question, QuestionSet, ValidateFor } from 'nest-commander' - -export interface SecretAnswers { - privateKey: string - tokenDeployerSalt: string - toposCoreSalt: string - toposCoreProxySalt: string - toposMessagingSalt: string - subnetRegistratorSalt: string - auth0ClientId: string - auth0ClientSecret: string -} - -@QuestionSet({ name: 'secrets-questions' }) -export class SecretQuestions { - @Question({ - message: - 'Please provide the private key of the account which will pay fees', - name: 'privateKey', - }) - parsePrivateKey(val: string) { - return val - } - - @ValidateFor({ name: 'privateKey' }) - verifyPrivateKey(val: string) { - const isValid = utils.isHexString(val, 32) - - if (!isValid) { - return 'Please provide a valid 32-byte 0x-prefixed hex string!' - } - - return true - } - - @Question({ - message: - 'Please provide the salt of the deployment of the TokenDeployer contract', - name: 'tokenDeployerSalt', - }) - parseTokenDeployerSalt(val: string) { - return val - } - - @Question({ - message: - 'Please provide the salt of the deployment of the ToposCore contract', - name: 'toposCoreSalt', - }) - parseToposCoreSalt(val: string) { - return val - } - - @Question({ - message: - 'Please provide the salt of the deployment of the ToposCoreProxy contract', - name: 'toposCoreProxySalt', - }) - parseToposCoreProxySalt(val: string) { - return val - } - - @Question({ - message: - 'Please provide the salt of the deployment of the ToposMessaging contract', - name: 'toposMessagingSalt', - }) - parseToposMessagingSalt(val: string) { - return val - } - - @Question({ - message: - 'Please provide the salt of the deployment of the SubnetRegistrator contract', - name: 'subnetRegistratorSalt', - }) - parseSubnetRegistratorSalt(val: string) { - return val - } - - @Question({ - message: 'Please provide the client id for Auth0', - name: 'auth0ClientId', - }) - parseAuth0ClientId(val: string) { - return val - } - - @Question({ - message: 'Please provide the client secret for Auth0', - name: 'auth0ClientSecret', - }) - parseAuth0ClientSecret(val: string) { - return val - } - - @ValidateFor({ name: 'tokenDeployerSalt' }) - @ValidateFor({ name: 'toposCoreSalt' }) - @ValidateFor({ name: 'toposCoreProxySalt' }) - @ValidateFor({ name: 'toposMessagingSalt' }) - @ValidateFor({ name: 'subnetRegistratorSalt' }) - @ValidateFor({ name: 'auth0ClientId' }) - @ValidateFor({ name: 'auth0ClientSecret' }) - verifySalt(val: string) { - return Boolean(val) - } -}