Skip to content
This repository has been archived by the owner on Aug 1, 2022. It is now read-only.

Commit

Permalink
feat(ui): Funding Pool v0
Browse files Browse the repository at this point in the history
The v0 of the Funding Pool is in alpha. The feature is only available
behind the isExperimental and fundingFeature flags. It operates using a
local ethereum node. All relevant documentation can be found in
FUNDING.md.

This is the result of a joined effort of multiple people. A special
thanks to @geigerzaehler, @CodeSandwich, and @brandonhaslegs.
  • Loading branch information
NunoAlexandre committed Jan 21, 2021
1 parent 88fdbec commit 5b4a327
Show file tree
Hide file tree
Showing 82 changed files with 6,842 additions and 155 deletions.
3 changes: 3 additions & 0 deletions DEVELOPMENT.md
Expand Up @@ -154,6 +154,9 @@ Here's a list of all scripts that are intended for developer use:
# - monorepo
# - saved preferences

yarn ethereum:start # Setup a local ethereum node to which we deploy
# the Radicle Contracts and set the intial balance
# of a stated local ethereum development account.

### Design System

Expand Down
86 changes: 86 additions & 0 deletions FUNDING.md
@@ -0,0 +1,86 @@
### Overview

Three moving pieces back the Radicle Funding experiences:

- An Ethereum wallet, owned by the user
- A connection between the Radicle Upstream (the app) and said Ethereum wallet
- The Radicle Contracts (Ethereum smart-contracts)

The funding experiences provided in the Radicle Upstream are Ethereum-based,
meaning that actions such as adding users to the list of receivers, collect
incoming support funds, and others, translate into ethereum transactions.

For users to approve those transactions originated in the app, they need to
establish a connection between an Ethereum wallet and the app. We provide this
capability through a WalletConnect integration. A substantial number of Ethereum
wallets support WalletConnect. [You can find the complete list here][wcw].

Once a wallet is connected to the app, the funding experiences become available
to the user. The user can now set up and edit their support, receive funds, etc.
All of these actions translate into transactions the user must review, (i.e.,
approve or reject) on their connected wallet.

These same transactions are provided and ran by the [Radicle Contracts][rc], our
custom Ethereum smart-contracts.


#### Development environment

In the development environment, we set up these three pieces as follows:

- A local WalletConnect test wallet instance

This instance will play the role of a real wallet:
- It provides a couple of test accounts that we use in development.
- The transactions triggered in the app will be prompted here for the user
to approve or reject.

- A local ganache instance

Ganache provides a local Ethereum RPC client for testing and development. The
Radicle Contracts are deployed to this instance. Here, we also set an initial
balance of the account we choose for development purposes.

For piece of mind, note that this instance has no connection to other networks
such as mainnet or testnet. Therefore, no real assets are ever used. Feel free
to play around!

![Radicle Funding Development Set up][dev-setup]

**Getting started**

- Install [walletconnect-test-wallet][wctw]

- `git clone git@github.com:radicle-dev/walletconnect-test-wallet.git`
- `cd walletconnect-test-wallet`
- `npm install`

- Set up the local test ethereum account

- Run `npm run start` within `walletconnect-test-wallet`. It should open the
test wallet in your browser at `localhost:3000`.

- Copy the full Ethereum address provided at the top of the page.

- Now, in `radicle-upstream`, run:

- `yarn install`
- `mkdir sandbox`
- `touch sandbox/.local-eth-account`
- Finally, paste the copied address in the previous step into this file.

**Running**

With everything installed and set up, run the following commands in different
tabs:

- `npm run start` within `walletconnect-test-wallet`
- `yarn ethereum:start` within `radicle-upstream`
`RADICLE_UPSTREAM_EXPERIMENTAL=true yarn start` within `radicle-upstream`
- Once the app is running, enable the funding feature in the Upstream settings


