├── client ├── static │ └── .gitkeep ├── .browserslistrc ├── public │ ├── _redirects │ ├── robots.txt │ ├── favicon.ico │ ├── img │ │ └── icons │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── mstile-150x150.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── apple-touch-icon-120x120.png │ │ │ ├── apple-touch-icon-152x152.png │ │ │ ├── apple-touch-icon-180x180.png │ │ │ ├── apple-touch-icon-60x60.png │ │ │ ├── apple-touch-icon-76x76.png │ │ │ ├── msapplication-icon-144x144.png │ │ │ ├── android-chrome-maskable-192x192.png │ │ │ ├── android-chrome-maskable-512x512.png │ │ │ └── safari-pinned-tab.svg │ └── index.html ├── babel.config.js ├── src │ ├── assets │ │ ├── logo.png │ │ ├── channels │ │ │ ├── dither.png │ │ │ └── wikirona.jpg │ │ └── feather │ │ │ ├── x.svg │ │ │ ├── plus.svg │ │ │ ├── search.svg │ │ │ ├── send.svg │ │ │ ├── user.svg │ │ │ ├── arrow-left.svg │ │ │ ├── bell.svg │ │ │ ├── home.svg │ │ │ ├── dollar-sign.svg │ │ │ ├── x-circle.svg │ │ │ ├── menu.svg │ │ │ ├── mail.svg │ │ │ ├── plus-circle.svg │ │ │ ├── white │ │ │ └── alert-circle.svg │ │ │ ├── x-square.svg │ │ │ ├── edit.svg │ │ │ ├── share.svg │ │ │ ├── color │ │ │ ├── heart.svg │ │ │ └── repeat.svg │ │ │ ├── heart.svg │ │ │ ├── log-in.svg │ │ │ ├── log-out.svg │ │ │ ├── hash.svg │ │ │ ├── repeat.svg │ │ │ ├── save.svg │ │ │ ├── globe.svg │ │ │ ├── users.svg │ │ │ ├── refresh-cw.svg │ │ │ ├── message-circle.svg │ │ │ ├── loader.svg │ │ │ └── settings.svg │ ├── views │ │ ├── Memos.vue │ │ ├── Accounts.vue │ │ ├── Channels.vue │ │ ├── Messages.vue │ │ ├── ChannelsIndex.vue │ │ ├── MemosIndex.vue │ │ ├── MemosNew.vue │ │ ├── Login.vue │ │ ├── Notifications.vue │ │ ├── AccountsIndex.vue │ │ ├── Home.vue │ │ ├── Settings.vue │ │ ├── MemosMemo.vue │ │ ├── ChannelsChannel.vue │ │ ├── AccountsAccount.vue │ │ └── Wallet.vue │ ├── store │ │ ├── modules │ │ │ ├── index.js │ │ │ ├── memoLikes.js │ │ │ ├── notifications.js │ │ │ ├── settings.js │ │ │ ├── accounts.js │ │ │ ├── blockchains.js │ │ │ └── memos.js │ │ ├── defaultFollowing.json │ │ ├── firebase.js │ │ └── index.js │ ├── components │ │ ├── CardWip.vue │ │ ├── BtnLarge.vue │ │ ├── CardMessage.vue │ │ ├── CornerError.vue │ │ ├── SectionDefault.vue │ │ ├── CornerSpinner.vue │ │ ├── AppFooter.vue │ │ ├── AppHeader.vue │ │ ├── AvatarChannel.vue │ │ ├── CardChannel.vue │ │ ├── BtnLoadMore.vue │ │ ├── AvatarAccount.vue │ │ ├── DcBtn.vue │ │ ├── BtnIcon.vue │ │ ├── MemoBody.vue │ │ ├── CardNotification.vue │ │ ├── AccountActions.vue │ │ ├── FormSendMemo.vue │ │ ├── FormSendTokens.vue │ │ ├── FormSetDisplayName.vue │ │ ├── InfiniteFeed.vue │ │ ├── CardAccount.vue │ │ └── CardMemo.vue │ ├── main.js │ ├── registerServiceWorker.disable │ ├── styles │ │ ├── variables.css │ │ └── normalize.css │ ├── App.vue │ ├── scripts │ │ ├── helpers.js │ │ └── tx.js │ └── router │ │ └── index.js ├── .gitignore ├── .eslintrc.js ├── README.md ├── package.json ├── PRIVACY.md └── LICENSE.md ├── .gitignore ├── indexer ├── .gitignore ├── README.md ├── package.json └── index.js ├── .gitmodules ├── docs ├── roadmap-06-indexer.md ├── roadmap-14-ios.md ├── roadmap-07-webapp.md ├── roadmap-12-docs.md ├── roadmap-16-website.md ├── roadmap-17-docs.md ├── roadmap-09-blockchain.md ├── roadmap-15-android.md ├── roadmap-05-spec.md ├── roadmap-08-website.md ├── roadmap-10-ios.md ├── roadmap-11-android.md ├── README.md ├── roadmap-13-blockchain.md ├── roadmap-01-poc.md ├── roadmap-02-spec.md ├── roadmap-03-indexer.md ├── roadmap-04-webapp.md ├── roadmap.md └── software-design.md ├── .github └── stale.yml ├── README.md └── channels.json /client/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /client/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /client/public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /indexer/.gitignore: -------------------------------------------------------------------------------- 1 | serviceAccountKey.json 2 | node_modules 3 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/assets/channels/dither.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/src/assets/channels/dither.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "functions/micro-unfurl"] 2 | path = functions/micro-unfurl 3 | url = https://github.com/beeman/micro-unfurl 4 | -------------------------------------------------------------------------------- /client/src/assets/channels/wikirona.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/src/assets/channels/wikirona.jpg -------------------------------------------------------------------------------- /client/public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /client/public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /client/public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /client/public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /client/public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /client/public/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /client/public/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allinbits/dither-legacy/HEAD/client/public/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /docs/roadmap-06-indexer.md: -------------------------------------------------------------------------------- 1 | # Dither Indexer (beta) 2 | 3 | > This is milestone #6 of the [Dither Roadmap](./roadmap.md). 4 | 5 | ## Requirements 6 | 7 | [Work in progress] 8 | -------------------------------------------------------------------------------- /docs/roadmap-14-ios.md: -------------------------------------------------------------------------------- 1 | # Dither iOS Client (beta) 2 | 3 | > This is milestone #14 of the [Dither Roadmap](./roadmap.md). 4 | 5 | ## Requirements 6 | 7 | [Work in progress] 8 | -------------------------------------------------------------------------------- /indexer/README.md: -------------------------------------------------------------------------------- 1 | # dither-indexer 2 | 3 | This script lets you watch a node on the Cosmos Hub for transactions with memos. If it exists, add it to a Firestore on Firebase. 4 | -------------------------------------------------------------------------------- /client/src/views/Memos.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /docs/roadmap-07-webapp.md: -------------------------------------------------------------------------------- 1 | # Dither Web Client (beta) 2 | 3 | > This is milestone #7 of the [Dither Roadmap](./roadmap.md). 4 | 5 | ## Requirements 6 | 7 | [Work in progress] 8 | -------------------------------------------------------------------------------- /docs/roadmap-12-docs.md: -------------------------------------------------------------------------------- 1 | # Dither Documentation (alpha) 2 | 3 | > This is milestone #12 of the [Dither Roadmap](./roadmap.md). 4 | 5 | ## Requirements 6 | 7 | [Work in progress] 8 | -------------------------------------------------------------------------------- /docs/roadmap-16-website.md: -------------------------------------------------------------------------------- 1 | # Dither Website (beta) 2 | 3 | > This is milestone #16 of the [Dither Roadmap](./roadmap.md). 4 | 5 | ## Requirements 6 | 7 | [Work in progress] 8 | -------------------------------------------------------------------------------- /docs/roadmap-17-docs.md: -------------------------------------------------------------------------------- 1 | # Dither Documentation (beta) 2 | 3 | > This is milestone #17 of the [Dither Roadmap](./roadmap.md). 4 | 5 | ## Requirements 6 | 7 | [Work in progress] 8 | -------------------------------------------------------------------------------- /client/src/views/Accounts.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /client/src/views/Channels.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /docs/roadmap-09-blockchain.md: -------------------------------------------------------------------------------- 1 | # Dither Blockchain Research 2 | 3 | > This is milestone #9 of the [Dither Roadmap](./roadmap.md). 4 | 5 | ## Requirements 6 | 7 | [Work in progress] 8 | -------------------------------------------------------------------------------- /docs/roadmap-15-android.md: -------------------------------------------------------------------------------- 1 | # Dither Android Client (beta) 2 | 3 | > This is milestone #15 of the [Dither Roadmap](./roadmap.md). 4 | 5 | ## Requirements 6 | 7 | [Work in progress] 8 | -------------------------------------------------------------------------------- /docs/roadmap-05-spec.md: -------------------------------------------------------------------------------- 1 | # Dither Protocol Specification (beta) 2 | 3 | > This is milestone #5 of the [Dither Roadmap](./roadmap.md). 4 | 5 | ## Requirements 6 | 7 | [Work in progress] 8 | -------------------------------------------------------------------------------- /client/src/store/modules/index.js: -------------------------------------------------------------------------------- 1 | const files = require.context(".", false, /\.js$/); 2 | let modules = {}; 3 | 4 | files.keys().forEach(key => { 5 | if (key === "./index.js") return; 6 | modules[key.replace(/(\.\/|\.js)/g, "")] = files(key).default; 7 | }); 8 | 9 | export default modules; 10 | -------------------------------------------------------------------------------- /client/src/assets/feather/x.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /docs/roadmap-08-website.md: -------------------------------------------------------------------------------- 1 | # Dither Website (alpha) 2 | 3 | > This is milestone #8 of the [Dither Roadmap](./roadmap.md). 4 | 5 | We need to create a marketing website for Dither. The first question is, should it be part of the Virgo website, or should it be standalone? 6 | 7 | ## Requirements 8 | 9 | [Work in progress] 10 | -------------------------------------------------------------------------------- /docs/roadmap-10-ios.md: -------------------------------------------------------------------------------- 1 | # Dither iOS Client (alpha) 2 | 3 | > This is milestone #10 of the [Dither Roadmap](./roadmap.md). 4 | 5 | We need to write an iOS application for Dither users. The first step is research to see if Apple's guidelines allow an application of this sort. 6 | 7 | ## Requirements 8 | 9 | [Work in progress] 10 | -------------------------------------------------------------------------------- /client/src/assets/feather/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/send.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/arrow-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/bell.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/roadmap-11-android.md: -------------------------------------------------------------------------------- 1 | # Dither Android Client (alpha) 2 | 3 | > This is milestone #11 of the [Dither Roadmap](./roadmap.md). 4 | 5 | We need to write an Android application for Dither users. The first step is research to see if Google's guidelines allow an application of this sort. 6 | 7 | ## Requirements 8 | 9 | [Work in progress] 10 | -------------------------------------------------------------------------------- /client/src/assets/feather/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/store/modules/memoLikes.js: -------------------------------------------------------------------------------- 1 | const memoLikes = { 2 | firestorePath: "memos/{memoId}/likes", 3 | firestoreRefType: "collection", 4 | moduleName: "memoLikes", 5 | statePropName: "data", 6 | namespaced: true, 7 | 8 | state: {}, 9 | getters: {}, 10 | mutations: {}, 11 | actions: {} 12 | }; 13 | 14 | export default memoLikes; 15 | -------------------------------------------------------------------------------- /client/src/assets/feather/dollar-sign.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/x-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/menu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/mail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/plus-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/white/alert-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/x-square.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/store/modules/notifications.js: -------------------------------------------------------------------------------- 1 | const notifications = { 2 | firestorePath: "accounts/{accountId}/notifications", 3 | firestoreRefType: "collection", 4 | moduleName: "notifications", 5 | statePropName: "data", 6 | namespaced: true, 7 | 8 | state: {}, 9 | getters: {}, 10 | mutations: {}, 11 | actions: {} 12 | }; 13 | 14 | export default notifications; 15 | -------------------------------------------------------------------------------- /client/src/assets/feather/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/share.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/color/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/log-in.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/log-out.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/hash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/CardWip.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /client/src/assets/feather/color/repeat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/repeat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/save.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/users.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/feather/refresh-cw.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"], 7 | parserOptions: { 8 | parser: "babel-eslint" 9 | }, 10 | rules: { 11 | // "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 12 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Dither Documentation 2 | 3 | Welcome to Dither! Dither is a new open source project that aims to create an uncensorable and decentralized social network that is built on the Cosmos Hub. Once IBC is launched on the Hub, we will look into launching our own Dither blockchain. 4 | 5 | Documentation is in the works. The [Software Design Document](./software-design.md) is a good place to learn what we're planning on building. 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | const settings = { 2 | firestorePath: "settings/{userId}", 3 | firestoreRefType: "doc", 4 | moduleName: "settings", 5 | statePropName: "data", 6 | 7 | // this object is your store module (will be added as '/settings') 8 | // you can also add state/getters/mutations/actions 9 | state: {}, 10 | getters: {}, 11 | mutations: {}, 12 | actions: {} 13 | }; 14 | 15 | export default settings; 16 | -------------------------------------------------------------------------------- /docs/roadmap-13-blockchain.md: -------------------------------------------------------------------------------- 1 | # Dither Blockchain (testnet) 2 | 3 | > This is milestone #13 of the [Dither Roadmap](./roadmap.md). 4 | 5 | With the launch of IBC, we can look into building and launching an application-specific blockchain for Dither, built with the Cosmos SDK. The milestone is dependent on many factors, including scalability issues, our team composition, and the features of IBC. 6 | 7 | ## Requirements 8 | 9 | [Work in progress] 10 | -------------------------------------------------------------------------------- /client/src/assets/feather/message-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/store/modules/accounts.js: -------------------------------------------------------------------------------- 1 | const accounts = { 2 | firestorePath: "accounts", 3 | firestoreRefType: "collection", // or 'doc' 4 | moduleName: "accounts", 5 | statePropName: "data", 6 | namespaced: true, // automatically added 7 | 8 | // this object is your store module (will be added as '/accounts') 9 | // you can also add state/getters/mutations/actions 10 | state: {}, 11 | getters: {}, 12 | mutations: {}, 13 | actions: {} 14 | }; 15 | 16 | export default accounts; 17 | -------------------------------------------------------------------------------- /client/src/store/modules/blockchains.js: -------------------------------------------------------------------------------- 1 | const accounts = { 2 | firestorePath: "blockchains", 3 | firestoreRefType: "collection", // or 'doc' 4 | moduleName: "blockchains", 5 | statePropName: "data", 6 | namespaced: true, // automatically added 7 | 8 | // this object is your store module (will be added as '/accounts') 9 | // you can also add state/getters/mutations/actions 10 | state: {}, 11 | getters: {}, 12 | mutations: {}, 13 | actions: {} 14 | }; 15 | 16 | export default accounts; 17 | -------------------------------------------------------------------------------- /client/src/components/BtnLarge.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 26 | -------------------------------------------------------------------------------- /client/src/components/CardMessage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | 26 | -------------------------------------------------------------------------------- /client/src/views/Messages.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /indexer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dither-indexer", 3 | "version": "0.1.0", 4 | "description": "The backend indexer for Dither.", 5 | "author": "Peng Zhong <172531+nylira@users.noreply.github.com>", 6 | "license": "Apache-2.0", 7 | "main": "index.js", 8 | "dependencies": { 9 | "axios": "^0.18.1", 10 | "firebase-admin": "^8.9.2", 11 | "is-json": "^2.0.1", 12 | "reconnecting-websocket": "^4.4.0", 13 | "ws": "^7.2.1" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/src/components/CornerError.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /docs/roadmap-01-poc.md: -------------------------------------------------------------------------------- 1 | # Dither Proof-of-Concept 2 | 3 | > This is milestone #1 of the [Dither Roadmap](./roadmap.md). 4 | 5 | We will work on building a proof-of-concept version of Dither and deploy it publicly to the Cosmos community. The POC feature set is unstable and continues to change as we move quickly through iterations and feedback rounds. This POC will inform us on the feasibility of a full-featured Dither, possible future scaling concerns, and also collect invaluable feedback from the community. 6 | 7 | The Proof-of-Concept is available to test at [dither.chat](https://dither.chat). Thanks for your feedback! 8 | -------------------------------------------------------------------------------- /client/src/assets/feather/loader.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # dither-client 2 | 3 | > A web client for Dither. Try it out at [dither.chat](https://dither.chat). 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | 23 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/src/store/defaultFollowing.json: -------------------------------------------------------------------------------- 1 | [ 2 | "cosmos129ekzf5pmmtf2ktkutzpkxch0pedkvq2av35s0", 3 | "cosmos139cqpnqye5jap2q80c862nqnalwvyf7l44xay9", 4 | "cosmos1avmaq45d6jctgc0k50zq9rmrntzmvnu9ll2qc3", 5 | "cosmos1ayf80yefluascnqaugzdcg7zr3rs8w0g0kezfk", 6 | "cosmos1cjshehd2e4s4pzrnw5zy3tk57eage5h3yxp2xn", 7 | "cosmos1dnavhyluk8xy0jqk9sty57p00ldremvp6smenv", 8 | "cosmos1gh4trmptgtdudxvdcvyjuluyf8lk0xhur02p57", 9 | "cosmos1lp73mdganczan8dfhkskhemwaty9vtjrv2frzq", 10 | "cosmos1q7w8wsa6d5z6de0xtfefr62cjvepmue7eysdr4", 11 | "cosmos1t0zcass9ekzdfhqyadsfthwh4w0n3zeafvcn6c", 12 | "cosmos1wmc67yrgnmv2mvpzwvayhrnljzgr5f7ty56zm2", 13 | "cosmos1x3kvwy4mkne0xdvhrkk842y37kdluzgekz3r9k" 14 | ] 15 | -------------------------------------------------------------------------------- /client/src/store/modules/memos.js: -------------------------------------------------------------------------------- 1 | const memos = { 2 | firestorePath: "memos", 3 | firestoreRefType: "collection", // or 'doc' 4 | moduleName: "memos", 5 | statePropName: "data", 6 | namespaced: true, // automatically added 7 | 8 | // this object is your store module (will be added as '/memos') 9 | // you can also add state/getters/mutations/actions 10 | state: {}, 11 | getters: {}, 12 | mutations: {}, 13 | actions: {}, 14 | 15 | serverChange: { 16 | addedHook: function(updateStore, doc, id, store) { 17 | //console.log("added memo", doc); 18 | store.dispatch("rmFromMemoQueue", id); 19 | store.commit("setHeight", doc.height); 20 | updateStore(doc); 21 | } 22 | } 23 | }; 24 | 25 | export default memos; 26 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | import App from "./App"; 4 | 5 | // vue plugins 6 | import VueGtag from "vue-gtag"; 7 | import Vuelidate from "vuelidate"; 8 | import VueMeta from "vue-meta"; 9 | 10 | Vue.use(VueGtag, { 11 | config: { id: "UA-158201725-1" } 12 | }); 13 | Vue.use(Vuelidate); 14 | Vue.use(VueMeta, { 15 | refreshOnceOnNavigation: true 16 | }); 17 | 18 | // sync store and router 19 | import { sync } from "vuex-router-sync"; 20 | import store from "./store/index.js"; 21 | import router from "./router"; 22 | sync(store, router); 23 | Vue.use(VueRouter); 24 | 25 | Vue.config.productionTip = false; 26 | 27 | /* eslint-disable no-new */ 28 | new Vue({ 29 | el: "#app", 30 | router, 31 | store, 32 | render: h => h(App) 33 | }); 34 | -------------------------------------------------------------------------------- /client/src/components/SectionDefault.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 34 | -------------------------------------------------------------------------------- /client/src/components/CornerSpinner.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 40 | -------------------------------------------------------------------------------- /client/src/assets/feather/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/AppFooter.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 | 43 | -------------------------------------------------------------------------------- /client/src/registerServiceWorker.disable: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from "register-service-worker"; 4 | 5 | if (process.env.NODE_ENV === "production") { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | "App is being served from cache by a service worker.\n" + 10 | "For more details, visit https://goo.gl/AFskqB" 11 | ); 12 | }, 13 | registered() { 14 | console.log("Service worker has been registered."); 15 | }, 16 | cached() { 17 | console.log("Content has been cached for offline use."); 18 | }, 19 | updatefound() { 20 | console.log("New content is downloading."); 21 | }, 22 | updated() { 23 | console.log("New content is available; please refresh."); 24 | }, 25 | offline() { 26 | console.log( 27 | "No internet connection found. App is running in offline mode." 28 | ); 29 | }, 30 | error(error) { 31 | console.error("Error during service worker registration:", error); 32 | } 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /client/src/views/ChannelsIndex.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /client/src/components/AppHeader.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 53 | -------------------------------------------------------------------------------- /client/src/components/AvatarChannel.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 31 | 32 | 48 | -------------------------------------------------------------------------------- /client/src/store/firebase.js: -------------------------------------------------------------------------------- 1 | import * as Firebase from "firebase/app"; 2 | import "firebase/firestore"; 3 | 4 | function initFirebase() { 5 | Firebase.initializeApp({ 6 | apiKey: "AIzaSyCkOOMlkKqyk_JUeFik8FA1cjHF571-UP8", 7 | authDomain: "ditherchat.firebaseapp.com", 8 | databaseURL: "https://ditherchat.firebaseio.com", 9 | projectId: "ditherchat", 10 | storageBucket: "ditherchat.appspot.com", 11 | messagingSenderId: "187301558478", 12 | appId: "1:187301558478:web:b68c5a181793205b21f89f", 13 | measurementId: "G-B7DGW8CBME" 14 | }); 15 | return new Promise((resolve, reject) => { 16 | Firebase.firestore() 17 | .enablePersistence() 18 | .then(resolve) 19 | .catch(err => { 20 | if (err.code === "failed-precondition") { 21 | reject(err); 22 | // Multiple tabs open, persistence can only be 23 | // enabled in one tab at a a time. 24 | } else if (err.code === "unimplemented") { 25 | reject(err); 26 | // The current browser does not support all of 27 | // the features required to enable persistence 28 | } 29 | }); 30 | }); 31 | } 32 | 33 | export { Firebase, initFirebase }; 34 | -------------------------------------------------------------------------------- /client/src/components/CardChannel.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 24 | 25 | 50 | -------------------------------------------------------------------------------- /client/src/views/MemosIndex.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /client/src/components/BtnLoadMore.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 44 | 45 | 52 | -------------------------------------------------------------------------------- /client/src/styles/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --tertiary: hsl(288, 67%, 55%); 3 | --secondary: hsl(217, 100%, 70%); 4 | --primary: hsl(233, 96%, 65%); 5 | --link: hsl(233, 96%, 65%); 6 | --hover: #46509f; 7 | --hover-bg: hsl(0, 0%, 98%); 8 | --hover-fg: hsl(0, 0%, 90%); 9 | --bright: hsl(0, 100%, 0%); 10 | --txt: hsla(0, 100%, 0%, 0.8); 11 | --dim: hsla(0, 100%, 0%, 0.667); 12 | --faint: hsla(0, 100%, 0%, 0.4); 13 | --bc: hsla(233, 24%, 75%, 0.175); 14 | --bc-dim: hsla(233, 24%, 75%, 0.0875); 15 | --bc-input: hsl(0, 0%, 50%); 16 | --app-fg: hsl(220, 33%, 98%); 17 | --app-bg: hsl(0, 0%, 100%); 18 | --app-nav: hsl(233, 38%, 14%); 19 | 20 | --success: hsl(120, 58%, 55%); 21 | --success-bc: hsl(120, 58%, 41%); 22 | --warning: hsl(35, 100%, 50%); 23 | --warning-bc: hsl(35, 75%, 38%); 24 | --danger: hsl(0, 100%, 55%); 25 | --danger-bc: hsl(0, 100%, 41%); 26 | 27 | --z-modal: 1000; 28 | --z-app-header: 100; 29 | --z-app-menu: 99; 30 | --z-tool-bar: 98; 31 | --z-list-item: 10; 32 | --z-default: 1; 33 | --z-zero: 0; 34 | --z-below: -1; 35 | 36 | --serif: "Mercury SSm A", "Mercury SSm B", "Georgia", "Times New Roman", serif; 37 | --sans: "Gotham SSm A", "Gotham SSm B", "Helvetica Neue", Arial, Helvetica, 38 | sans-serif; 39 | } 40 | -------------------------------------------------------------------------------- /client/src/components/AvatarAccount.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 35 | 36 | 42 | -------------------------------------------------------------------------------- /client/src/views/MemosNew.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dither (Archived) 2 | 3 | This repository houses the original Dither prototype for on-chain memo messaging. It is now 4 | archived; ongoing product work and community collaboration have moved to `dither.chat` 5 | mono-repo. 6 | 7 | | Home | Link | 8 | | --- | --- | 9 | | Active codebase & issues | [allinbits/dither.chat](https://github.com/allinbits/dither.chat) | 10 | | Live application | [dither.chat](https://dither.chat) | 11 | | Legacy snapshot | [`9869db3`](https://github.com/allinbits/dither/tree/9869db3933d08a3c74b32828a8fbc1c4f89bd9cd) | 12 | 13 | ## What Changed Since This Prototype 14 | 15 | - **Architecture**: This prototype relied on Firestore memo ingestion; `dither.chat` now runs a 16 | full chain indexer, real-time feeds, and wallet integrations. 17 | - **Protocol surface**: Simple comment memos evolved into the richer Dither protocol 18 | (`dither.Post`, `dither.Like`, AuthZ-enabled flows). 19 | - **Operations**: The modern stack ships managed infrastructure, social bridges, and broader tooling 20 | beyond this prototype. 21 | 22 | Dither began under the Virgo initiative as an experiment in fully on-chain social messaging. This 23 | archive preserves that starting point; for current documentation, roadmap, and contribution 24 | guidelines, head to [allinbits/dither.chat](https://github.com/allinbits/dither.chat). 25 | -------------------------------------------------------------------------------- /client/src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /channels.json: -------------------------------------------------------------------------------- 1 | { 2 | "general": { 3 | "title": "general", 4 | "avatar": "", 5 | "description": "This is an uncensored feed of general Dither messages.", 6 | "whitelist": [] 7 | }, 8 | "cosmos": { 9 | "title": "cosmos", 10 | "avatar": "", 11 | "description": "Cosmos is a network of blockchains that interoperate to create the foundation for a new token economy. Dither is built on Cosmos. https://cosmos.network", 12 | "whitelist": [] 13 | }, 14 | "virgo": { 15 | "title": "virgo", 16 | "avatar": "", 17 | "description": "The Virgo vision is to provide the world with the best open collaboration technology. Dither is a project of Virgo. https://virgo.org", 18 | "whitelist": [] 19 | }, 20 | "wikirona": { 21 | "title": "wikirona", 22 | "avatar": "wikirona.jpg", 23 | "description": "A COVID-19 response repository built by the people, for the people. https://github.com/wikirona/wikirona", 24 | "whitelist": [ 25 | "cosmos1lp73mdganczan8dfhkskhemwaty9vtjrv2frzq", 26 | "cosmos1gh4trmptgtdudxvdcvyjuluyf8lk0xhur02p57", 27 | "cosmos1utv5x494jd8g402sj55lx5qc05ryuzx7wwtysg", 28 | "cosmos1gtm9uwx0xsjwwva2kk933y7tmfw5pcfvwmwujn" 29 | ] 30 | }, 31 | "meta": { 32 | "title": "meta", 33 | "avatar": "", 34 | "description": "Ran into a bug with Dither? Have comments or suggestion for future features? Please post here! Source code: https://github.com/virgo-project/dither", 35 | "whitelist": [] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dither-client", 3 | "version": "0.2.0", 4 | "description": "A web client for Dither.", 5 | "author": "Peng Zhong <172531+nylira@users.noreply.github.com>", 6 | "license": "Apache-2.0", 7 | "scripts": { 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "@tendermint/sig": "^0.4.1", 14 | "bip39": "^3.0.2", 15 | "byte-length": "^1.0.2", 16 | "core-js": "^3.6.4", 17 | "date-fns": "^2.10.0", 18 | "firebase": "^7.9.1", 19 | "firebaseui": "^4.4.0", 20 | "get-urls": "^9.2.0", 21 | "identicon.js": "^2.3.3", 22 | "linkifyjs": "^2.1.9", 23 | "pug": "^2.0.4", 24 | "pug-plain-loader": "^1.0.0", 25 | "register-service-worker": "^1.6.2", 26 | "vue": "^2.6.11", 27 | "vue-gtag": "^1.6.2", 28 | "vue-meta": "^2.3.3", 29 | "vue-router": "^3.1.5", 30 | "vuelidate": "^0.7.5", 31 | "vuex": "^3.1.2", 32 | "vuex-easy-firestore": "^1.35.3", 33 | "vuex-router-sync": "^5.0.0" 34 | }, 35 | "devDependencies": { 36 | "@vue/cli-plugin-babel": "~4.2.0", 37 | "@vue/cli-plugin-eslint": "~4.2.0", 38 | "@vue/cli-plugin-pwa": "~4.2.0", 39 | "@vue/cli-plugin-router": "~4.2.0", 40 | "@vue/cli-plugin-vuex": "~4.2.0", 41 | "@vue/cli-service": "~4.2.0", 42 | "@vue/eslint-config-prettier": "^6.0.0", 43 | "babel-eslint": "^10.0.3", 44 | "eslint": "^6.7.2", 45 | "eslint-plugin-prettier": "^3.1.1", 46 | "eslint-plugin-vue": "^6.1.2", 47 | "prettier": "^1.19.1", 48 | "stylus": "^0.54.7", 49 | "stylus-loader": "^3.0.2", 50 | "vue-template-compiler": "^2.6.11" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /client/src/components/DcBtn.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 36 | 37 | 70 | -------------------------------------------------------------------------------- /client/src/views/Notifications.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /client/src/components/BtnIcon.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 40 | 41 | 85 | -------------------------------------------------------------------------------- /docs/roadmap-02-spec.md: -------------------------------------------------------------------------------- 1 | # Dither Protocol Specification (alpha) 2 | 3 | > This is milestone #2 of the [Dither Roadmap](./roadmap.md). 4 | 5 | We need to research, design, and publish a messaging protocol for Dither, built on top of the Cosmos Hub memo field. Once IBC is launched, we should try and update the protocol to support cross-chain communications. 6 | 7 | ## Requirements 8 | 9 | - **Identity transactions** - We have to provide a way for users to specify and customize their identity. This would include the minimum email address, username, display name, avatar. There may be a way to collaborate with starname in implementing identity. 10 | - **Social transactions** - We need to include a way for users to follow, unfollow, and block other users. Without these features, it's difficult for users to be engaged. 11 | - **Message transactions** - The protocol needs to support broadcast-type transactions at a minimum, where one user can broadcast a message to many others. Direct user to user message transactions is also useful, but may be a reach goal. 12 | - **Message interaction transactions** - The protocol needs to support user interactions with messages. This includes things like commenting, liking, and reposting a parent message. 13 | - **Token transactions** - Sending tokens is a primary feature of the Cosmos Hub. We need to make sure to maintain support for this. 14 | - **Offchain transactions** - Not all of the features need to be communicated to the Cosmos Hub. One example are user notifications - I don't think it makes sense to check "read" on a notification and make that transaction on the Hub. There are probably others. 15 | - **Security considerations** - As an uncensorable messaging protocol, there will be times when messages are unwanted by the general community. We need to make sure this protocol has defenses against spammers and malicious attackers. 16 | 17 | ## Reach Goals 18 | 19 | - **IBC support** - We need to understand the IBC spec and figure out if it's possible, and how, to conduct interblockchain messaging with Dither. 20 | - **More messaging transactions** - More types of message transactions, like polls, could be added. 21 | -------------------------------------------------------------------------------- /client/src/views/AccountsIndex.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 60 | 61 | 77 | -------------------------------------------------------------------------------- /docs/roadmap-03-indexer.md: -------------------------------------------------------------------------------- 1 | # Dither Indexer (alpha) 2 | 3 | > This is milestone #3 of the [Dither Roadmap](./roadmap.md). 4 | 5 | The Dither Indexer is a relayer that listens to `newBlock` events coming from a Cosmos Hub full node and parses the transactions in each block, checking for valid Dither transactions. If there are valid Dither transactions, they are added to a centralized database. 6 | 7 | ## Requirements 8 | 9 | - **Feature parity with the Dither Protocol** - The Dither Indexer must support the vast majority of the transaction types proposed in the [Dither Protocol Specification](./roadmap-01-spec). This includes identity, social, messsages, message interactions, tokens, and offchain transactions. 10 | - **Rate limits** - We need to provide reasonable limits to allow developers to access messages and user content that has been indexed by the Dither Indexer. 11 | - **User Authentication** - Dither requires a backend that is capable of supporting user signups and logins. It should authenticate and store user data (like passwords) in a salted and hashed format. We should also look into using third-party authentication like OAuth. 12 | - **Database selection** - We need to research and select a suitable database to store messages indexed by the Dither Indexer. This database needs to performant and work well with multiplatform clients. 13 | - **Wallet support** - Users that access Dither through client software (like a web app or mobile app) require a secure way to store a small amount of ATOM tokens to allow them to create transactions on the Cosmos Hub. 14 | - **Reliability** - The indexer must stay online as much as possible and correctly catch every valid Dither transaction that goes through the Cosmos Hub. If at any point the indexer goes offline, there should be catchup mechanisms to allow the indexer to rapidly collect all missed messages and add them to the database. 15 | - **Security considerations** - As an uncensorable and agnostic messaging protocol, there will be times when malicious actors will spam the Cosmos Hub with valid transactions that are harmful to the Dither community. We need to find out a way to handle and aleviate these sorts of issues through the Dither Indexer. 16 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 37 | 38 | 93 | -------------------------------------------------------------------------------- /client/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 73 | -------------------------------------------------------------------------------- /docs/roadmap-04-webapp.md: -------------------------------------------------------------------------------- 1 | # Dither Web Client (alpha) 2 | 3 | > This is milestone #4 of the [Dither Roadmap](./roadmap.md). 4 | 5 | The Dither Web Client serves as a way for end-users to view messages, post messages, and interact with other users on the Dither social network. We plan on building mobile applications as well, but it makes the most sense to start with a web client. 6 | 7 | ## Requirements 8 | 9 | - **Transaction type parity with Dither Indexer** - The Dither Web Client must support all of the transaction types built in the [Dither Indexer](./roadmap-01-spec). This includes support for identity, social, messsages, message interactions, tokens, and offchain transactions. The client also need to support viewing these transactions in table and list based views. 10 | - **User Settings Page** - Dither users need to be able to set display names, avatars, and be able to purchase usernames. 11 | - **Home Page** - Dither users need to be able to view messages of users that they are following. 12 | - **Search Page** - Dither users need to be able to search through the history of all memos. 13 | - **Notifications Page** - Dither users need to be able to receive notifications if another user comments, likes, or reposts their content. Likewise if another user follows them. 14 | - **Wallet Page** - Dither users need to be able to send and receive ATOM like any other wallet built for the Cosmos Hub. 15 | - **Login/Signup Pages** - Potential users need a way to sign up for the application. 16 | - **Hashtags Support** - We need to support hashtags. Whether if they should be used for a channels like IRC, Slack, Discord or just as hashtags like Facebook, Twitter, etc. is something we have to figure out. 17 | significant whitespace in messaging 18 | - **Security considerations** - The web client heavily involves the use of Cosmos Hub transactions. We need to research and figure out a solution that allows users to securely store their private key while allowing them to frictionlessly make Cosmos Hub transactions. 19 | 20 | ## Reach Goals 21 | 22 | - **Browser extension-based wallet support** - There are upcoming browser extensions for Cosmos blockchains that function similarily to MetaMask for Ethereum-based blockchains. These are known as Lunie and Keplr. We should look into how difficult it would be to support these browser-based wallets. 23 | - **File uploads** - 24 | -------------------------------------------------------------------------------- /client/src/components/MemoBody.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 68 | 69 | 92 | -------------------------------------------------------------------------------- /client/src/components/CardNotification.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 61 | 62 | 95 | -------------------------------------------------------------------------------- /client/src/scripts/helpers.js: -------------------------------------------------------------------------------- 1 | import { formatDistanceStrict } from "date-fns"; 2 | import linkifyHtml from "linkifyjs/html"; 3 | 4 | function getDisplayName(accounts, address) { 5 | if (accounts && accounts[address] && accounts[address].displayname) { 6 | return accounts[address].displayname; 7 | } 8 | return "Anonymous"; 9 | } 10 | 11 | function getMemoPrefix(type, parentAddress) { 12 | switch (type) { 13 | case "comment": 14 | return `/c ${parentAddress} `; 15 | case "like": 16 | return `/l ${parentAddress} `; 17 | case "quote": 18 | return `/q ${parentAddress} `; 19 | case "repost": 20 | return `/r ${parentAddress} `; 21 | default: 22 | return "/p "; 23 | } 24 | } 25 | 26 | function getTxSender(tx) { 27 | let sender = "Loading..."; 28 | if (tx) { 29 | let txEventMessage = tx.events.find(e => e.type === "message"); 30 | let txEventMessageAttribute = txEventMessage.attributes.find( 31 | a => a.key === "sender" 32 | ); 33 | sender = txEventMessageAttribute.value; 34 | } 35 | return sender; 36 | } 37 | 38 | function linkifyMemo(text) { 39 | return linkifyHtml(text, { 40 | formatHref: (href, type) => { 41 | if (type === "hashtag") { 42 | href = "https://dither.chat/channels/" + href.substring(1); 43 | } 44 | return href; 45 | }, 46 | ignoreTags: ["script", "style"] 47 | }); 48 | } 49 | 50 | function timeAgo(timestamp) { 51 | let value = ""; 52 | if (timestamp) { 53 | value = formatDistanceStrict(new Date(timestamp), new Date()); 54 | // an ugly hack to replace longer strings with the short form 55 | value = value.replace(" seconds", "s"); 56 | value = value.replace(" second", "s"); 57 | value = value.replace(" minutes", "m"); 58 | value = value.replace(" minute", "m"); 59 | value = value.replace(" hours", "h"); 60 | value = value.replace(" hour", "h"); 61 | value = value.replace(" days", "d"); 62 | value = value.replace(" day", "d"); 63 | value = value.replace(" weeks", "w"); 64 | value = value.replace(" week", "w"); 65 | value = value.replace(" months", "m"); 66 | value = value.replace(" month", "m"); 67 | value = value.replace(" years", "y"); 68 | value = value.replace(" year", "y"); 69 | return value; 70 | } 71 | return ""; 72 | } 73 | 74 | function truncAddress(addr) { 75 | if (addr) { 76 | let value = addr.slice(7, addr.length); 77 | return value.slice(value.length - 8, value.length); 78 | } else { 79 | return "N/A"; 80 | } 81 | } 82 | 83 | export default { 84 | getDisplayName, 85 | getMemoPrefix, 86 | getTxSender, 87 | linkifyMemo, 88 | timeAgo, 89 | truncAddress 90 | }; 91 | -------------------------------------------------------------------------------- /client/src/components/AccountActions.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 77 | 78 | 98 | -------------------------------------------------------------------------------- /client/src/views/Settings.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 73 | 74 | 81 | -------------------------------------------------------------------------------- /client/src/components/FormSendMemo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 95 | 96 | 117 | -------------------------------------------------------------------------------- /client/src/components/FormSendTokens.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 79 | 80 | 117 | -------------------------------------------------------------------------------- /client/src/components/FormSetDisplayName.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 78 | 79 | 116 | -------------------------------------------------------------------------------- /client/src/scripts/tx.js: -------------------------------------------------------------------------------- 1 | import { signTx, createBroadcastTx } from "@tendermint/sig"; 2 | import store from "../store/index.js"; 3 | 4 | function defaultTx(from, to, amount, memo, gas) { 5 | return { 6 | fee: { 7 | amount: [{ amount: "0", denom: "" }], 8 | gas: gas 9 | }, 10 | memo: memo, 11 | msg: [ 12 | { 13 | type: "cosmos-sdk/MsgSend", 14 | value: { 15 | from_address: from, 16 | to_address: to, 17 | amount: [ 18 | { 19 | denom: "uatom", 20 | amount: amount 21 | } 22 | ] 23 | } 24 | } 25 | ] 26 | }; 27 | } 28 | 29 | //params: { 30 | // from: "cosmos1addr", 31 | // to: "cosmos1addr", 32 | // amount: "0.01", 33 | // memo: "{ type: 'like', parent: 'cosmos1addr' }" 34 | //} 35 | async function sendTx(params) { 36 | // get account information 37 | let account = await fetch( 38 | `${store.state.blockchain.lcd}/auth/accounts/${params.from}` 39 | ); 40 | let accountJson = await account.json(); 41 | // console.log("account info", accountJson.result); 42 | 43 | // if this tx doesn't send tokens, send uatom to this address 44 | if (!params.amount) { 45 | params.to = store.state.blockchain.toAddress; 46 | params.amount = "200"; 47 | } 48 | 49 | let tx = defaultTx( 50 | params.from, 51 | params.to, 52 | params.amount, 53 | params.memo, 54 | store.state.blockchain.defaultGas 55 | ); 56 | 57 | // set the sequence to be the current account sequence plus any queued memos 58 | let newSequence = ( 59 | parseInt(accountJson.result.value.sequence) + 60 | store.state.blockchain.queuedSequence 61 | ).toString(); 62 | 63 | // console.log("sequence", accountJson.result.value.sequence); 64 | // console.log("newSequence", newSequence); 65 | 66 | let signMeta = { 67 | account_number: accountJson.result.value.account_number, 68 | chain_id: store.state.blockchain.chainId, 69 | sequence: newSequence 70 | }; 71 | 72 | let wallet = { 73 | address: params.from, 74 | privateKey: Uint8Array.from(store.state.settings.data.wallet.privateKey), 75 | publicKey: Uint8Array.from(store.state.settings.data.wallet.publicKey) 76 | }; 77 | 78 | // prepare the transaction for sending 79 | const txSigned = signTx(tx, signMeta, wallet); 80 | const txBroadcast = createBroadcastTx(txSigned, "sync"); 81 | 82 | // send tx and await results 83 | let txResponse = await fetch(`${store.state.blockchain.lcd}/txs`, { 84 | method: "POST", 85 | headers: { 86 | "Content-Type": "application/json" 87 | }, 88 | body: JSON.stringify(txBroadcast) 89 | }); 90 | let txResponseJson = await txResponse.json(); 91 | 92 | // console.log("tx params", params); 93 | 94 | if (params.amount === "200") { 95 | let queuedMemo = { 96 | id: txResponseJson.txhash, 97 | address: params.from, 98 | channel: JSON.parse(params.memo).channel, 99 | height: 0, 100 | memo: JSON.parse(tx.memo), 101 | parent: JSON.parse(params.memo).parent, 102 | response: txResponseJson, 103 | timestamp: new Date().toISOString(), 104 | tx: txBroadcast, 105 | type: JSON.parse(params.memo).type, 106 | reposts: 0, 107 | likes: 0, 108 | comments: 0 109 | }; 110 | return queuedMemo; 111 | } else { 112 | let queuedTxSend = { 113 | id: txResponseJson.txhash, 114 | address: params.from, 115 | height: 0, 116 | memo: tx.memo, 117 | response: txResponseJson, 118 | timestamp: new Date().toISOString(), 119 | tx: txBroadcast 120 | }; 121 | return queuedTxSend; 122 | } 123 | } 124 | 125 | export default { sendTx }; 126 | -------------------------------------------------------------------------------- /client/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | 4 | Vue.use(Router); 5 | 6 | export default new Router({ 7 | mode: "history", 8 | base: process.env.BASE_URL, 9 | scrollBehavior(to, from, savedPosition) { 10 | if (savedPosition) { 11 | return savedPosition; 12 | } else { 13 | return { x: 0, y: 0 }; 14 | } 15 | }, 16 | routes: [ 17 | { 18 | path: "/", 19 | name: "home", 20 | component: () => 21 | import(/* webpackChunkName: "home" */ "../views/Home.vue") 22 | }, 23 | { 24 | path: "/accounts", 25 | component: () => 26 | import(/* webpackChunkName: "accounts" */ "@/views/Accounts.vue"), 27 | children: [ 28 | { 29 | path: "/", 30 | name: "accounts", 31 | component: () => 32 | import( 33 | /* webpackChunkName: "accounts-index" */ "@/views/AccountsIndex.vue" 34 | ) 35 | }, 36 | { 37 | path: ":address", 38 | name: "account", 39 | component: () => 40 | import( 41 | /* webpackChunkName: "account" */ "@/views/AccountsAccount.vue" 42 | ) 43 | } 44 | ] 45 | }, 46 | { 47 | path: "/channels", 48 | component: () => 49 | import(/* webpackChunkName: "channels" */ "@/views/Channels.vue"), 50 | children: [ 51 | { 52 | path: "/", 53 | name: "channels", 54 | component: () => 55 | import( 56 | /* webpackChunkName: "channels-index" */ "@/views/ChannelsIndex.vue" 57 | ) 58 | }, 59 | { 60 | path: ":channel", 61 | name: "channel", 62 | component: () => 63 | import( 64 | /* webpackChunkName: "channel" */ "@/views/ChannelsChannel.vue" 65 | ) 66 | } 67 | ] 68 | }, 69 | { 70 | path: "/login", 71 | name: "login", 72 | component: () => 73 | import(/* webpackChunkName: "login" */ "../views/Login.vue") 74 | }, 75 | { 76 | path: "/messages", 77 | name: "messages", 78 | component: () => 79 | import(/* webpackChunkName: "messages" */ "../views/Messages.vue") 80 | }, 81 | { 82 | path: "/memos", 83 | component: () => 84 | import(/* webpackChunkName: "memos" */ "@/views/Memos.vue"), 85 | children: [ 86 | { 87 | path: "/", 88 | name: "memos", 89 | component: () => 90 | import( 91 | /* webpackChunkName: "memos-index" */ "@/views/MemosIndex.vue" 92 | ) 93 | }, 94 | { 95 | path: "new", 96 | name: "memos-new", 97 | component: () => 98 | import(/* webpackChunkName: "memos-new" */ "@/views/MemosNew.vue") 99 | }, 100 | { 101 | path: ":memo", 102 | name: "memo", 103 | component: () => 104 | import(/* webpackChunkName: "memo" */ "@/views/MemosMemo.vue") 105 | } 106 | ] 107 | }, 108 | { 109 | path: "/notifications", 110 | name: "notifications", 111 | component: () => 112 | import( 113 | /* webpackChunkName: "notifications" */ "../views/Notifications.vue" 114 | ) 115 | }, 116 | { 117 | path: "/settings", 118 | name: "settings", 119 | component: () => 120 | import(/* webpackChunkName: "settings" */ "../views/Settings.vue") 121 | }, 122 | { 123 | path: "/wallet", 124 | name: "wallet", 125 | component: () => 126 | import(/* webpackChunkName: "notifications" */ "../views/Wallet.vue") 127 | } 128 | ] 129 | }); 130 | -------------------------------------------------------------------------------- /client/src/components/InfiniteFeed.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 108 | 109 | 116 | -------------------------------------------------------------------------------- /client/src/views/MemosMemo.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /client/src/views/ChannelsChannel.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 127 | 128 | 150 | -------------------------------------------------------------------------------- /client/src/components/CardAccount.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 116 | 117 | 155 | -------------------------------------------------------------------------------- /client/src/views/AccountsAccount.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 137 | 138 | 174 | -------------------------------------------------------------------------------- /docs/roadmap.md: -------------------------------------------------------------------------------- 1 | # Dither Roadmap 2 | 3 | This is the current software development roadmap for Dither. We plan to start official development in Q2 2020, and the estimated end date is Q2 2022. 4 | 5 | ## First six months 6 | 7 | [**Dither Proof-of-Concept**](./roadmap-01-poc.md) - We will work on a proof-of-concept version of Dither and deploy it publicly to the Cosmos community. The POC feature set is unstable and continues to change as we move quickly through iterations and feedback rounds. This POC will inform us on the feasibility of Dither, possible scaling concerns, and also gather invaluable feedback from the community. We will publish the POC on GitHub. (S) 8 | 9 | [**Dither Protocol Specification (alpha)**](./roadmap-02-spec.md) - We will research, design, and publish a messaging protocol for Dither, built on top of the Cosmos Hub memo field, with future support for IBC in mind. We will publish the alpha protocol spec on GitHub. (S) 10 | 11 | [**Dither Indexer (alpha)**](./roadmap-03-indexer.md) - The indexer will save all Cosmos Hub txs that are valid Dither txs. It will also continuously monitor the blockchain for new memos. The REST interface will allow for viewing, filtering, searching across txs, as well as support off-chain user account features. We will publish the Dither Indexer on GitHub. (M) 12 | 13 | [**Dither Web Client (alpha)**](./roadmap-04-webapp.md) - We will deliver a Dither web application that supports mobile and desktop devices. It will support subset of the MVP features that have defined for Dither via the protocol and indexer. We will publish the alpha web client GitHub. (M) 14 | 15 | ## Second six months 16 | 17 | [**Dither Protocol Specification (beta)**](./roadmap-05-spec.md) - We will continue working on on the Dither protocol specification. This will culminate in a beta release within the first year. We will attempt a feature freeze in preparation for the beta releases of the indexer and the web client. We will publish this beta release on GitHub. (S) 18 | 19 | [**Dither Indexer (beta)**](./roadmap-06-indexer.md) - Based on the beta protocol specification, we will continue updating, testing, and securing the Dither Indexer. These changes should increase the feature set of the indexer, improve test coverage, and also should improve performance. This will culminate in a beta release on GitHub. (M) 20 | 21 | [**Dither Web Client (beta)**](./roadmap-07-webapp.md) - We will continue to fix bugs, improve performance, accessibility, and add features to the web client. This will culminate in a beta release on GitHub. (L) 22 | 23 | [**Dither Website (alpha)**](./roadmap-08-website.md) - We will start designing and developing a marketing site for Dither. There will be two distinct audiences: (1) Dither application developers and (2) Dither users. This website will be deployed and source code will be available on GitHub. (S) 24 | 25 | ## Third six months 26 | 27 | [**Dither Blockchain Research**](./roadmap-09-blockchain.md) - Depending on the throughput of the Cosmos Hub and the traction of Dither, we may be forced to move onto a separate blockchain. We will research and define the work required to convert Dither into a custom blockchain application (likely built on the Cosmos SDK). We'll publish this research and development plan on GitHub. (M) 28 | 29 | [**Dither iOS Client (alpha)**](./roadmap-10-ios.md) - We will start designing and building an iOS application for Dither. Initially, it will support recent versions of iPhones and iPod touch devices. We will publish the source on GitHub and deploy via TestFlight. (L) 30 | 31 | [**Dither Android Client (alpha)**](./roadmap-11-android.md) - We will start designing and building an Android application for Dither. Initially, it will support recent versions of popular Android phones. We will publish the source on GitHub and deploy via Google Play Beta. (L) 32 | 33 | [**Dither Documentation (alpha)**](./roadmap-12-docs.md) - We will write documentation for the Dither protocol and indexer. We will also provide tutorials for application developers to implement Dither protocol support. We will publish these docs on GitHub. (S) 34 | 35 | ## Fourth six months 36 | 37 | [**Dither Blockchain (testnet)**](./roadmap-13-blockchain.md) - Dither will likely overload the Cosmos Hub with fluff transactions. Based on the research we conducted earlier, we will build Dither as a custom Cosmos SDK-based application. We will publish the source on GitHub and will launch at least one testnet. (M) 38 | 39 | [**Dither iOS Client (beta)**](./roadmap-14-ios.md) - We will continue maintaining and improving the iOS application for Dither. During this beta phase, we expect that that Dither iOS will support all the MVP features. We will publish the source on GitHub and deploy via TestFlight. (L) 40 | 41 | [**Dither Android Client (beta)**](./roadmap-15-android.md) - We will continue maintaining and improving the iOS application for Dither. During this beta phase, we expect that that Dither iOS will support all the MVP features. We will publish the source on GitHub and deploy via Google Play Beta. (L) 42 | 43 | [**Dither Website (beta)**](./roadmap-16-website.md) - We will continue maintaining the Dither website. We will continue to update site content based on user feedback & research, roadmap clarity, etc. We will publish the source on GitHub. (S) 44 | 45 | [**Dither Documentation (beta)**](./roadmap-17-docs.md) - We will continue maintaining Dither documentation and updating content based on developer feedback, feature improvements, new tutorials, etc. We will publish the source on GitHub. (S) 46 | -------------------------------------------------------------------------------- /client/src/views/Wallet.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 151 | -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import VuexEasyFirestore from "vuex-easy-firestore"; 4 | Vue.use(Vuex); 5 | import router from "../router/index.js"; 6 | 7 | // import firebase 8 | import { Firebase, initFirebase } from "./firebase.js"; 9 | 10 | // import vuex-firestore modules 11 | import accounts from "./modules/accounts.js"; 12 | import blockchains from "./modules/blockchains.js"; 13 | import memos from "./modules/memos.js"; 14 | import memoLikes from "./modules/memoLikes.js"; 15 | import notifications from "./modules/notifications.js"; 16 | import settings from "./modules/settings.js"; 17 | import defaultFollowing from "./defaultFollowing.json"; 18 | 19 | // connect vuex-firestore modules to firestore 20 | const easyFirestore = VuexEasyFirestore( 21 | [accounts, blockchains, memos, memoLikes, notifications, settings], 22 | { 23 | logging: true, 24 | FirebaseDependency: Firebase 25 | } 26 | ); 27 | 28 | // import channels 29 | import channels from "../../../channels.json"; 30 | 31 | const storeData = { 32 | plugins: [easyFirestore], 33 | getters: { 34 | accounts: state => { 35 | return state.accounts.data; 36 | }, 37 | blockchain: state => { 38 | return state.blockchain; 39 | }, 40 | blockchains: state => { 41 | return state.blockchains.data; 42 | }, 43 | chainId: state => { 44 | return state.blockchain.chainId; 45 | }, 46 | channels: state => { 47 | return state.channels; 48 | }, 49 | defaultAccounts: state => { 50 | return state.defaultAccounts.data; 51 | }, 52 | following: state => { 53 | return state.following; 54 | }, 55 | memos: state => { 56 | return state.memos.data; 57 | }, 58 | memoLikes: state => { 59 | return state.memoLikes.data; 60 | }, 61 | notifications: state => { 62 | return state.notifications.data; 63 | }, 64 | settings: state => { 65 | return state.settings.data; 66 | }, 67 | queuedMemos: state => { 68 | return state.queuedMemos; 69 | }, 70 | queuedSequence: state => { 71 | return state.queuedSequence; 72 | }, 73 | user: state => { 74 | return state.user; 75 | }, 76 | userSignedIn: state => { 77 | return state.userSignedIn; 78 | } 79 | }, 80 | state: { 81 | blockchain: { 82 | chainId: "cosmoshub-3", 83 | lcd: "https://lcd.nylira.net", 84 | toAddress: "cosmos1lfq5rmxmlp8eean0cvr5lk49zglcm5aqyz7mgq", 85 | defaultGas: "100000", 86 | height: 0, 87 | queuedSequence: 0, 88 | // load the last x days of memos (at 7s per block) 89 | blockRange: (1 * 24 * 60 * 60) / 7 90 | }, 91 | channels: channels, 92 | userSignedIn: false, 93 | user: { 94 | displayName: "Loading", 95 | providerData: [ 96 | { 97 | photoURL: "Loading", 98 | displayName: "Loading", 99 | providerId: "Loading" 100 | } 101 | ] 102 | }, 103 | following: [], 104 | queuedMemos: {}, 105 | queuedTxSends: {} 106 | }, 107 | actions: { 108 | addToMemoQueue({ commit }, memo) { 109 | commit("addQueuedMemo", memo); 110 | commit("incrementQueuedSequence"); 111 | // console.log("add to queue:", state.queuedMemos[memo.id]); 112 | }, 113 | rmFromMemoQueue({ commit, state }, id) { 114 | if (state.queuedMemos[id]) { 115 | // console.log("rm from queue:", state.queuedMemos[id]); 116 | commit("rmQueuedMemo", id); 117 | commit("decrementQueuedSequence"); 118 | } 119 | }, 120 | authenticate({ commit }) { 121 | return new Promise((resolve, reject) => { 122 | Firebase.auth().onAuthStateChanged(user => { 123 | if (user) { 124 | commit("signInUser", user); 125 | resolve(user); 126 | } else { 127 | reject(); 128 | } 129 | }); 130 | }); 131 | }, 132 | fetchSettings({ dispatch }) { 133 | return new Promise((resolve, reject) => { 134 | dispatch("authenticate") 135 | .then(() => { 136 | dispatch("settings/fetchAndAdd").then(settings => { 137 | resolve(settings); 138 | }); 139 | }) 140 | .catch(() => { 141 | reject(); 142 | }); 143 | }); 144 | }, 145 | fetchFollowingList({ dispatch, commit }) { 146 | return new Promise(resolve => { 147 | dispatch("fetchSettings") 148 | .then(settings => { 149 | const address = settings.wallet.address; 150 | Firebase.firestore() 151 | .collection("accounts") 152 | .doc(address) 153 | .get() 154 | .then(account => { 155 | // add users own account to following 156 | let following = account.data().following; 157 | following.push(address); 158 | 159 | commit("setFollowing", following); 160 | resolve(following); 161 | }); 162 | }) 163 | .catch(() => { 164 | commit("setFollowing", defaultFollowing); 165 | resolve(defaultFollowing); 166 | }); 167 | }); 168 | } 169 | }, 170 | mutations: { 171 | setHeight(state, height) { 172 | if (height > state.blockchain.height) { 173 | state.blockchain.height = height; 174 | // console.log("set blockchain height to", state.blockchain.height); 175 | } 176 | }, 177 | setFollowing(state, following) { 178 | state.following = following; 179 | }, 180 | addFollow(state, address) { 181 | state.following.push(address); 182 | }, 183 | rmFollow(state, address) { 184 | state.following = state.following.filter( 185 | followingAddress => followingAddress !== address 186 | ); 187 | }, 188 | addQueuedMemo(state, memo) { 189 | Vue.set(state.queuedMemos, memo.id, memo); 190 | }, 191 | rmQueuedMemo(state, id) { 192 | Vue.delete(state.queuedMemos, id); 193 | }, 194 | addQueuedTxSends(state, txSend) { 195 | Vue.set(state.queuedTxSends, txSend.id, txSend); 196 | console.log("adding to queue:", state.queuedTxSends[txSend.id]); 197 | }, 198 | incrementQueuedSequence(state) { 199 | state.blockchain.queuedSequence += 1; 200 | // console.log("queuedSequence + 1", state.blockchain.queuedSequence); 201 | }, 202 | decrementQueuedSequence(state) { 203 | state.blockchain.queuedSequence -= 1; 204 | // console.log("queuedSequence - 1", state.blockchain.queuedSequence); 205 | }, 206 | signInUser(state, user) { 207 | state.user = user; 208 | state.userSignedIn = true; 209 | }, 210 | signOutUser(state) { 211 | Firebase.auth().signOut(); 212 | state.user = {}; 213 | state.userSignedIn = false; 214 | router.push({ 215 | name: "login" 216 | }); 217 | } 218 | } 219 | }; 220 | 221 | // init Vuex 222 | const store = new Vuex.Store(storeData); 223 | 224 | // init Firebase 225 | initFirebase().catch(error => { 226 | console.log("there was a firebase error", error); 227 | // take user to a page stating an error occurred 228 | // (might be a connection error, or the app is open in another tab) 229 | }); 230 | 231 | export default store; 232 | -------------------------------------------------------------------------------- /client/PRIVACY.md: -------------------------------------------------------------------------------- 1 | Privacy Policy 2 | ============== 3 | 4 | Last revised on 2020-02-11 5 | 6 | ### The Gist 7 | 8 | All in Bits Inc will collect certain non-personally identify information about you as you use our sites. We may use this data to better understand our users. We can also publish this data, but the data will be about a large group of users, not individuals. 9 | 10 | We will also ask you to provide personal information, but you'll always be able to opt out. If you give us personal information, we won't do anything evil with it. 11 | 12 | We can also use cookies, but you can choose not to store these. 13 | 14 | That's the basic idea, but you must read through the entire Privacy Policy below and agree with all the details before you use any of our sites. 15 | 16 | ### Reuse 17 | 18 | This document is based upon the [Automattic Privacy Policy](http://automattic.com/privacy/) and is licensed under [Creative Commons Attribution Share-Alike License 2.5](http://creativecommons.org/licenses/by-sa/2.5/). Basically, this means you can use it verbatim or edited, but you must release new versions under the same license and you have to credit Automattic somewhere (like this!). Automattic is not connected with and does not sponsor or endorse All in Bits Inc or its use of the work. 19 | 20 | All in Bits Inc, Inc. ("All in Bits Inc") makes available services include our web site (dither.chat), our blog, our API, and any other software, sites, and services offered by All in Bits Inc in connection to any of those (taken together, the "Service"). It is All in Bits Inc's policy to respect your privacy regarding any information we may collect while operating our websites. 21 | 22 | ### Questions 23 | 24 | If you have question about this Privacy Policy, please contact us at peng@virgo.org 25 | 26 | ### Visitors 27 | 28 | Like most website operators, All in Bits Inc collects non-personally-identifying information of the sort that web browsers and servers typically make available, such as the browser type, language preference, referring site, and the date and time of each visitor request. All in Bits Inc's purpose in collecting non-personally identifying information is to better understand how All in Bits Inc's visitors use its website. From time to time, All in Bits Inc may release non-personally-identifying information in the aggregate, e.g., by publishing a report on trends in the usage of its website. 29 | 30 | All in Bits Inc also collects potentially personally-identifying information like Internet Protocol (IP) addresses. All in Bits Inc does not use such information to identify its visitors, however, and does not disclose such information, other than under the same circumstances that it uses and discloses personally-identifying information, as described below. We may also collect and use IP addresses to block users who violated our Terms of Service. 31 | 32 | ### Gathering of Personally-Identifying Information 33 | 34 | Certain visitors to All in Bits Inc's websites choose to interact with All in Bits Inc in ways that require All in Bits Inc to gather personally-identifying information. The amount and type of information that All in Bits Inc gathers depends on the nature of the interaction. All in Bits Inc collects such information only insofar as is necessary or appropriate to fulfill the purpose of the visitor's interaction with All in Bits Inc. All in Bits Inc does not disclose personally-identifying information other than as described below. And visitors can always refuse to supply personally-identifying information, with the caveat that it may prevent them from engaging in certain Service-related activities. 35 | 36 | Additionally, some interactions, such as posting a comment, may ask for optional personal information. For instance, when posting a comment, may provide a website that will be displayed along with a user's name when the comment is displayed. Supplying such personal information is completely optional and is only displayed for the benefit and the convenience of the user. 37 | 38 | ### Aggregated Statistics 39 | 40 | All in Bits Inc may collect statistics about the behavior of visitors to the Service. For instance, All in Bits Inc may monitor the most popular parts of dither.chat. All in Bits Inc may display this information publicly or provide it to others. However, All in Bits Inc does not disclose personally-identifying information other than as described below. 41 | 42 | ### Protection of Certain Personally-Identifying Information 43 | 44 | All in Bits Inc discloses potentially personally-identifying and personally-identifying information only to those of its employees, contractors and affiliated organizations that (i) need to know that information in order to process it on All in Bits Inc's behalf or to provide services available at All in Bits Inc's websites, and (ii) that have agreed not to disclose it to others. Some of those employees, contractors and affiliated organizations may be located outside of your home country; by using the Service, you consent to the transfer of such information to them. All in Bits Inc will not rent or sell potentially personally-identifying and personally-identifying information to anyone. Other than to its employees, contractors and affiliated organizations, as described above, All in Bits Inc discloses potentially personally-identifying and personally-identifying information only when required to do so by law, or when All in Bits Inc believes in good faith that disclosure is reasonably necessary to protect the property or rights of All in Bits Inc, third parties or the public at large. If you are a registered user of the Service and have supplied your email address, All in Bits Inc may occasionally send you an email to tell you about new features, solicit your feedback, or just keep you up to date with what's going on with All in Bits Inc and our products. We primarily use our website and blog to communicate this type of information, so we expect to keep this type of email to a minimum. If you send us a request (for example via a support email or via one of our feedback mechanisms), we reserve the right to publish it in order to help us clarify or respond to your request or to help us support other users. All in Bits Inc takes all measures reasonably necessary to protect against the unauthorized access, use, alteration or destruction of potentially personally-identifying and personally-identifying information. 45 | 46 | ### Cookies 47 | A cookie is a string of information that a website stores on a visitor's computer, and that the visitor's browser provides to the Service each time the visitor returns. All in Bits Inc uses cookies to help All in Bits Inc identify and track visitors, their usage of All in Bits Inc Service, and their Service access preferences. All in Bits Inc visitors who do not wish to have cookies placed on their computers should set their browsers to refuse cookies before using All in Bits Inc's websites, with the drawback that certain features of All in Bits Inc's websites may not function properly without the aid of cookies. 48 | 49 | ### Data Storage 50 | All in Bits Inc uses third party vendors and hosting partners to provide the necessary hardware, software, networking, storage, and related technology required to run the Service. You understand that although you retain full rights to your data, it may be stored on third party storage and transmitted through third party networks. 51 | 52 | ### Privacy Policy Changes 53 | Although most changes are likely to be minor, All in Bits Inc may change its Privacy Policy from time to time, and in All in Bits Inc's sole discretion. All in Bits Inc encourages visitors to frequently check this page for any changes to its Privacy Policy. Your continued use of this site after any change in this Privacy Policy will constitute your acceptance of such change. 54 | -------------------------------------------------------------------------------- /client/src/styles/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { 178 | /* 1 */ 179 | overflow: visible; 180 | } 181 | 182 | /** 183 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 184 | * 1. Remove the inheritance of text transform in Firefox. 185 | */ 186 | 187 | button, 188 | select { 189 | /* 1 */ 190 | text-transform: none; 191 | } 192 | 193 | /** 194 | * Correct the inability to style clickable types in iOS and Safari. 195 | */ 196 | 197 | button, 198 | [type="button"], 199 | [type="reset"], 200 | [type="submit"] { 201 | -webkit-appearance: button; 202 | } 203 | 204 | /** 205 | * Remove the inner border and padding in Firefox. 206 | */ 207 | 208 | button::-moz-focus-inner, 209 | [type="button"]::-moz-focus-inner, 210 | [type="reset"]::-moz-focus-inner, 211 | [type="submit"]::-moz-focus-inner { 212 | border-style: none; 213 | padding: 0; 214 | } 215 | 216 | /** 217 | * Restore the focus styles unset by the previous rule. 218 | */ 219 | 220 | button:-moz-focusring, 221 | [type="button"]:-moz-focusring, 222 | [type="reset"]:-moz-focusring, 223 | [type="submit"]:-moz-focusring { 224 | outline: 1px dotted ButtonText; 225 | } 226 | 227 | /** 228 | * Correct the padding in Firefox. 229 | */ 230 | 231 | fieldset { 232 | padding: 0.35em 0.75em 0.625em; 233 | } 234 | 235 | /** 236 | * 1. Correct the text wrapping in Edge and IE. 237 | * 2. Correct the color inheritance from `fieldset` elements in IE. 238 | * 3. Remove the padding so developers are not caught out when they zero out 239 | * `fieldset` elements in all browsers. 240 | */ 241 | 242 | legend { 243 | box-sizing: border-box; /* 1 */ 244 | color: inherit; /* 2 */ 245 | display: table; /* 1 */ 246 | max-width: 100%; /* 1 */ 247 | padding: 0; /* 3 */ 248 | white-space: normal; /* 1 */ 249 | } 250 | 251 | /** 252 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 253 | */ 254 | 255 | progress { 256 | vertical-align: baseline; 257 | } 258 | 259 | /** 260 | * Remove the default vertical scrollbar in IE 10+. 261 | */ 262 | 263 | textarea { 264 | overflow: auto; 265 | } 266 | 267 | /** 268 | * 1. Add the correct box sizing in IE 10. 269 | * 2. Remove the padding in IE 10. 270 | */ 271 | 272 | [type="checkbox"], 273 | [type="radio"] { 274 | box-sizing: border-box; /* 1 */ 275 | padding: 0; /* 2 */ 276 | } 277 | 278 | /** 279 | * Correct the cursor style of increment and decrement buttons in Chrome. 280 | */ 281 | 282 | [type="number"]::-webkit-inner-spin-button, 283 | [type="number"]::-webkit-outer-spin-button { 284 | height: auto; 285 | } 286 | 287 | /** 288 | * 1. Correct the odd appearance in Chrome and Safari. 289 | * 2. Correct the outline style in Safari. 290 | */ 291 | 292 | [type="search"] { 293 | -webkit-appearance: textfield; /* 1 */ 294 | outline-offset: -2px; /* 2 */ 295 | } 296 | 297 | /** 298 | * Remove the inner padding in Chrome and Safari on macOS. 299 | */ 300 | 301 | [type="search"]::-webkit-search-decoration { 302 | -webkit-appearance: none; 303 | } 304 | 305 | /** 306 | * 1. Correct the inability to style clickable types in iOS and Safari. 307 | * 2. Change font properties to `inherit` in Safari. 308 | */ 309 | 310 | ::-webkit-file-upload-button { 311 | -webkit-appearance: button; /* 1 */ 312 | font: inherit; /* 2 */ 313 | } 314 | 315 | /* Interactive 316 | ========================================================================== */ 317 | 318 | /* 319 | * Add the correct display in Edge, IE 10+, and Firefox. 320 | */ 321 | 322 | details { 323 | display: block; 324 | } 325 | 326 | /* 327 | * Add the correct display in all browsers. 328 | */ 329 | 330 | summary { 331 | display: list-item; 332 | } 333 | 334 | /* Misc 335 | ========================================================================== */ 336 | 337 | /** 338 | * Add the correct display in IE 10+. 339 | */ 340 | 341 | template { 342 | display: none; 343 | } 344 | 345 | /** 346 | * Add the correct display in IE 10. 347 | */ 348 | 349 | [hidden] { 350 | display: none; 351 | } 352 | -------------------------------------------------------------------------------- /indexer/index.js: -------------------------------------------------------------------------------- 1 | const isJson = require('is-json') 2 | const channels = require("../channels.json") 3 | 4 | // setup firebase 5 | const admin = require("firebase-admin"); 6 | let serviceAccount = require("./serviceAccountKey.json"); 7 | admin.initializeApp({ 8 | credential: admin.credential.cert(serviceAccount) 9 | }); 10 | let db = admin.firestore(); 11 | 12 | // setup state 13 | const state = { 14 | lcd: "https://lcd.nylira.net", 15 | wss: "wss://rpc.nylira.net:443/websocket", 16 | block: {}, 17 | txs: [] 18 | }; 19 | 20 | // setup websocket 21 | const WebSocket = require("ws"); 22 | const ReconnectingWebSocket = require("reconnecting-websocket"); 23 | let ws = new ReconnectingWebSocket(state.wss, [], { WebSocket: WebSocket }); 24 | const axios = require("axios"); 25 | 26 | ws.onopen = function() { 27 | ws.send( 28 | JSON.stringify({ 29 | jsonrpc: "2.0", 30 | method: "subscribe", 31 | id: "1", 32 | params: ["tm.event = 'NewBlock'"] 33 | }) 34 | ); 35 | }; 36 | 37 | ws.onmessage = function(msg) { 38 | let msgData = JSON.parse(msg.data); 39 | // console.log(msgData); 40 | // console.log(msgData.result.data); 41 | if (msgData.result.data && msgData.result.data.value) { 42 | let event = msgData.result.data.value; 43 | // console.log(event); 44 | setBlockchainHeader(event.block.header) 45 | state.block = event.block; 46 | getTxs(event.block.header.height); 47 | } 48 | }; 49 | 50 | async function getTxs(height) { 51 | // let url = `${state.lcd}/txs?tx.height=${height - 1}`; 52 | let url = `${state.lcd}/txs?tx.height=${height}`; 53 | let transactions = await axios.get(url); 54 | processTxs(transactions.data); 55 | return Promise.resolve(); 56 | } 57 | 58 | function processTxs(txs) { 59 | txs.txs.forEach(tx => { 60 | // console.log('tx') 61 | // console.log('tx.logs', tx.logs) 62 | let txLogMsgZero = tx.logs.find(l => l.msg_index == 0); 63 | if (txLogMsgZero.success && isJson(tx.tx.value.memo)) { 64 | 65 | // enforce recipient is dither's wallet 66 | let validTxRecipient = tx.tx.value.msg[0].value.to_address === "cosmos1lfq5rmxmlp8eean0cvr5lk49zglcm5aqyz7mgq" 67 | 68 | // enforce minimum cost of transaction 69 | let validTxCost = tx.tx.value.msg[0].value.amount[0].amount === "200" 70 | 71 | // enforce whitelist for memos posted in channels 72 | let validTxSender = true 73 | let sender = getSender(tx) 74 | let parsedMemo = destructureMemo(tx.tx.value.memo); 75 | let channelName = parsedMemo.channel 76 | if (channelName) { 77 | let channel = channels[channelName] 78 | // console.log('memo is part of a channel:', channel) 79 | validTxSender = channel.whitelist.includes(sender) 80 | // console.log('is the tx sender within the whitelist?', validTxSender) 81 | // console.log('whitelist', channel.whitelist, 'sender', sender) 82 | } 83 | 84 | if (validTxRecipient && validTxCost && validTxSender) { 85 | console.log("valid memo", tx); 86 | writeTx(tx); 87 | } else { 88 | console.log('msg cost too low || recipient not Dither', msgCost) 89 | } 90 | } 91 | }); 92 | } 93 | 94 | const increment = admin.firestore.FieldValue.increment(1); 95 | 96 | function writeTx(tx) { 97 | let txId = tx.txhash 98 | let txData = { 99 | height: parseInt(tx.height), 100 | timestamp: tx.timestamp, 101 | address: getSender(tx), 102 | tx: tx, 103 | comments: 0, 104 | reposts: 0 105 | }; 106 | txData = { ...txData, ...destructureMemo(tx.tx.value.memo) }; 107 | 108 | // create a memo 109 | db.collection("memos") 110 | .doc(txId) 111 | .set(txData); 112 | 113 | // increment memos count by one 114 | db.collection("accounts") 115 | .doc(txData.address) 116 | .set({ memos: increment }, { merge: true }); 117 | 118 | if (txData.type === "comment") { 119 | db.collection("memos") 120 | .doc(txData.parent) 121 | .set({ comments: increment }, { merge: true }); 122 | } 123 | 124 | if (txData.type === "repost") { 125 | // add repost to memo 126 | db.collection("memos").doc(txData.parent) 127 | .collection("reposts").doc(txId).set(txData) 128 | 129 | // increment repost count 130 | db.collection("memos").doc(txData.parent) 131 | .set({ reposts: increment }, { merge: true }); 132 | 133 | // add like to account 134 | db.collection("accounts").doc(txData.address) 135 | .collection("reposts").doc(txId).set(txData) 136 | 137 | notifyAccount(txId, txData) 138 | } 139 | 140 | if (txData.type === "like") { 141 | // add like to memo 142 | db.collection("memos").doc(txData.parent) 143 | .collection("likes").doc(txId).set(txData) 144 | 145 | // increment like count 146 | db.collection("memos").doc(txData.parent) 147 | .set({ likes: increment }, { merge: true }); 148 | 149 | // add like to account 150 | db.collection("accounts").doc(txData.address) 151 | .collection("likes").doc(txId).set(txData) 152 | 153 | notifyAccount(txId, txData) 154 | } 155 | 156 | if (txData.type === "follow") { 157 | // add followed account to user account 158 | db.collection("accounts").doc(txData.address).update({ 159 | following: admin.firestore.FieldValue.arrayUnion(txData.parent) 160 | }) 161 | 162 | // add user account to followed's account 163 | db.collection("accounts").doc(txData.parent).update({ 164 | followers: admin.firestore.FieldValue.arrayUnion(txData.address) 165 | }) 166 | 167 | notifyAccountFollow(txId, txData) 168 | } 169 | 170 | if (txData.type === "unfollow") { 171 | console.log(txData.address, 'unfollows', txData.parent) 172 | db.collection("accounts").doc(txData.address).update({ 173 | following: admin.firestore.FieldValue.arrayRemove(txData.parent) 174 | }) 175 | db.collection("accounts").doc(txData.parent).update({ 176 | followers: admin.firestore.FieldValue.arrayRemove(txData.address) 177 | }) 178 | } 179 | 180 | if (txData.type === "set-displayname") { 181 | db.collection("accounts") 182 | .doc(txData.address) 183 | .set({ displayname: txData.memo.body }, { merge: true }); 184 | } 185 | 186 | } 187 | 188 | // helper functions 189 | function getSender(tx) { 190 | let sender = "Loading..."; 191 | if (tx) { 192 | let txEventMessage = tx.events.find(e => e.type === "message"); 193 | let txEventMessageAttribute = txEventMessage.attributes.find( 194 | a => a.key === "sender" 195 | ); 196 | sender = txEventMessageAttribute.value; 197 | } 198 | return sender; 199 | } 200 | 201 | function destructureMemo(memoJson) { 202 | let memo = JSON.parse(memoJson) 203 | let data = { 204 | memo: memo, 205 | type: memo.type, 206 | } 207 | if (memo.channel) { 208 | data.channel = memo.channel 209 | } 210 | if (memo.parent) { 211 | data.parent = memo.parent 212 | } 213 | return data 214 | } 215 | 216 | function setBlockchainHeader(header) { 217 | console.log(header.height); 218 | db.collection("blockchains") 219 | .doc("cosmoshub-3") 220 | .set({ header: header }, { merge: true }); 221 | } 222 | 223 | // notify account about memo interaction 224 | function notifyAccount(txId, txData) { 225 | let parentMemoRef = db.collection("memos").doc(txData.parent) 226 | parentMemoRef.get() 227 | .then(doc => { 228 | if (!doc.exists) { 229 | console.log('No such document!'); 230 | } else { 231 | let parentMemo = doc.data() 232 | let txDataNotification = JSON.parse(JSON.stringify(txData)) 233 | txDataNotification.read = false 234 | db.collection("accounts").doc(parentMemo.address) 235 | .collection("notifications").doc(txId).set(txDataNotification) 236 | } 237 | }) 238 | .catch(err => { 239 | console.log('Error getting document', err); 240 | }); 241 | } 242 | 243 | // notify account about follow 244 | function notifyAccountFollow(txId, txData) { 245 | let txDataNotification = JSON.parse(JSON.stringify(txData)) 246 | txDataNotification.read = false 247 | db.collection("accounts").doc(txData.parent) 248 | .collection("notifications").doc(txId).set(txDataNotification) 249 | } 250 | -------------------------------------------------------------------------------- /client/src/components/CardMemo.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 239 | 240 | 343 | -------------------------------------------------------------------------------- /docs/software-design.md: -------------------------------------------------------------------------------- 1 | # Dither Software Design Document 2 | 3 | ## 1. Vision 4 | 5 | Today's social networks are means for the majority of humanity to communicate with their local and global communities. But these networks are owned by corporations that serve their shareholders, and not their billions of users. It is the mission of Dither to solve the issue of centralized power and censorship created by current social networks. We hope to offer to users of Dither their own sovereign channel of communication to the world without fearing for censorship or bans. 6 | 7 | ### 1.1 Audiences 8 | 9 | There will be different types of users who may be interested in using Dither. Here are some of the audience categories. 10 | 11 | #### 1.1.1 ATOM Holder 12 | 13 | The initial audience that we hope will flock to Dither are ATOM holders. There are disputes around ownership of discussion channels in the Cosmos community. The primary issue is that a decentralized network does not map well to a communication channels with single sources of authority. Whoever owns the communication channel wields too much power in a decentralized network. 14 | 15 | Dither hopes to fix that problem. It's built on the Cosmos Hub where admin level permissions don't exist. People are free to communicate on any topic they want without fear of being banned by a moderator, and other people can follow their preferred source of news and block the others. 16 | 17 | #### 1.1.2 Dither Developer 18 | 19 | An interesting audience segment for Dither is the application developer. Since Dither is an open protocol, it enables third-party Cosmos SDK developers to embed Dither within their own product. The possiblities are endless, but we have to make sure our APIs are well-documented and code is well-structured to allow developers to easily dig in. 20 | 21 | We need an incentive structure to enourage developers to upstream their code. One tantalizing option is developing a plugin system that encourages the community to publish their own open-source Dither modules for everyone else. 22 | 23 | #### 1.1.3 Blockchain Enthusiast 24 | 25 | Similar to ATOM token holders, blockchain enthusiasts understand the resilience of decentralized governance. We're hoping blockchain enthusiasts will come onboard Dither and bring along their own communities. 26 | 27 | We can encourage blockchain enthusiasts and investors to join by building and promoting a Dither blockchain with some sort of Dither token. 28 | 29 | #### 1.1.4 Mainstream Early Adopter 30 | 31 | Over time, assuming we build a great product, the majority of the people using Dither will be mainstream early adopters. It's been clear over the years that social media corporations do not have our best interests at heart. They have a terrifying power to ban anything they disagre with. As is their right as private corporations. But we believe that communication should not be censored -- thus Dither. 32 | 33 | We can court the mainstream early adopter by building a strong brand and a very usable product. They should not have to know anything about blockchain or ~7 second transaction times to use Dither. 34 | 35 | ### 1.2 Defining Success 36 | 37 | The first level of success is gauging usage. How many people are using Dither on a day-to-day basis? How much time do they spend using the application? Is the number of users growing over time? 38 | 39 | The second level of success is courting developers. If we manage to bring together a healthy ecosystem of developers who use and develop on Dither or on top of Dither, we can say we've succeeded on higher level. 40 | 41 | What would make Dither truly successful? We think that Dither will be successful when it becomes self-sustaining. We're hoping for a community of engaged users and volunteer developers to emerge from the initial set of users. We hope will self-govern and organize themselves to continue running Dither into perpetuity. 42 | 43 | ## 2. System Overview 44 | 45 | Dither is a collection of open source components that works together to enable an uncensorable and decentralized social network for web and mobile devices. User interaction on Dither is powered by the Cosmos Hub blockchain. User data is stored in an SQL database that is written to by a transaction indexing tool that we create. 46 | 47 | ### 2.1 Features 48 | 49 | Dither needs to meet a set of functionaity requirements, listed below. 50 | 51 | #### 2.1.1 Follow 52 | 53 | Users need to be able to follow another user's account. When they follow a user's account, that user should get a notification that they gained this new follower. The user should also be able to unfollow another user at any time. This transaction should be on-chain. 54 | 55 | A user's homepage feed consists primarily of the latest posts from the people they follow. 56 | 57 | #### 2.1.2 Post 58 | 59 | Users need to be able to post new dithers. Due to the limitations of the Cosmos Hub memo field, Dithers are chunks of content that contain at most 512 bytes of data. The data is formatted in JSON and can include just about anything. When the user hits post, the dither content is validated and then is broadcasted via a Cosmos Hub transaction. Once the transaction is validated by the Cosmos Hub, the dither content is appended to database via the Dither Index. The new dither is pushed to all of the users followers, appearing on their respective homepages. 60 | 61 | #### 2.1.3 Search 62 | 63 | Users need to be able to search through the dither database created by the Dither Indexer. This is an off-chain query. 64 | 65 | #### 2.1.4 Notify 66 | 67 | Users should be notified if someone: (1) replies to them, (2) likes their dither, (3) reposts their dither, or (4) starts following them. 68 | 69 | #### 2.1.5 Send 70 | 71 | Users should able to send ATOM to other users and to other cosmos1 addresses. 72 | 73 | ### 2.2 System Components 74 | 75 | Dither is made up of three core components: (1) the blockchain, (2) the indexer, and (3) the client. This section will detail out these three parts. 76 | 77 | #### 2.2.1 Dither Blockchain 78 | 79 | Dither is a social network powered by blockchain technology. Currently, this is the Cosmos Hub blockchain. We expect that with future growth, we will strain the limits of the transaction throughput on the Cosmos Hub. Well, either that or the transactions will be too expensive to combat the greater usage. The long-term goal of Dither is to put it onto its own application-specific blockchain. 80 | 81 | ##### 2.2.1.1 Dither on the Cosmos Hub 82 | 83 | For at least first year of development, we expect that we will be piggy-backing off of the Cosmos Hub blockchain. 84 | 85 | ##### 2.2.1.2 Dither on an application-specific blockchain 86 | 87 | Going into the second year, we will have the resources to research, build and launch a custom blockchain for Dither. 88 | 89 | #### 2.2.2. Dither Indexer 90 | 91 | The Dither Indexer is a database that interacts with a Cosmos Hub full node to query for and store information. It is the primary data store for Dither and is queried by various front-ends to allow users to sign up, login, view, send, and query for messages. 92 | 93 | #### 2.2.3 Dither Client 94 | 95 | THe Dither client is an interchangeable component of Dither. We plan on building and maintaining three separate clients in order to be as accessible to as large of an audience as possible. 96 | 97 | ##### 2.2.3.1 Dither Web 98 | The Dither Web Client is a graphical user interface built with modern web technologies. It is the first client that we will build for Dither, and it will serve as the reference implementation for the iOS and Android applications. 99 | 100 | ##### 2.2.3.2 Dither iOS 101 | The iOS Client is a full-featured Dither application for recent iOS devices. 102 | 103 | ##### 2.2.3.3 Dither Android 104 | The Android Client is a full-featured Dither application for recent Android devices. 105 | 106 | ## 3. Dither Client Specification 107 | 108 | ### 3.1 Application Routes 109 | 110 | Note that `(auth)` means that the user needs to be logged in, and that `(no-ui)` means this route doesn't have a page associated with it, and it should redirect the user to another page. 111 | 112 | * /explore - The homepage for signed out users. Encourages users to sign up for a new account. 113 | * /welcome - (no-ui) This is the signup flow for new users 114 | * /welcome/signup - First step of signup, accepts a username and password. 115 | * Field - Username 116 | * Field - Password 117 | * Button - Continue 118 | * /welcome/wallet - Second step signup that creates a wallet for the user 119 | * Text - Explanation of what this wallet is for 120 | * Text - Displays the private key for the user to backup manually 121 | * Button - I've backed up my mnemonic 122 | * /welcome/wallet-confirm - Second step signup that creates a wallet for the user 123 | * Textarea - Requests the user to input part of their private key 124 | * Text - Displays checkmark if the private key is correct 125 | * Button - Start over and generate a new wallet 126 | * Button - Continue 127 | * /welcome/tokens - Third step signup that somehow gives tokens to the user 128 | * Button - Get tokens 129 | * Text - Displays the current user balance 130 | * Button - I have tokens! 131 | * /welcome/follow - Fourth step of signup that encourages the user to follow some accounts. 132 | * /login - Displays a form to allow registered users to login. 133 | * /index - (auth) The homepage for signed in users. Displays recent messages posted by signed-in users's following 134 | * /accounts - (no-ui) 135 | * /accounts/:account - Displays recent messages posted by a specific account. 136 | * /accounts/:account/messages/:message - Displays a specific message posted by a specific account. 137 | * /notifications - (auth) Displays a list of notifications for the signed-in user. 138 | * /search - (no-ui) supports queries for block height, transaction hash, @username, cosmos address, hashtag, or plaintext. 139 | * /search/:height - Displays messages posted on a specific block height 140 | * /search/:hash - Displays a specific message with this transaction hash 141 | * /search/:username - Displays users that have a similar username to the query 142 | * /search/:address - Displays recent messages posted by a specific address 143 | * /search/:hashtag - Displays recent messages with a specific hashtag 144 | * /account - (no-ui) These pages apply to a specific user. 145 | * /account/wallet - (auth) Displays a user's cosmos address and token balance, a form to send tokens to a username or cosmos address, and also a list of historical transactions 146 | * /account/settings - (auth) Displays forms to allow users to change their username, avatar, and other application settings. 147 | * /account/forgot - Displays a form to allow signed-out users to reset their password 148 | 149 | ### 3.2 App Components 150 | 151 | * cardMessage 152 | * cardAccount 153 | * pageHeaderAccount 154 | * appHeader 155 | * appMenu 156 | 157 | ## 4. Roadmap 158 | 159 | See the [roadmap](./roadmap.md). 160 | -------------------------------------------------------------------------------- /client/public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /client/LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2020 Virgo 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | --------------------------------------------------------------------------------