Add web-apps and laconicd in MobyMask v2 watcher stack (#226)

* Rename .env file

* Add web app services to docker compose file

* Add laconicd to deploy contract and send txs

* Add demo with steps for running mobymask app with L2 chain

* Add fix for yarn install on M1 platform in react-peer

* Update multiaddrs to use websockets

* Add notes in readmes

---------

Co-authored-by: prathamesh0 <prathamesh.musale0@gmail.com>
Former-commit-id: cacd306b22
This commit is contained in:
Nabarun Gogoi 2023-03-24 17:23:54 +05:30 committed by GitHub
parent 8a4f189286
commit 1951a5d398
24 changed files with 449 additions and 25 deletions

View File

@ -0,0 +1,25 @@
version: "3.2"
services:
laconicd:
restart: unless-stopped
image: cerc/laconicd:local
command: ["sh", "/docker-entrypoint-scripts.d/create-fixturenet.sh"]
volumes:
- ../config/fixturenet-laconicd/create-fixturenet.sh:/docker-entrypoint-scripts.d/create-fixturenet.sh
ports:
- "9473"
- "8545"
- "8546"
- "1317"
healthcheck:
test: ["CMD", "nc", "-v", "localhost", "8545"]
interval: 20s
timeout: 5s
retries: 15
start_period: 10s
networks:
# https://docs.docker.com/compose/networking/#configure-the-default-network
default:
name: mobymask-v2-network

View File

@ -21,6 +21,26 @@ services:
retries: 15 retries: 15
start_period: 10s start_period: 10s
mobymask:
restart: unless-stopped
depends_on:
mobymask-watcher-db:
condition: service_healthy
image: cerc/mobymask:local
working_dir: /app/packages/server
environment:
- ENV=PROD
command: ["sh", "-c", "npm start"]
volumes:
- ../config/watcher-mobymask-v2/secrets.json:/app/packages/server/secrets.json
- moby_data_server:/app/packages/server
healthcheck:
test: ["CMD", "nc", "-v", "localhost", "3330"]
interval: 20s
timeout: 5s
retries: 15
start_period: 10s
mobymask-watcher-server: mobymask-watcher-server:
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
@ -30,18 +50,81 @@ services:
command: ["sh", "-c", "yarn server"] command: ["sh", "-c", "yarn server"]
volumes: volumes:
- ../config/watcher-mobymask-v2/watcher.toml:/app/packages/mobymask-v2-watcher/environments/local.toml - ../config/watcher-mobymask-v2/watcher.toml:/app/packages/mobymask-v2-watcher/environments/local.toml
- ../config/watcher-mobymask-v2/.env:/app/packages/peer/.env - ../config/watcher-mobymask-v2/peer.env:/app/packages/peer/.env
- ../config/watcher-mobymask-v2/relay-id.json:/app/packages/mobymask-v2-watcher/relay-id.json - ../config/watcher-mobymask-v2/relay-id.json:/app/packages/mobymask-v2-watcher/relay-id.json
ports: ports:
- "0.0.0.0:3001:3001" - "0.0.0.0:3001:3001"
- "0.0.0.0:9001:9001" - "0.0.0.0:9001:9001"
- "0.0.0.0:9090:9090" - "0.0.0.0:9090:9090"
healthcheck: healthcheck:
test: ["CMD", "nc", "-v", "localhost", "9090"] test: ["CMD", "busybox", "nc", "localhost", "9090"]
interval: 20s interval: 20s
timeout: 5s timeout: 5s
retries: 15 retries: 15
start_period: 5s start_period: 5s
mobymask-watcher-peer:
restart: unless-stopped
depends_on:
mobymask:
condition: service_healthy
image: cerc/watcher-mobymask-v2:local
command: ["sh", "peer-start.sh"]
volumes:
- ../config/watcher-mobymask-v2/watcher.toml:/app/packages/mobymask-v2-watcher/environments/local.toml
- ../config/watcher-mobymask-v2/peer-id.json:/app/packages/mobymask-v2-watcher/peer-id.json
- ../config/watcher-mobymask-v2/peer-start.sh:/app/packages/mobymask-v2-watcher/peer-start.sh
- moby_data_server:/server
mobymask-app:
depends_on:
mobymask-watcher-server:
condition: service_healthy
mobymask:
condition: service_healthy
image: cerc/mobymask-ui:local
command: ["sh", "mobymask-app-start.sh"]
volumes:
- ../config/watcher-mobymask-v2/mobymask-app.env:/app/.env
- ../config/watcher-mobymask-v2/mobymask-app-config.json:/app/src/mobymask-app-config.json
- ../config/watcher-mobymask-v2/mobymask-app-start.sh:/app/mobymask-app-start.sh
- moby_data_server:/server
ports:
- "0.0.0.0:3002:3000"
healthcheck:
test: ["CMD", "nc", "-v", "localhost", "3000"]
interval: 20s
timeout: 5s
retries: 15
start_period: 10s
shm_size: '1GB'
peer-test-app:
depends_on:
mobymask-watcher-server:
condition: service_healthy
image: cerc/react-peer:local
working_dir: /app/packages/test-app
command: ["sh", "-c", "yarn build && serve -s build"]
volumes:
- ../config/watcher-mobymask-v2/test-app-config.json:/app/packages/test-app/src/config.json
ports:
- "0.0.0.0:3003:3000"
healthcheck:
test: ["CMD", "nc", "-v", "localhost", "3000"]
interval: 20s
timeout: 5s
retries: 15
start_period: 10s
volumes: volumes:
mobymask_watcher_db_data: mobymask_watcher_db_data:
moby_data_server:
networks:
# https://docs.docker.com/compose/networking/#configure-the-default-network
default:
# https://docs.docker.com/compose/networking/#use-a-pre-existing-network
name: mobymask-v2-network
external: true

View File

@ -1 +0,0 @@
RELAY="/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/12D3KooWSPCsVkHVyLQoCqhu2YRPvvM7o6r6NRYyLM5zeA6Uig5t"

View File

@ -1,8 +0,0 @@
{
"relayNodes": [
"/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/12D3KooWSPCsVkHVyLQoCqhu2YRPvvM7o6r6NRYyLM5zeA6Uig5t"
],
"peer": {
"enableDebugInfo": true
}
}

View File

@ -0,0 +1,8 @@
{
"relayNodes": [
"/ip4/127.0.0.1/tcp/9090/ws/p2p/12D3KooWSPCsVkHVyLQoCqhu2YRPvvM7o6r6NRYyLM5zeA6Uig5t"
],
"peer": {
"enableDebugInfo": true
}
}

View File

@ -0,0 +1,9 @@
#!/bin/sh
set -e
# Merging config files to get deployed contract address
jq -s '.[0] * .[1]' /app/src/mobymask-app-config.json /server/config.json > /app/src/config.json
npm run build
serve -s build

View File

@ -0,0 +1 @@
REACT_APP_WATCHER_URI=http://localhost:3001/graphql

View File

@ -0,0 +1,5 @@
{
"id": "12D3KooWK6myjc8r1KBnfP9igp31qJkPaVfsKDjKrjoSefV5SDEo",
"privKey": "CAESQJMHbMaH+UEOtjGOzXYtoPO/cdHakCtN1hcnknIWzx/6ie1lxb+8kfzBjwt7apfj8fHlTCYSIVK8Q2AWu9a2h3g=",
"pubKey": "CAESIIntZcW/vJH8wY8Le2qX4/Hx5UwmEiFSvENgFrvWtod4"
}

View File

@ -0,0 +1,10 @@
#!/bin/sh
# Private key of account with balance
PRIVATE_KEY=
# Assign deployed contract address from server config
CONTRACT_ADDRESS=`jq '.address' /server/config.json`
echo 'yarn peer-listener --contract-address <CONTRACT_ADDRESS> --private-key <PRIVATE_KEY>'
yarn peer-listener --contract-address $CONTRACT_ADDRESS --private-key $PRIVATE_KEY

View File

@ -0,0 +1 @@
RELAY="/ip4/127.0.0.1/tcp/9090/ws/p2p/12D3KooWSPCsVkHVyLQoCqhu2YRPvvM7o6r6NRYyLM5zeA6Uig5t"

View File

@ -0,0 +1,5 @@
{
"rpcUrl": "http://laconicd:8545",
"privateKey": "GENESIS_ACCOUNT_PRIVATE_KEY",
"baseURI": "http://127.0.0.1:3002/#"
}

View File

@ -0,0 +1,8 @@
{
"relayNodes": [
"/ip4/127.0.0.1/tcp/9090/ws/p2p/12D3KooWSPCsVkHVyLQoCqhu2YRPvvM7o6r6NRYyLM5zeA6Uig5t"
],
"peer": {
"enableDebugInfo": true
}
}

View File

@ -31,7 +31,7 @@
enableDebugInfo = true enableDebugInfo = true
[server.p2p.peer] [server.p2p.peer]
relayMultiaddr = '/ip4/mobymask-watcher-server/tcp/9090/http/p2p-webrtc-direct/p2p/12D3KooWSPCsVkHVyLQoCqhu2YRPvvM7o6r6NRYyLM5zeA6Uig5t' relayMultiaddr = '/dns4/mobymask-watcher-server/tcp/9090/ws/p2p/12D3KooWSPCsVkHVyLQoCqhu2YRPvvM7o6r6NRYyLM5zeA6Uig5t'
pubSubTopic = 'mobymask' pubSubTopic = 'mobymask'
peerIdFile = './peer-id.json' peerIdFile = './peer-id.json'
enableDebugInfo = true enableDebugInfo = true
@ -55,7 +55,7 @@
[upstream] [upstream]
[upstream.ethServer] [upstream.ethServer]
gqlApiEndpoint = "http://ipld-eth-server:8083/graphql" gqlApiEndpoint = "http://ipld-eth-server:8083/graphql"
rpcProviderEndpoint = "http://ipld-eth-server:8082" rpcProviderEndpoint = "http://laconicd:8545"
blockDelayInMilliSecs = 60000 blockDelayInMilliSecs = 60000
[upstream.cache] [upstream.cache]

View File

@ -1,11 +1,13 @@
FROM node:18.15.0-alpine3.16 FROM node:18.15.0-alpine3.16
RUN apk --update --no-cache add make git RUN apk --update --no-cache add make git jq
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN npm install -g serve
RUN echo "Building mobymask-ui" && \ RUN echo "Building mobymask-ui" && \
npm install npm install

View File

@ -0,0 +1,13 @@
FROM node:16.17.1-alpine3.16
RUN apk --update --no-cache add python3 alpine-sdk
WORKDIR /app
COPY . .
RUN yarn
# Add scripts
RUN mkdir /scripts
ENV PATH="${PATH}:/scripts"

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Build cerc/mobymask
# See: https://stackoverflow.com/a/246128/1701505
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
docker build -t cerc/mobymask:local -f ${SCRIPT_DIR}/Dockerfile ${CERC_REPO_BASE_DIR}/MobyMask

View File

@ -1,12 +1,14 @@
FROM node:18.15.0-alpine3.16 FROM node:18.15.0-alpine3.16
RUN apk --update --no-cache add make git RUN apk --update --no-cache add make git python3
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN yarn global add serve
RUN echo "Building react-peer" && \ RUN echo "Building react-peer" && \
yarn && yarn workspace @cerc-io/react-peer build yarn install --ignore-scripts && yarn build --ignore @cerc-io/test-app
WORKDIR /app WORKDIR /app

View File

@ -4,7 +4,7 @@ RUN apt-get update \
&& apt-get install -y curl gnupg build-essential \ && apt-get install -y curl gnupg build-essential \
&& curl --silent --location https://deb.nodesource.com/setup_18.x | bash - \ && curl --silent --location https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get update \ && apt-get update \
&& apt-get install -y nodejs git \ && apt-get install -y nodejs git busybox jq \
&& node -v && node -v
RUN corepack enable \ RUN corepack enable \

View File

@ -21,6 +21,7 @@ cerc/uniswap-v3-info
cerc/watcher-mobymask-v2 cerc/watcher-mobymask-v2
cerc/react-peer cerc/react-peer
cerc/mobymask-ui cerc/mobymask-ui
cerc/mobymask
cerc/test-container cerc/test-container
cerc/eth-probe cerc/eth-probe
cerc/builder-js cerc/builder-js

View File

@ -15,6 +15,7 @@ watcher-erc20
watcher-erc721 watcher-erc721
watcher-uniswap-v3 watcher-uniswap-v3
watcher-mobymask-v2 watcher-mobymask-v2
mobymask-laconicd
test test
eth-probe eth-probe
keycloak keycloak

View File

@ -13,6 +13,7 @@ cerc-io/mobymask-watcher
cerc-io/watcher-ts cerc-io/watcher-ts
cerc-io/react-peer cerc-io/react-peer
cerc-io/mobymask-ui cerc-io/mobymask-ui
cerc-io/MobyMask
vulcanize/uniswap-watcher-ts vulcanize/uniswap-watcher-ts
vulcanize/uniswap-v3-info vulcanize/uniswap-v3-info
vulcanize/assemblyscript vulcanize/assemblyscript

View File

@ -10,6 +10,30 @@ Clone required repositories:
laconic-so --stack mobymask-v2 setup-repositories laconic-so --stack mobymask-v2 setup-repositories
``` ```
Checkout to the required versions and branches in repos
```bash
# watcher-ts
cd ~/cerc/watcher-ts
git checkout v0.2.31
# react-peer
cd ~/cerc/react-peer
git checkout v0.2.29
# mobymask-ui
cd ~/cerc/mobymask-ui
git checkout laconic
# laconicd
cd ~/cerc/laconicd
git checkout v0.8.0
# MobyMask
cd ~/cerc/MobyMask
git checkout v0.1.1
```
Build the container images: Build the container images:
```bash ```bash
@ -20,8 +44,58 @@ This should create the required docker images in the local image registry.
Deploy the stack: Deploy the stack:
* Deploy the laconic chain
```bash ```bash
laconic-so --stack mobymask-v2 deploy-system up laconic-so --stack mobymask-v2 deploy-system --include mobymask-laconicd up
```
* Check that laconic chain status is healthy
```bash
docker ps
```
* Export the private key from laconicd
```bash
laconic-so --stack mobymask-v2 deploy-system --include mobymask-laconicd exec laconicd "echo y | laconicd keys export mykey --unarmored-hex --unsafe"
```
* Set the private key in [secrets.json](../../config/watcher-mobymask-v2/secrets.json) file that will be used by mobymask container to deploy contract
* Create a new account
```bash
laconic-so --stack mobymask-v2 deploy-system --include mobymask-laconicd exec laconicd "laconicd keys add alice"
```
* Transfer balance to new account
```bash
laconic-so --stack mobymask-v2 deploy-system --include mobymask-laconicd exec laconicd 'laconicd tx bank send $(laconicd keys show mykey -a) $(laconicd keys show alice -a) 1000000000000000000000000aphoton --fees 2000aphoton'
```
* Export the private key of new account from laconicd
```bash
laconic-so --stack mobymask-v2 deploy-system --include mobymask-laconicd exec laconicd "echo y | laconicd keys export alice --unarmored-hex --unsafe"
```
* Set the private key (`PRIVATE_KEY`) in [peer-start.sh](../../config/watcher-mobymask-v2/peer-start.sh) file that will be used to start the peer that sends txs to L2 chain
* Deploy the other containers
```bash
laconic-so --stack mobymask-v2 deploy-system --include watcher-mobymask-v2 up
```
* Check that all containers are healthy using `docker ps`
NOTE: The `mobymask-ui` container might not start. If mobymask-app is not running at http://localhost:3002, run command again to start the container
```bash
laconic-so --stack mobymask-v2 deploy-system --include watcher-mobymask-v2 up
``` ```
## Tests ## Tests
@ -29,16 +103,16 @@ laconic-so --stack mobymask-v2 deploy-system up
Find the watcher container's id: Find the watcher container's id:
```bash ```bash
docker ps | grep "cerc/watcher-mobymask-v2:local" laconic-so --stack mobymask-v2 deploy-system --include watcher-mobymask-v2 ps | grep "mobymask-watcher-server"
``` ```
Example output Example output
``` ```
8b38e9a64d7e cerc/watcher-mobymask-v2:local "sh -c 'yarn server'" 35 seconds ago Up 14 seconds (health: starting) 0.0.0.0:3001->3001/tcp, 0.0.0.0:9001->9001/tcp, 0.0.0.0:9090->9090/tcp laconic-aeb84676de2b0a7671ae90d537fc7d26-mobymask-watcher-server-1 id: 5d3aae4b22039fcd1c9b18feeb91318ede1100581e75bb5ac54f9e436066b02c, name: laconic-bfb01caf98b1b8f7c8db4d33f11b905a-mobymask-watcher-server-1, ports: 0.0.0.0:3001->3001/tcp, 0.0.0.0:9001->9001/tcp, 0.0.0.0:9090->9090/tcp
``` ```
In above output the container ID is `8b38e9a64d7e` In above output the container ID is `5d3aae4b22039fcd1c9b18feeb91318ede1100581e75bb5ac54f9e436066b02c`
Export it for later use: Export it for later use:
@ -52,10 +126,51 @@ Run the peer tests:
docker exec -w /app/packages/peer $CONTAINER_ID yarn test docker exec -w /app/packages/peer $CONTAINER_ID yarn test
``` ```
## Web Apps
Check that the status for web-app containers are healthy by using `docker ps`
### mobymask-app
The mobymask-app should be running at http://localhost:3002
### peer-test-app
The peer-test-app should be running at http://localhost:3003
## Details
* The relay node for p2p network is running at http://localhost:9090
* The [peer package](https://github.com/cerc-io/watcher-ts/tree/main/packages/peer) (published in [gitea](https://git.vdb.to/cerc-io/-/packages/npm/@cerc-io%2Fpeer)) can be used in client code for connecting to the network
* The [react-peer package](https://github.com/cerc-io/react-peer/tree/main/packages/react-peer) (published in [gitea](https://git.vdb.to/cerc-io/-/packages/npm/@cerc-io%2Freact-peer)) which uses the peer package can be used in react app for connecting to the network
## Demo
Follow the [demo](./demo.md) to try out the MobyMask app with L2 chain
## Clean up ## Clean up
To stop all the services running in background run: Stop all the services running in background run:
```bash ```bash
laconic-so --stack mobymask-v2 deploy-system down laconic-so --stack mobymask-v2 deploy-system --include watcher-mobymask-v2 down
laconic-so --stack mobymask-v2 deploy-system --include mobymask-laconicd down
```
Clear volumes:
* List all volumes
```bash
docker volume ls
```
* Remove volumes created by this stack
Example:
```bash
docker volume rm laconic-bfb01caf98b1b8f7c8db4d33f11b905a_moby_data_server
``` ```

View File

@ -0,0 +1,130 @@
# Demo
* Get the root invite link URL for mobymask-app
```
laconic-so --stack mobymask-v2 deploy-system --include watcher-mobymask-v2 logs mobymask
```
NOTE: Clear the browser cache (local storage) for http://127.0.0.1:3002 to remove old invitations
The invite link is seen at the end of the logs
Example:
```
laconic-bfb01caf98b1b8f7c8db4d33f11b905a-mobymask-1 | http://127.0.0.1:3002/#/members?invitation=%7B%22v%22%3A1%2C%22signedDelegations%22%3A%5B%7B%22signature%22%3A%220x7559bd412f02677d60820e38243acf61547f79339395a34f7d4e1630e645aeb30535fc219f79b6fbd3af0ce3bd05132ad46d2b274a9fbc4c36bc71edd09850891b%22%2C%22delegation%22%3A%7B%22delegate%22%3A%220xc0838c92B2b71756E0eAD5B3C1e1F186baeEAAac%22%2C%22authority%22%3A%220x0000000000000000000000000000000000000000000000000000000000000000%22%2C%22caveats%22%3A%5B%7B%22enforcer%22%3A%220x558024C7d593B840E1BfD83E9B287a5CDad4db15%22%2C%22terms%22%3A%220x0000000000000000000000000000000000000000000000000000000000000000%22%7D%5D%7D%7D%5D%2C%22key%22%3A%220x98da9805821f1802196443e578fd32af567bababa0a249c07c82df01ecaa7d8d%22%7D
```
* In the debug panel, check if it is connected to the p2p network (It should be connected to atleast one other peer for pubsub to work).
* Create an invite link in the app by clicking on `Create new invite link` button.
* Switch to the `MESSAGES` tab in debug panel for viewing incoming messages later.
* Open the invite link in a new browser with different profile (to simulate remote browser)
* Check that it is connected to any other peer in the network.
* In `Report a phishing attempt` section, report multiple phishers using the `submit` button. Click on the `Submit batch to p2p network` button. This broadcasts signed invocations to the connected peers.
* In the `MESSAGES` tab of other browsers, a message can be seen with the signed invocations.
* In a terminal check logs from the watcher peer container.
* Get the container id
```bash
laconic-so --stack mobymask-v2 deploy-system --include watcher-mobymask-v2 ps | grep mobymask-watcher-peer
```
* Check logs
```bash
docker logs -f CONTAINER_ID
```
* It should have received the message, sent transaction to L2 chain and received a transaction receipt with block details.
Example log:
```
2023-03-23T10:25:19.771Z vulcanize:peer-listener [10:25:19] Received a message on mobymask P2P network from peer: PeerId(12D3KooWAVNswtcrX12iDYukEoxdQwD34kJyRWcQTfZ4unGg2xjd)
2023-03-23T10:25:24.143Z laconic:libp2p-utils Transaction receipt for invoke message {
to: '0x558024C7d593B840E1BfD83E9B287a5CDad4db15',
blockNumber: 1996,
blockHash: '0xebef19c21269654804b2ef2d4bb5cb6c88743b37ed77e82222dc5671debf3afb',
transactionHash: '0xf8c5a093a93f793012196073a7d0cb3ed6fbd2846126c066cb31c72100960cb1',
effectiveGasPrice: '1500000007',
gasUsed: '250000'
}
```
* Check the phisher in watcher GQL: http://localhost:3001/graphql
* Use the blockHash from transaction receipt details or query for latest block
```gql
query {
latestBlock {
hash
number
}
}
```
* Get the deployed contract address
```bash
laconic-so --stack mobymask-v2 deploy-system --include watcher-mobymask-v2 exec mobymask-app "cat src/config.json"
```
The value of `address` field is the deployed contract address
* Check for phisher value
```gql
query {
isPhisher(
blockHash: "TX_OR_LATEST_BLOCK_HASH",
contractAddress: "CONTRACT_ADDRESS",
# If reported phisher name was "test" then key0 value is "TWT:test"
key0: "TWT:PHISHER_NAME"
) {
value
}
}
```
It should return true for reported phisher names.
* Watcher internally is using L2 chain `eth_getStorageAt` method.
* Check the phisher name in mobymask app in `Check Phisher Status` section.
* Watcher GQL API is used for checking phisher.
* Manage the invitations by clicking on the `Outstanding Invitations in p2p network`.
* Revoke the created invitation by clicking on `Revoke (p2p network)`
* Revocation messages can be seen in the debug panel `MESSAGES` tab of other browsers.
* Check the watcher peer logs. It should receive a message and log the transaction receipt for revoke message.
* Try reporting a phisher from the revoked invitee's browser.
* The invocation message for reporting phisher would be broadcasted to all peers.
* Check the watcher peer logs. A transaction failed error should be logged.
* Check the reported phisher in [watcher GQL](https://localhost:3001/graphql)
```gql
query {
isPhisher(
blockHash: "LATEST_BLOCK_HASH",
contractAddress: "CONTRACT_ADDRESS",
key0: "TWT:PHISHER_NAME"
) {
value
}
}
```
It should return false as the invitation/delegation used for reporting phishers has been revoked.

View File

@ -4,8 +4,14 @@ repos:
- cerc-io/watcher-ts - cerc-io/watcher-ts
- cerc-io/react-peer - cerc-io/react-peer
- cerc-io/mobymask-ui - cerc-io/mobymask-ui
- cerc-io/laconicd
- cerc-io/MobyMask
containers: containers:
- cerc/watcher-mobymask-v2 - cerc/watcher-mobymask-v2
- cerc/react-peer
- cerc/mobymask-ui - cerc/mobymask-ui
- cerc/laconicd
- cerc/mobymask
pods: pods:
- mobymask-laconicd
- watcher-mobymask-v2 - watcher-mobymask-v2