[wcw]:https://walletconnect.org/wallets/
[wctw]:https://github.com/radicle-dev/walletconnect-test-wallet
[rc]:https://github.com/radicle-dev/radicle-contracts
[dev-setup]:./funding-dev-setup.svg "Radicle Funding Development Set up"
1 change: 1 addition & 0 deletions funding-dev-setup.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 12 additions & 1 deletion package.json
Expand Up @@ -65,8 +65,11 @@
},
"main": "./native/main.comp.js",
"devDependencies": {
"@ethersproject/cli": "^5.0.7",
"@rollup/plugin-inject": "^4.0.2",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.1",
"@rollup/plugin-node-resolve": "^10.0.0",
"@rollup/plugin-typescript": "^8.1.0",
"@tsconfig/svelte": "^1.0.10",
"@types/jest": "^26.0.19",
Expand All @@ -88,6 +91,7 @@
"eslint-plugin-no-only-tests": "^2.4.0",
"eslint-plugin-svelte3": "^3.0.0",
"eslint-svelte3-preprocess": "^0.0.4",
"ganache-cli": "^v6.12.0",
"husky": ">=4.3.6",
"jest": "^26.6.3",
"lint-staged": "^10.5.3",
Expand All @@ -113,12 +117,14 @@
"ts-node": "^9.1.1",
"tslib": "^2.0.3",
"typescript": "^4.1.3",
"util": "^0.12.3",
"wait-on": "^5.2.1"
},
"scripts": {
"start": "RADICLE_UPSTREAM_PROXY_PATH=../proxy/target/release/radicle-proxy yarn _private:start",
"start:dev": "RADICLE_UPSTREAM_PROXY_PATH=../proxy/target/debug/radicle-proxy yarn _private:start:dev",
"start:test": "RADICLE_UPSTREAM_PROXY_PATH=../proxy/target/release/radicle-proxy RADICLE_UPSTREAM_PROXY_ARGS=--test yarn _private:start",
"ethereum:start": "./scripts/ethereum-dev-node.sh",
"test": "TZ='UTC' yarn test:unit && TZ='UTC' yarn test:integration",
"test:integration": "TZ='UTC' run-p --race _private:proxy:start:test _private:test:integration",
"test:integration:debug": "TZ='UTC' run-p --race _private:rollup:watch _private:proxy:start:test:watch _private:test:integration:debug",
Expand Down Expand Up @@ -150,9 +156,14 @@
},
"dependencies": {
"@types/qs": "^6.9.5",
"@walletconnect/client": "^1.3.1",
"browserify": "^17.0.0",
"ethers": "^5.0.23",
"marked": "^1.2.7",
"mnemonist": "^0.38.1",
"pure-svg-code": "^1.0.6",
"radicle-contracts": "git+https://github.com/radicle-dev/radicle-contracts.git#5dd3138d8a731cff59835961deb7295b89520608",
"svelte-persistent-store": "^0.1.5",
"rollup-plugin-css-only": "^3.1.0",
"timeago.js": "^4.0.2",
"twemoji": "13.0.1",
Expand Down
14 changes: 14 additions & 0 deletions proxy/api/src/session/settings.rs
Expand Up @@ -9,6 +9,8 @@ pub struct Settings {
pub appearance: Appearance,
/// User-determined p2p parameters.
pub coco: CoCo,
#[serde(default)]
pub feature_flags: FeatureFlags,
}

/// Knobs for the look and feel.
Expand Down Expand Up @@ -70,3 +72,15 @@ impl Default for CoCo {
}
}
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct FeatureFlags {
/// Whether the funding feature is enabled or disabled.
pub funding: bool,
}

