Skip to content

Commit

Permalink
Add Snapshot Creation (#1216)
Browse files Browse the repository at this point in the history
* update snapshot proposal page

* create snpashot proposal workflow

* update UI

* utilize snapshot js

* add snapshot create proposal

* add sushi token for testing purpose

* correct wrong code

* complete creating snapshot proposal

* update manage community form
  • Loading branch information
fstar1129 authored and dillchen committed Jul 14, 2021
1 parent 08d024e commit 10434cd
Show file tree
Hide file tree
Showing 29 changed files with 806 additions and 83 deletions.
12 changes: 10 additions & 2 deletions client/scripts/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,13 @@ export async function selectNode(n?: NodeInfo, deferred = false): Promise<boolea
'./controllers/chain/ethereum/fei/adapter'
)).default;
newChain = new Fei(n, app);
} else if (n.chain.network === ChainNetwork.Sushi) {
const Sushi = (await import(
/* webpackMode: "lazy" */
/* webpackChunkName: "commonwealth-main" */
'./controllers/chain/ethereum/sushi/adapter'
)).default;
newChain = new Sushi(n, app);
} else {
throw new Error('Invalid chain');
}
Expand Down Expand Up @@ -626,8 +633,8 @@ $(() => {
'/:scope/discussions/:topic': importRoute('views/pages/discussions', { scoped: true, deferChain: true }),
'/:scope/search': importRoute('views/pages/search', { scoped: true, deferChain: true }),
'/:scope/members': importRoute('views/pages/members', { scoped: true, deferChain: true }),
'/:scope/snapshot-proposals': importRoute('views/pages/snapshot_proposals', { scoped: true, deferChain: true }),
'/:scope/snapshot-proposal/:identifier': importRoute('views/pages/view_snapshot_proposal/index', { scoped: true }),
'/:scope/snapshot-proposals/:snapshotId': importRoute('views/pages/snapshot_proposals', { scoped: true, deferChain: true }),
'/:scope/snapshot-proposal/:snapshotId/:identifier': importRoute('views/pages/view_snapshot_proposal', { scoped: true }),
'/:scope/chat': importRoute('views/pages/chat', { scoped: true, deferChain: true }),
'/:scope/referenda': importRoute('views/pages/referenda', { scoped: true }),
'/:scope/proposals': importRoute('views/pages/proposals', { scoped: true }),
Expand All @@ -639,6 +646,7 @@ $(() => {
'/:scope/delegate': importRoute('views/pages/delegate', { scoped: true, }),
'/:scope/login': importRoute('views/pages/login', { scoped: true, deferChain: true }),
'/:scope/new/thread': importRoute('views/pages/new_thread', { scoped: true, deferChain: true }),
'/:scope/new/snapshot-proposal/:snapshotId': importRoute('views/pages/new_snapshot_proposal', { scoped: true, deferChain: true }),
'/:scope/new/proposal/:type': importRoute('views/pages/new_proposal/index', { scoped: true }),
'/:scope/admin': importRoute('views/pages/admin', { scoped: true }),
'/:scope/spec_settings': importRoute('views/pages/spec_settings', { scoped: true, deferChain: true }),
Expand Down
63 changes: 63 additions & 0 deletions client/scripts/controllers/chain/ethereum/sushi/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { EthereumCoin } from 'adapters/chain/ethereum/types';

import { Erc20Factory } from 'Erc20Factory';
import EthereumAccount from 'controllers/chain/ethereum/account';
import EthereumAccounts from 'controllers/chain/ethereum/accounts';
import { ChainBase, IChainAdapter, NodeInfo } from 'models';

import ChainEntityController from 'controllers/server/chain_entities';
import { IApp } from 'state';

import EthereumTokenChain from './chain';
import SushiApi from './api';

export default class Sushi extends IChainAdapter<EthereumCoin, EthereumAccount> {
public readonly base = ChainBase.Ethereum;
// TODO: ensure this chainnetwork -> chainclass
public readonly class;
public readonly contractAddress: string;
public readonly isToken = true;

public chain: EthereumTokenChain;
public accounts: EthereumAccounts;
public hasToken: boolean = false;

constructor(meta: NodeInfo, app: IApp) {
super(meta, app);
this.chain = new EthereumTokenChain(this.app);
this.accounts = new EthereumAccounts(this.app);
this.class = meta.chain.network;
this.contractAddress = meta.address;
}

public async initApi() {
await this.chain.resetApi(this.meta);
await this.chain.initMetadata();
await this.accounts.init(this.chain);
const api = new SushiApi(Erc20Factory.connect, this.meta.address, this.chain.api.currentProvider as any);
await api.init();
this.chain.contractApi = api;
await super.initApi();
}

public async initData() {
await this.chain.initEventLoop();
await super.initData();
await this.activeAddressHasToken(this.app.user?.activeAccount?.address);
}

public async deinit() {
await super.deinit();
this.accounts.deinit();
this.chain.deinitMetadata();
this.chain.deinitEventLoop();
this.chain.deinitApi();
}

public async activeAddressHasToken(activeAddress?: string) {
if (!activeAddress) return false;
const account = this.accounts.get(activeAddress);
const balance = await account.tokenBalance(this.contractAddress);
this.hasToken = balance && !balance.isZero();
}
}
5 changes: 5 additions & 0 deletions client/scripts/controllers/chain/ethereum/sushi/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Erc20 } from 'Erc20';

import ContractApi from 'controllers/chain/ethereum/contractApi';

export default class SushiApi extends ContractApi<Erc20> { }
8 changes: 8 additions & 0 deletions client/scripts/controllers/chain/ethereum/sushi/chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import EthereumChain from '../chain';
import ContractApi from './api';

// Thin wrapper over EthereumChain to guarantee the `init()` implementation
// on the Governance module works as expected.
export default class SushiChain extends EthereumChain {
public contractApi: ContractApi;
}
3 changes: 2 additions & 1 deletion client/scripts/controllers/server/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ class SnapshotController {
public get proposalStore() { return this._proposalStore; }

public async fetchSnapshotProposals(snapshot: string) {
const response = await $.get(`https://hub.snapshot.page/api/${snapshot}/proposals`);
const hubUrl = process.env.SNAPSHOT_APP_HUB_URL || 'https://testnet.snapshot.org';
const response = await $.get(`${hubUrl}/api/${snapshot}/proposals`);
// if (response.status !== 'Success') {
// throw new Error(`Cannot fetch snapshot proposals: ${response.status}`);
// }
Expand Down
10 changes: 10 additions & 0 deletions client/scripts/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,16 @@ export const loadScript = (scriptURI) => {
});
};

export function formatSpace(key, space) {
space = {
key,
...space,
members: space.members || [],
filters: space.filters || {}
};
if (!space.filters.minScore) space.filters.minScore = 0;
return space;
}

export const removeOrAddClasslistToAllElements = (
cardList: ICardListItem[],
Expand Down
6 changes: 6 additions & 0 deletions client/scripts/helpers/snapshot_client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Client from '@snapshot-labs/snapshot.js/src/client';

const hubUrl = process.env.SNAPSHOT_APP_HUB_URL || 'https://testnet.snapshot.org';
const snapshotClient = new Client(hubUrl);

export default snapshotClient;
4 changes: 3 additions & 1 deletion client/scripts/models/ChainInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class ChainInfo {
// TODO: change to accept an object
public async updateChainData({
name, description, website, discord, element, telegram,
github, stagesEnabled, additionalStages, customDomain
github, stagesEnabled, additionalStages, customDomain, snapshot
}) {
// TODO: Change to PUT /chain
const r = await $.post(`${app.serverUrl()}/updateChain`, {
Expand All @@ -190,6 +190,7 @@ class ChainInfo {
'stagesEnabled': stagesEnabled,
'additionalStages': additionalStages,
'customDomain': customDomain,
'snapshot': snapshot,
'jwt': app.user.jwt,
});
const updatedChain: ChainInfo = r.result;
Expand All @@ -203,6 +204,7 @@ class ChainInfo {
this.stagesEnabled = updatedChain.stagesEnabled;
this.additionalStages = updatedChain.additionalStages;
this.customDomain = updatedChain.customDomain;
this.snapshot = updatedChain.snapshot;
}

public addFeaturedTopic(topic: string) {
Expand Down
7 changes: 7 additions & 0 deletions client/scripts/models/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export enum ChainNetwork {
CosmosHub = 'cosmos-hub',
Gaia13k = 'gaia-13k',
Yearn = 'yearn',
Sushi = 'sushi',
Fei = 'fei'
}

Expand Down Expand Up @@ -126,6 +127,12 @@ export enum ChainClass {
Commonwealth = 'commonwealth',
Yearn = 'yearn',
Fei = 'fei',
<<<<<<< HEAD
=======
Sushi = 'sushi',
Crust = 'crust',
ERC20 = 'erc20',
>>>>>>> Add Snapshot Creation (#1216)
}

>>>>>>> Add yearn and fei migrations and controllers (#1156)
Expand Down
15 changes: 14 additions & 1 deletion client/scripts/views/components/sidebar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ export const OnchainNavigationModule: m.Component<{}, {}> = {
const showCommonwealthMenuOptions = app.chain?.network === ChainNetwork.Commonwealth;

const showMarlinOptions = app.user.activeAccount && app.chain?.network === ChainNetwork.Marlin;
const showSubmitSnapshotProposalOptions = app.user.activeAccount && app.chain?.meta.chain.snapshot &&
(app.chain?.network === ChainNetwork.Yearn ||
app.chain?.network === ChainNetwork.Fei ||
app.chain?.network === ChainNetwork.Sushi);

const onSnapshotProposal = (p) => p.startsWith(`/${app.activeId()}/snapshot-proposals`);
const onProposalPage = (p) => (
Expand Down Expand Up @@ -388,9 +392,18 @@ export const OnchainNavigationModule: m.Component<{}, {}> = {
label: 'Snapshot Proposals',
onclick: (e) => {
e.preventDefault();
m.route.set(`/${app.activeId()}/snapshot-proposals`);
m.route.set(`/${app.activeChainId()}/snapshot-proposals/${app.chain.meta.chain.snapshot}`);
},
}),
showSubmitSnapshotProposalOptions && m(Button, {
fluid: true,
rounded: true,
onclick: (e) => {
e.preventDefault();
m.route.set(`/${app.activeChainId()}/new/snapshot-proposal/${app.chain.meta.chain.snapshot}`);
},
label: 'Submit a Proposal',
}),
showCommonwealthMenuOptions && m(Button, {
fluid: true,
rounded: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface IChainMetadataManagementState {
customDomain: string;
network: ChainNetwork;
symbol: string;
snapshot: string;
}

const ChainMetadataManagementTable: m.Component<IChainOrCommMetadataManagementAttrs, IChainMetadataManagementState> = {
Expand All @@ -41,6 +42,7 @@ const ChainMetadataManagementTable: m.Component<IChainOrCommMetadataManagementAt
vnode.state.iconUrl = vnode.attrs.chain.iconUrl;
vnode.state.network = vnode.attrs.chain.network;
vnode.state.symbol = vnode.attrs.chain.symbol;
vnode.state.snapshot = vnode.attrs.chain.snapshot;
},
view: (vnode) => {
return m('.ChainMetadataManagementTable', [
Expand Down Expand Up @@ -111,6 +113,12 @@ const ChainMetadataManagementTable: m.Component<IChainOrCommMetadataManagementAt
placeholder: 'gov.edgewa.re',
onChangeHandler: (v) => { vnode.state.customDomain = v; },
}),
m(InputPropertyRow, {
title: 'Snapshot',
defaultValue: vnode.state.snapshot,
placeholder: vnode.state.network,
onChangeHandler: (v) => { vnode.state.snapshot = v; },
}),
m('tr', [
m('td', 'Admins'),
m('td', [ m(ManageRolesRow, {
Expand Down Expand Up @@ -142,7 +150,8 @@ const ChainMetadataManagementTable: m.Component<IChainOrCommMetadataManagementAt
github,
stagesEnabled,
additionalStages,
customDomain
customDomain,
snapshot
} = vnode.state;
try {
await vnode.attrs.chain.updateChainData({
Expand All @@ -155,7 +164,8 @@ const ChainMetadataManagementTable: m.Component<IChainOrCommMetadataManagementAt
github,
stagesEnabled,
additionalStages,
customDomain
customDomain,
snapshot
});
$(e.target).trigger('modalexit');
} catch (err) {
Expand Down
23 changes: 23 additions & 0 deletions client/scripts/views/pages/new_snapshot_proposal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'pages/new_proposal_page.scss';

import m from 'mithril';

import Sublayout from 'views/sublayout';
import NewProposalForm from 'views/pages/new_snapshot_proposal/new_proposal_form';

const NewSnapshotProposalPage: m.Component<{snapshotId: string}> = {
view: (vnode) => {

return m(Sublayout, {
class: 'NewProposalPage',
title: `New Snapshot Proposal`,
showNewProposalButton: true,
}, [
m('.forum-container', [
m(NewProposalForm, {snapshotId: vnode.attrs.snapshotId}),
])
]);
}
};

export default NewSnapshotProposalPage;
Loading

0 comments on commit 10434cd

Please sign in to comment.