impl Default for FeatureFlags {
fn default() -> Self {
Self { funding: false }
}
}
46 changes: 46 additions & 0 deletions rollup.app.js
@@ -1,6 +1,9 @@
import commonjs from "@rollup/plugin-commonjs";
import livereload from "rollup-plugin-livereload";
import resolve from "@rollup/plugin-node-resolve";
import inject from "@rollup/plugin-inject";
import json from "@rollup/plugin-json";
import * as browserifyNodeBuiltins from "browserify/lib/builtins";
import svelte from "rollup-plugin-svelte";
import { terser } from "rollup-plugin-terser";
import typescript from "@rollup/plugin-typescript";
Expand All @@ -18,6 +21,7 @@ export default {
file: "public/bundle.js",
},
plugins: [
json(),
svelte({
compilerOptions: {
// enable run-time checks when not in production
Expand All @@ -30,12 +34,34 @@ export default {

resolve({
browser: true,
preferBuiltins: true,
dedupe: importee =>
importee === "svelte" || importee.startsWith("svelte/"),
}),

commonjs(),

inject({
modules: {
process: "_process",
Buffer: ["buffer", "Buffer"],
},
}),

{
name: "node-builtins",
resolveId(importee) {
if (importee === "util") {
// We need a more recent version than browserify provides
return { id: require.resolve("util/util.js") };
}
const builtinPath = browserifyNodeBuiltins[importee];
if (builtinPath) {
return { id: builtinPath };
}
},
},

typescript({
// See https://github.com/rollup/plugins/issues/272
noEmitOnError: production,
Expand All @@ -52,4 +78,24 @@ export default {
watch: {
clearScreen: false,
},

// Skip certain warnings originated by third-party libraries
onwarn: function (warning) {
if (
warning.code === "THIS_IS_UNDEFINED" &&
warning.id.includes("node_modules/@ethersproject/")
) {
return;
}

if (
warning.code === "CIRCULAR_DEPENDENCY" &&
warning.importer.includes("node_modules/readable-stream/")
) {
return;
}

// // Pass on any other warnings
console.warn(warning.message);
},
};
42 changes: 42 additions & 0 deletions scripts/deploy-dev-contracts.js
@@ -0,0 +1,42 @@
#!/usr/bin/env node

const { deployAll } = require("radicle-contracts");
const ethers = require("ethers");
const fs = require("fs");

main().catch(e => {
console.error(e);
process.exit(1);
});

async function main() {
const provider = new ethers.providers.JsonRpcProvider(
"http://localhost:8545"
);
const signer = provider.getSigner(0);
const txCount = await signer.getTransactionCount();
if (txCount !== 0) {
throw new Error(
"Deployer account has non-zero transaction count. You need to reset your chain"
);
}

console.log("\n### Deploying the Radicle Contracts...\n");
const contracts = await deployAll(signer);
console.log(`Rad token deployed at ${contracts.rad.address.toLowerCase()}`);
console.log(`ENS deployed at ${contracts.ens.address.toLowerCase()}`);
console.log(
`Eth Pool deployed at ${contracts.ethPool.address.toLowerCase()}`
);
console.log(
`Erc20 Pool deployed at ${contracts.erc20Pool.address.toLowerCase()}`
);
console.log("Done.\n");

const devEthAccount = fs
.readFileSync("sandbox/.local-eth-account", "utf-8")
.trim();

// Set the initial balance of the used erc20 token for the development account.
await (await contracts.rad.transfer(devEthAccount, 98765)).wait();
}
30 changes: 30 additions & 0 deletions scripts/ethereum-dev-node.sh
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
#
# Run a local ethereum node and deploy the latest contracts to it.
#

set -eumo pipefail

node_modules/.bin/ganache-cli \
--mnemonic "image napkin cruise dentist name plunge crisp muscle nest floor vessel blush" \
--defaultBalanceEther 1000 \
"$@" &

function stop_ganache() {
kill %1 2>/dev/null || true
fg %1 2>/dev/null || true
}

trap stop_ganache SIGINT EXIT SIGTERM

sleep 4

echo "Deploying the Radicle Dev Contracts..."
./scripts/deploy-dev-contracts.js;
echo "Done"

echo "Adding funds to your account..."
ethers --rpc http://localhost:8545 --account-rpc 0 --yes send $(< ./sandbox/.local-eth-account) 10
echo "Done"

fg %1
25 changes: 25 additions & 0 deletions ui/App.svelte
Expand Up @@ -2,6 +2,7 @@
import Router, { push, location } from "svelte-spa-router";
import * as hotkeys from "./src/hotkeys.ts";
import { isExperimental } from "./src/ipc";
import "./src/localPeer.ts";
import * as path from "./src/path.ts";
import * as remote from "./src/remote.ts";
Expand All @@ -18,6 +19,8 @@
import Hotkeys from "./Hotkeys.svelte";
import Theme from "./Theme.svelte";
import TransactionCenter from "./App/TransactionCenter.svelte";
import Blank from "./Screen/Blank.svelte";
import Bsod from "./Screen/Bsod.svelte";
import Onboarding from "./Screen/Onboarding.svelte";
Expand All @@ -27,10 +30,17 @@
import ModalNewProject from "./Modal/NewProject.svelte";
import ModalSearch from "./Modal/Search.svelte";
import ModalShortcuts from "./Modal/Shortcuts.svelte";
import ModalTransaction from "./Modal/Transaction.svelte";
import NotFound from "./Screen/NotFound.svelte";
import Profile from "./Screen/Profile.svelte";
import Project from "./Screen/Project.svelte";
import Settings from "./Screen/Settings.svelte";
import ModalLinkAddress from "./Modal/Funding/LinkAddress.svelte";
import ModalPoolOnboarding from "./Modal/Funding/Onboarding.svelte";
import ModalWalletQRCode from "./Modal/Wallet/QRCode.svelte";
import ModalTopUp from "./Modal/Funding/Pool/TopUp.svelte";
import ModalWithdraw from "./Modal/Funding/Pool/Withdraw.svelte";
import ModalCollect from "./Modal/Funding/Pool/Collect.svelte";
import UserProfile from "./Screen/UserProfile.svelte";
const routes = {
Expand All @@ -52,6 +62,13 @@
"/new-project": ModalNewProject,
"/search": ModalSearch,
"/shortcuts": ModalShortcuts,
"/wallet/qrcode": ModalWalletQRCode,
"/funding/link": ModalLinkAddress,
"/funding/pool/onboarding": ModalPoolOnboarding,
"/funding/pool/collect": ModalCollect,
"/funding/pool/withdraw": ModalWithdraw,
"/funding/pool/top-up": ModalTopUp,
"/transaction": ModalTransaction,
};
$: switch ($store.status) {
Expand Down Expand Up @@ -82,6 +99,10 @@
error.show($store.error);
break;
}
$: sessionIsUnsealed =
$store.status === remote.Status.Success &&
$store.data.status === Status.UnsealedSession;
</script>

<style>
Expand All @@ -101,6 +122,10 @@
<NotificationFaucet />
<Theme />

{#if isExperimental() && sessionIsUnsealed && $location !== path.designSystemGuide()}
<TransactionCenter />
{/if}

<Remote {store} context="session" disableErrorLogging={true}>
<Router {routes} />

Expand Down

0 comments on commit 5b4a327

Please sign in to comment.