types#ChainNetwork TypeScript Examples

The following examples show how to use types#ChainNetwork. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: login_with_wallet_dropdown.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
// Returns a default chain for a chainbase
export function baseToNetwork(n: ChainBase): ChainNetwork {
  switch (n) {
    case ChainBase.CosmosSDK:
      return ChainNetwork.Osmosis;
    case ChainBase.Substrate:
      return ChainNetwork.Edgeware;
    case ChainBase.Ethereum:
      return ChainNetwork.Ethereum;
    case ChainBase.NEAR:
      return ChainNetwork.NEAR;
    case ChainBase.Solana:
      return ChainNetwork.Solana;
    default:
      return null;
  }
}
Example #2
Source File: eth_dao_form.tsx    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
private state: CreateEthDaoState = {
    error: '',
    loaded: false,
    loading: false,
    saving: false,
    status: '',
    form: {
      address: '',
      chainString: 'Ethereum Mainnet',
      ethChainId: 1,
      id: '',
      name: '',
      network: ChainNetwork.Compound,
      nodeUrl: '',
      symbol: '',
      tokenName: 'token',
      ...initChainForm(),
    },
  };
Example #3
Source File: delegate.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
getDelegate = async (
  vnode: m.Vnode<Record<string, never>, IDelegateFormState>
) => {
  if (app.chain.network === ChainNetwork.Compound) {
    vnode.state.currentDelegate = await (
      app.chain as Compound
    ).chain.getDelegate();
  } else if (app.chain.network === ChainNetwork.Aave) {
    // TODO: switch on delegation type
    vnode.state.currentDelegate = await (app.chain as Aave).chain.getDelegate(
      app.user.activeAccount.address,
      'voting'
    );
  }
  m.redraw();
}
Example #4
Source File: delegate.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
setDelegate = async (
  vnode: m.Vnode<Record<string, never>, IDelegateFormState>
) => {
  if (app.chain.apiInitialized) {
    let delegationPromise: Promise<void>;
    if (app.chain.network === ChainNetwork.Compound) {
      delegationPromise = (app.chain as Compound).chain.setDelegate(
        vnode.state.form.address
      );
    } else if (app.chain.network === ChainNetwork.Aave) {
      delegationPromise = (app.chain as Aave).chain.setDelegate(
        vnode.state.form.address
      );
    }
    if (delegationPromise) {
      try {
        await delegationPromise;
        notifySuccess(
          `Sent transaction to delegate to ${vnode.state.form.address}`
        );
        getDelegate(vnode);
      } catch (err) {
        notifyError(`${err.message}`);
      }
    }
  }
}
Example #5
Source File: community_cards.tsx    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
oninit() {
    const chainsAndCategories = app.config.chainCategories;
    const categoryTypes = app.config.chainCategoryTypes;

    this.filterMap = {};
    this.chainCategories = categoryTypes.map(
      (category) => category.category_name
    );
    this.chainBases = Object.keys(ChainBase);
    this.chainNetworks = Object.keys(ChainNetwork).filter(
      (val) => val === 'ERC20'
    ); // We only are allowing ERC20 for now

    // Load Filter Map
    for (const cat of this.chainCategories) {
      this.filterMap[cat] = false;
    }
    for (const base of this.chainBases) {
      this.filterMap[base] = false;
    }
    for (const network of this.chainNetworks) {
      this.filterMap[network] = false;
    }

    // Handle mapping provided by ChainCategories table
    this.chainToCategoriesMap = buildChainToCategoriesMap(
      categoryTypes,
      chainsAndCategories
    );
  }
Example #6
Source File: delegate.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
DelegatePage: m.Component = {
  view: () => {
    if (!app.chain || !app.chain.loaded) {
      // chain load failed
      if (app.chain && app.chain.failed) {
        return m(PageNotFound, {
          title: 'Wrong Ethereum Provider Network!',
          message: 'Change Metamask to point to Ethereum Mainnet',
        });
      }
      // wrong chain loaded
      if (
        app.chain &&
        app.chain.loaded &&
        app.chain.network !== ChainNetwork.Compound &&
        app.chain.network !== ChainNetwork.Aave
      ) {
        return m(PageNotFound, {
          title: 'Delegate Page',
          message: 'Delegate page for Marlin and Aave users only!',
        });
      }
      // chain loading
      return m(PageLoading, {
        message: 'Connecting to chain',
        title: 'Delegate',
      });
    }

    return m(
      Sublayout,
      {
        class: 'DelegatePage',
        title: 'Delegate',
      },
      [m('.forum-container', [m(DelegateForm, {})])]
    );
  },
}
Example #7
Source File: identifiers.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
chainEntityTypeToProposalName = (t: IChainEntityKind): string => {
  if (t === SubstrateTypes.EntityKind.TreasuryProposal)
    return 'Treasury Proposal';
  else if (t === SubstrateTypes.EntityKind.DemocracyReferendum)
    return 'Referendum';
  else if (t === SubstrateTypes.EntityKind.DemocracyProposal)
    return 'Democracy Proposal';
  else if (t === SubstrateTypes.EntityKind.CollectiveProposal)
    return 'Council Motion';
  else if (t === SubstrateTypes.EntityKind.TreasuryBounty)
    return 'Bounty Proposal';
  else if (t === SubstrateTypes.EntityKind.TipProposal) return 'Treasury Tip';
  else if (t === 'proposal') {
    if (app.chain.network === ChainNetwork.Sputnik) {
      return 'Sputnik Proposal';
    }
    if (app.chain.network === ChainNetwork.Moloch) {
      return 'Moloch Proposal';
    }
    if (app.chain.network === ChainNetwork.Compound) {
      return 'Onchain Proposal';
    }
    if (app.chain.network === ChainNetwork.Aave) {
      return 'Onchain Proposal';
    }
  }
}
Example #8
Source File: chain.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
public async sendTx(account: CosmosAccount, tx: EncodeObject): Promise<readonly Event[]> {
    // TODO: error handling
    // TODO: support multiple wallets
    if (this._app.chain.network === ChainNetwork.Terra) {
      throw new Error('Tx not yet supported on Terra');
    }
    const wallet = this.app.wallets.getByName(WalletId.Keplr) as KeplrWebWalletController;
    if (!wallet) throw new Error('Keplr wallet not found');
    if (!wallet.enabled) {
      await wallet.enable();
    }
    const client = await SigningStargateClient.connectWithSigner(this._app.chain.meta.node.url, wallet.offlineSigner);

    // these parameters will be overridden by the wallet
    // TODO: can it be simulated?
    const DEFAULT_FEE: StdFee = {
      gas: '180000',
      amount: [{ amount: (2.5e-8).toFixed(9), denom: this.denom }]
    };
    const DEFAULT_MEMO = '';

    // send the transaction using keplr-supported signing client
    try {
      const result = await client.signAndBroadcast(account.address, [ tx ], DEFAULT_FEE, DEFAULT_MEMO);
      console.log(result);
      if (isBroadcastTxFailure(result)) {
        throw new Error('TX execution failed.');
      } else if (isBroadcastTxSuccess(result)) {
        const txHash = result.transactionHash;
        const txResult = await this._tmClient.tx({ hash: Buffer.from(txHash, 'hex') });
        return txResult.result.events;
      } else {
        throw new Error('Unknown broadcast result');
      }
    } catch (err) {
      console.log(err.message);
      throw err;
    }
  }
Example #9
Source File: shared.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
// TODO: refactor fee computation into a more standard form that can be used throughout
  //   and shown at TX creation time
  public async canPayFee(
    sender: SubstrateAccount,
    txFunc: (api: ApiPromise) => SubmittableExtrinsic<'promise'>,
    additionalDeposit?: SubstrateCoin,
  ): Promise<boolean> {
    const senderBalance = await sender.freeBalance;
    const netBalance = additionalDeposit ? senderBalance.sub(additionalDeposit) : senderBalance;
    let fees: SubstrateCoin;
    if (sender.chain.network === ChainNetwork.Edgeware) {
      // XXX: we cannot compute tx fees on edgeware yet, so we are forced to assume no fees
      //   besides explicit additional fees
      fees = additionalDeposit || this.coins(0);
    } else {
      fees = await this.computeFees(sender.address, txFunc);
    }
    console.log(`sender free balance: ${senderBalance.format(true)}, tx fees: ${fees.format(true)}, `
      + `additional deposit: ${additionalDeposit ? additionalDeposit.format(true) : 'N/A'}`);
    return netBalance.gte(fees);
  }
Example #10
Source File: identifiers.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
chainEntityTypeToProposalSlug = (
  t: IChainEntityKind
): ProposalType => {
  if (t === SubstrateTypes.EntityKind.TreasuryProposal)
    return ProposalType.SubstrateTreasuryProposal;
  else if (t === SubstrateTypes.EntityKind.DemocracyReferendum)
    return ProposalType.SubstrateDemocracyReferendum;
  else if (t === SubstrateTypes.EntityKind.DemocracyProposal)
    return ProposalType.SubstrateDemocracyProposal;
  else if (t === SubstrateTypes.EntityKind.CollectiveProposal)
    return ProposalType.SubstrateCollectiveProposal;
  else if (t === SubstrateTypes.EntityKind.TreasuryBounty)
    return ProposalType.SubstrateBountyProposal;
  else if (t === SubstrateTypes.EntityKind.TipProposal)
    return ProposalType.SubstrateTreasuryTip;
  else if (t === 'proposal') {
    if (app.chain.network === ChainNetwork.Sputnik) {
      return ProposalType.SputnikProposal;
    }
    if (app.chain.network === ChainNetwork.Moloch) {
      return ProposalType.MolochProposal;
    }
    if (app.chain.network === ChainNetwork.Compound) {
      return ProposalType.CompoundProposal;
    }
    if (app.chain.network === ChainNetwork.Aave) {
      return ProposalType.AaveProposal;
    }
  }
}
Example #11
Source File: edit_topic_thresholds_modal.tsx    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
view(vnode) {
    const { topic } = vnode.attrs;

    if (typeof this.newTokenThresholdInWei !== 'string') {
      this.newTokenThresholdInWei = topic.tokenThreshold?.toString() || '0';
    }

    const decimals = app.chain?.meta?.decimals
      ? app.chain.meta.decimals
      : app.chain.network === ChainNetwork.ERC721
      ? 0
      : 18;

    return (
      <div class="EditTopicThresholdsRow">
        <div class="topic-name">{topic.name}</div>
        <div class="input-and-button-row">
          <TokenDecimalInput
            decimals={decimals}
            defaultValueInWei={topic.tokenThreshold.toString()}
            onInputChange={(newValue: string) => {
              this.newTokenThresholdInWei = newValue;
            }}
          />
          <Button
            label="Update"
            intent="primary"
            rounded={true}
            disabled={!this.newTokenThresholdInWei}
            onclick={async (e) => {
              e.preventDefault();
              try {
                const status = await app.topics.setTopicThreshold(
                  topic,
                  this.newTokenThresholdInWei
                );
                if (status === 'Success') {
                  notifySuccess('Successfully updated threshold value');
                } else {
                  notifyError('Could not update threshold value');
                }
              } catch (err) {
                notifyError(`Invalid threshold value: ${err.message}`);
              }
            }}
          />
        </div>
      </div>
    );
  }
Example #12
Source File: ronin_web_wallet.ts    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
public readonly specificChains = [ ChainNetwork.AxieInfinity ];
Example #13
Source File: chain_entities.ts    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
export function chainToEventNetwork(c: ChainInfo): SupportedNetwork {
  if (c.base === ChainBase.Substrate) return SupportedNetwork.Substrate;
  if (c.network === ChainNetwork.ERC20) return SupportedNetwork.ERC20;
  if (c.network === ChainNetwork.Compound) return SupportedNetwork.Compound;
  if (c.network === ChainNetwork.Aave) return SupportedNetwork.Aave;
  if (c.network === ChainNetwork.Moloch) return SupportedNetwork.Moloch;
  throw new Error(`Invalid event chain: ${c.id}, on network ${c.network}, base ${c.base}`);
}
Example #14
Source File: delegate.ts    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
DelegateForm: m.Component<Record<string, never>, IDelegateFormState> = {
  oninit: (vnode) => {
    vnode.state.form = {
      address: '',
      amount: null,
    };
    vnode.state.loading = false;
    getDelegate(vnode);
  },
  view: (vnode) => {
    const { form, loading } = vnode.state;
    const hasValue = app.chain.network === ChainNetwork.Compound;
    return [
      m(DelegateStats, {
        currentDelegate: vnode.state.currentDelegate,
      }),
      m(Form, { class: 'DelegateForm' }, [
        m(Grid, [
          m(Col, [
            m('h2', 'Set up your delegation:'),
            m(FormGroup, [
              m(FormLabel, 'Your delegate:'),
              m(Input, {
                name: 'address',
                placeholder: 'Paste address you want to delegate to',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.form.address = result;
                  m.redraw();
                },
              }),
              hasValue && m(FormLabel, 'Amount to delegate:'),
              hasValue &&
                m(Input, {
                  name: 'amount',
                  placeholder: '10000',
                  defaultValue: '',
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.form.amount = result;
                    m.redraw();
                  },
                }),
            ]),
            m(FormGroup, [
              m(Button, {
                disabled: form.address === '' || loading,
                intent: 'primary',
                rounded: true,
                label: 'Delegate!',
                onclick: async (e) => {
                  e.preventDefault();
                  vnode.state.loading = true;
                  await setDelegate(vnode);
                  vnode.state.loading = false;
                  m.redraw();
                },
                type: 'submit',
              }),
            ]),
          ]),
        ]),
      ]),
    ];
  },
}
Example #15
Source File: identifiers.ts    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
chainToProposalSlug = (c: ChainInfo): ProposalType => {
  if (c.base === ChainBase.CosmosSDK) return ProposalType.CosmosProposal;
  if (c.network === ChainNetwork.Sputnik) return ProposalType.SputnikProposal;
  if (c.network === ChainNetwork.Moloch) return ProposalType.MolochProposal;
  if (c.network === ChainNetwork.Compound) return ProposalType.CompoundProposal;
  if (c.network === ChainNetwork.Aave) return ProposalType.AaveProposal;
  throw new Error(`Cannot determine proposal slug from chain ${c.id}.`);
}
Example #16
Source File: chain_metadata_rows.tsx    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
network: ChainNetwork;
Example #17
Source File: login_selector.tsx    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
CHAINNETWORK_SHORT = {
  [ChainNetwork.AxieInfinity]: 'Ronin',
  [ChainNetwork.Terra]: 'Terra',
}
Example #18
Source File: ChainInfo.ts    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
public readonly network: ChainNetwork;
Example #19
Source File: identifiers.ts    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
proposalSlugToClass = () => {
  const mmap = new Map<
    string,
    ProposalModule<any, any, any> | ThreadsController
  >([[ProposalType.OffchainThread, app.threads]]);
  if (app.chain.base === ChainBase.Substrate) {
    mmap.set(
      ProposalType.SubstrateDemocracyReferendum,
      (app.chain as any).democracy
    );
    mmap.set(
      ProposalType.SubstrateDemocracyProposal,
      (app.chain as any).democracyProposals
    );
    mmap.set(
      ProposalType.SubstrateCollectiveProposal,
      (app.chain as any).council
    );
    mmap.set(
      ProposalType.PhragmenCandidacy,
      (app.chain as any).phragmenElections
    );
    mmap.set(
      ProposalType.SubstrateTreasuryProposal,
      (app.chain as any).treasury
    );
    mmap.set(ProposalType.SubstrateBountyProposal, (app.chain as any).bounties);
    mmap.set(ProposalType.SubstrateTreasuryTip, (app.chain as any).tips);
  } else if (app.chain.base === ChainBase.CosmosSDK) {
    mmap.set(ProposalType.CosmosProposal, (app.chain as any).governance);
  }
  if (
    app.chain.network === ChainNetwork.Kusama ||
    app.chain.network === ChainNetwork.Polkadot
  ) {
    mmap.set(
      ProposalType.SubstrateTechnicalCommitteeMotion,
      (app.chain as any).technicalCommittee
    );
  }
  if (app.chain.network === ChainNetwork.Moloch) {
    mmap.set(ProposalType.MolochProposal, (app.chain as any).governance);
  }
  if (app.chain.network === ChainNetwork.Compound) {
    mmap.set(ProposalType.CompoundProposal, (app.chain as any).governance);
  }
  if (app.chain.network === ChainNetwork.Aave) {
    mmap.set(ProposalType.AaveProposal, (app.chain as any).governance);
  }
  if (app.chain.network === ChainNetwork.Sputnik) {
    mmap.set(ProposalType.SputnikProposal, (app.chain as any).dao);
  }
  return mmap;
}
Example #20
Source File: new_proposal_form.ts    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
NewProposalForm = {
  form: {},
  oncreate: (vnode) => {
    vnode.state.toggleValue = 'proposal';
  },
  oninit: (vnode) => {
    vnode.state.aaveTabCount = 1;
    vnode.state.activeAaveTabIndex = 0;
    vnode.state.aaveProposalState = [
      {
        target: null,
        value: null,
        calldata: null,
        signature: null,
        withDelegateCall: false,
      },
    ];
    vnode.state.cosmosProposalType = SupportedCosmosProposalTypes.Text;
    vnode.state.sputnikProposalType =
      SupportedSputnikProposalTypes.AddMemberToRole;
  },
  view: (vnode) => {
    const callback = vnode.attrs.callback;
    const author = app.user.activeAccount;
    const proposalTypeEnum = vnode.attrs.typeEnum;

    if (!author) return m('div', 'Must be logged in');
    if (!callback) return m('div', 'Must have callback');
    if (app.chain?.network === ChainNetwork.Plasm)
      return m('div', 'Unsupported network');

    let hasCouncilMotionChooser: boolean;
    let hasAction: boolean;
    let hasToggle: boolean;
    let hasPreimageInput: boolean;
    let hasTitleAndDescription: boolean;
    let hasBountyTitle: boolean;
    let hasTopics: boolean;
    let hasBeneficiary: boolean;
    let hasAmount: boolean;
    let hasPhragmenInfo: boolean;
    let hasDepositChooser: boolean;
    // bounty proposal
    let hasBountyValue: boolean;
    // tip
    let hasTipsFields: boolean;
    // council motion
    let hasVotingPeriodAndDelaySelector: boolean;
    let hasReferendumSelector: boolean;
    let hasExternalProposalSelector: boolean;
    let hasTreasuryProposalSelector: boolean;
    let hasThreshold: boolean;
    // moloch proposal
    let hasMolochFields: boolean;
    // compound proposal
    let hasCompoundFields: boolean;
    // aave proposal
    let hasAaveFields: boolean;
    // sputnik proposal
    let hasSputnikFields: boolean;
    let hasCosmosFields: boolean;
    // data loaded
    let dataLoaded = true;

    if (proposalTypeEnum === ProposalType.SubstrateDemocracyProposal) {
      hasAction = true;
      hasToggle = true;
      hasDepositChooser = vnode.state.toggleValue === 'proposal';
      if (hasDepositChooser) {
        dataLoaded = !!(app.chain as Substrate).democracyProposals?.initialized;
      }
    } else if (proposalTypeEnum === ProposalType.SubstrateCollectiveProposal) {
      hasCouncilMotionChooser = true;
      hasAction =
        vnode.state.councilMotionType === 'createExternalProposal' ||
        vnode.state.councilMotionType === 'createExternalProposalMajority';
      hasVotingPeriodAndDelaySelector =
        vnode.state.councilMotionType === 'createFastTrack' ||
        vnode.state.councilMotionType === 'createExternalProposalDefault';
      hasReferendumSelector =
        vnode.state.councilMotionType === 'createEmergencyCancellation';
      hasExternalProposalSelector =
        vnode.state.councilMotionType === 'vetoNextExternal' ||
        vnode.state.councilMotionType === 'createFastTrack' ||
        vnode.state.councilMotionType === 'createExternalProposalDefault';
      hasTreasuryProposalSelector =
        vnode.state.councilMotionType === 'createTreasuryApprovalMotion' ||
        vnode.state.councilMotionType === 'createTreasuryRejectionMotion';
      hasThreshold = vnode.state.councilMotionType !== 'vetoNextExternal';
      if (hasExternalProposalSelector)
        dataLoaded = !!(app.chain as Substrate).democracyProposals?.initialized;
    } else if (proposalTypeEnum === ProposalType.OffchainThread) {
      hasTitleAndDescription = true;
      hasTopics = true;
    } else if (proposalTypeEnum === ProposalType.SubstrateTreasuryProposal) {
      hasBeneficiary = true;
      hasAmount = true;
      const treasury = (app.chain as Substrate).treasury;
      dataLoaded = !!treasury.initialized;
    } else if (proposalTypeEnum === ProposalType.SubstrateBountyProposal) {
      hasBountyTitle = true;
      hasBountyValue = true;
      const bountyTreasury = (app.chain as Substrate).bounties;
      dataLoaded = !!bountyTreasury.initialized;
    } else if (proposalTypeEnum === ProposalType.SubstrateTreasuryTip) {
      hasTipsFields = true;
      // TODO: this is only true if the proposer is doing reportAwesome()
      //   we need special code for newTip().
      const tips = (app.chain as Substrate).tips;
      dataLoaded = !!tips.initialized;
    } else if (proposalTypeEnum === ProposalType.PhragmenCandidacy) {
      hasPhragmenInfo = true;
      const elections = (app.chain as Substrate).phragmenElections;
      dataLoaded = !!elections.initialized;
    } else if (proposalTypeEnum === ProposalType.CosmosProposal) {
      hasCosmosFields = true;
      dataLoaded = !!(app.chain as Cosmos).governance.initialized;
    } else if (proposalTypeEnum === ProposalType.MolochProposal) {
      hasMolochFields = true;
    } else if (proposalTypeEnum === ProposalType.CompoundProposal) {
      hasCompoundFields = true;
    } else if (proposalTypeEnum === ProposalType.AaveProposal) {
      hasAaveFields = true;
    } else if (proposalTypeEnum === ProposalType.SputnikProposal) {
      hasSputnikFields = true;
    } else {
      return m('.NewProposalForm', 'Invalid proposal type');
    }

    if (
      hasAction &&
      !(app.user.activeAccount as SubstrateAccount).isCouncillor
    ) {
      dataLoaded = false;
    }

    const createNewProposal = () => {
      const done = (result) => {
        vnode.state.error = '';
        callback(result);
        return result;
      };
      let createFunc: (...args) => ITXModalData | Promise<ITXModalData> = (
        a
      ) => {
        return (
          proposalSlugToClass().get(proposalTypeEnum) as ProposalModule<
            any,
            any,
            any
          >
        ).createTx(...a);
      };
      let args = [];
      if (proposalTypeEnum === ProposalType.OffchainThread) {
        app.threads
          .create(
            author.address,
            OffchainThreadKind.Forum,
            OffchainThreadStage.Discussion,
            app.activeChainId(),
            vnode.state.form.title,
            vnode.state.form.topicName,
            vnode.state.form.topicId,
            vnode.state.form.description
          )
          .then(done)
          .then(() => {
            m.redraw();
          })
          .catch((err) => {
            console.error(err);
          });
        return;
      } else if (proposalTypeEnum === ProposalType.SubstrateDemocracyProposal) {
        const deposit = vnode.state.deposit
          ? app.chain.chain.coins(vnode.state.deposit, true)
          : (app.chain as Substrate).democracyProposals.minimumDeposit;

        if (!EdgewareFunctionPicker.getMethod()) {
          notifyError('Missing arguments');
          return;
        } else if (vnode.state.toggleValue === 'proposal') {
          const proposalHash = blake2AsHex(
            EdgewareFunctionPicker.getMethod().toHex()
          );
          args = [
            author,
            EdgewareFunctionPicker.getMethod(),
            proposalHash,
            deposit,
          ];
          createFunc = ([au, mt, pr, dep]) =>
            (app.chain as Substrate).democracyProposals.createTx(
              au,
              mt,
              pr,
              dep
            );
        } else if (vnode.state.toggleValue === 'preimage') {
          vnode.attrs.onChangeSlugEnum('democracypreimage');
          const encodedProposal = EdgewareFunctionPicker.getMethod().toHex();
          args = [author, EdgewareFunctionPicker.getMethod(), encodedProposal];
          createFunc = ([au, mt, pr]) =>
            (app.chain as Substrate).democracyProposals.notePreimage(
              au,
              mt,
              pr
            );
        } else if (vnode.state.toggleValue === 'imminent') {
          vnode.attrs.onChangeSlugEnum('democracyimminent');
          const encodedProposal = EdgewareFunctionPicker.getMethod().toHex();
          args = [author, EdgewareFunctionPicker.getMethod(), encodedProposal];
          createFunc = ([au, mt, pr]) =>
            (app.chain as Substrate).democracyProposals.noteImminentPreimage(
              au,
              mt,
              pr
            );
        } else {
          throw new Error('Invalid toggle state');
        }
      } else if (
        proposalTypeEnum === ProposalType.SubstrateCollectiveProposal &&
        vnode.state.councilMotionType === 'vetoNextExternal'
      ) {
        args = [author, vnode.state.nextExternalProposalHash];
        createFunc = ([a, h]) =>
          (app.chain as Substrate).council.vetoNextExternal(a, h);
      } else if (
        proposalTypeEnum === ProposalType.SubstrateCollectiveProposal
      ) {
        if (!vnode.state.threshold) throw new Error('Invalid threshold');
        const threshold = vnode.state.threshold;
        if (vnode.state.councilMotionType === 'createExternalProposal') {
          args = [
            author,
            threshold,
            EdgewareFunctionPicker.getMethod(),
            EdgewareFunctionPicker.getMethod().encodedLength,
          ];
          createFunc = ([a, t, mt, l]) =>
            (app.chain as Substrate).council.createExternalProposal(
              a,
              t,
              mt,
              l
            );
        } else if (
          vnode.state.councilMotionType === 'createExternalProposalMajority'
        ) {
          args = [
            author,
            threshold,
            EdgewareFunctionPicker.getMethod(),
            EdgewareFunctionPicker.getMethod().encodedLength,
          ];
          createFunc = ([a, t, mt, l]) =>
            (app.chain as Substrate).council.createExternalProposalMajority(
              a,
              t,
              mt,
              l
            );
        } else if (
          vnode.state.councilMotionType === 'createExternalProposalDefault'
        ) {
          args = [
            author,
            threshold,
            EdgewareFunctionPicker.getMethod(),
            EdgewareFunctionPicker.getMethod().encodedLength,
          ];
          createFunc = ([a, t, mt, l]) =>
            (app.chain as Substrate).council.createExternalProposalDefault(
              a,
              t,
              mt,
              l
            );
        } else if (vnode.state.councilMotionType === 'createFastTrack') {
          args = [
            author,
            threshold,
            vnode.state.nextExternalProposalHash,
            vnode.state.votingPeriod,
            vnode.state.enactmentDelay,
          ];
          createFunc = ([a, b, c, d, e]) =>
            (app.chain as Substrate).council.createFastTrack(a, b, c, d, e);
        } else if (
          vnode.state.councilMotionType === 'createEmergencyCancellation'
        ) {
          args = [author, threshold, vnode.state.referendumId];
          createFunc = ([a, t, h]) =>
            (app.chain as Substrate).council.createEmergencyCancellation(
              a,
              t,
              h
            );
        } else if (
          vnode.state.councilMotionType === 'createTreasuryApprovalMotion'
        ) {
          args = [author, threshold, vnode.state.treasuryProposalIndex];
          createFunc = ([a, t, i]) =>
            (app.chain as Substrate).council.createTreasuryApprovalMotion(
              a,
              t,
              i
            );
        } else if (
          vnode.state.councilMotionType === 'createTreasuryRejectionMotion'
        ) {
          args = [author, threshold, vnode.state.treasuryProposalIndex];
          createFunc = ([a, t, i]) =>
            (app.chain as Substrate).council.createTreasuryRejectionMotion(
              a,
              t,
              i
            );
        } else {
          throw new Error('Invalid council motion type');
        }

        return createTXModal(createFunc(args)).then(done);
      } else if (proposalTypeEnum === ProposalType.SubstrateTreasuryProposal) {
        if (!vnode.state.form.beneficiary)
          throw new Error('Invalid beneficiary address');
        const beneficiary = app.chain.accounts.get(
          vnode.state.form.beneficiary
        );
        args = [author, vnode.state.form.amount, beneficiary];
      } else if (proposalTypeEnum === ProposalType.SubstrateBountyProposal) {
        if (!vnode.state.form.title) throw new Error('Invalid title');
        if (!vnode.state.form.value) throw new Error('Invalid value');
        args = [author, vnode.state.form.value, vnode.state.form.title];
        createFunc = ([a, v, t]) =>
          (app.chain as Substrate).bounties.createTx(a, v, t);
        return createTXModal(createFunc(args)).then(done);
      } else if (proposalTypeEnum === ProposalType.SubstrateBountyProposal) {
        if (!vnode.state.form.reason) throw new Error('Invalid reason');
        if (!vnode.state.form.beneficiary)
          throw new Error('Invalid beneficiary address');
        const beneficiary = app.chain.accounts.get(
          vnode.state.form.beneficiary
        );
        args = [vnode.state.form.reason, beneficiary];
      } else if (proposalTypeEnum === ProposalType.PhragmenCandidacy) {
        args = [author];
        createFunc = ([a]) =>
          (
            app.chain as Substrate
          ).phragmenElections.activeElection.submitCandidacyTx(a);
      } else if (proposalTypeEnum === ProposalType.CosmosProposal) {
        let prop: ProtobufAny;
        const { title, description } = vnode.state.form;
        const deposit = vnode.state.deposit
          ? new CosmosToken(
              (app.chain as Cosmos).governance.minDeposit.denom,
              vnode.state.deposit,
              false
            )
          : (app.chain as Cosmos).governance.minDeposit;
        if (
          vnode.state.cosmosProposalType === SupportedCosmosProposalTypes.Text
        ) {
          prop = (app.chain as Cosmos).governance.encodeTextProposal(
            title,
            description
          );
        } else if (
          vnode.state.cosmosProposalType ===
          SupportedCosmosProposalTypes.CommunitySpend
        ) {
          prop = (app.chain as Cosmos).governance.encodeCommunitySpend(
            title,
            description,
            vnode.state.recipient,
            vnode.state.payoutAmount
          );
        } else {
          throw new Error('Unknown Cosmos proposal type.');
        }
        // TODO: add disabled / loading
        (app.chain as Cosmos).governance
          .submitProposalTx(author as CosmosAccount, deposit, prop)
          .then((result) => {
            done(result);
            navigateToSubpage(`/proposal/${result}`);
          })
          .catch((err) => notifyError(err.message));
        return;
      } else if (proposalTypeEnum === ProposalType.MolochProposal) {
        // TODO: check that applicant is valid ETH address in hex
        if (!vnode.state.applicantAddress)
          throw new Error('Invalid applicant address');
        if (typeof vnode.state.tokenTribute !== 'number')
          throw new Error('Invalid token tribute');
        if (typeof vnode.state.sharesRequested !== 'number')
          throw new Error('Invalid shares requested');
        if (!vnode.state.title) throw new Error('Invalid title');
        const details = JSON.stringify({
          title: vnode.state.title,
          description: vnode.state.description || '',
        });
        (app.chain as Moloch).governance
          .createPropWebTx(
            author as MolochMember,
            vnode.state.applicantAddress,
            new BN(vnode.state.tokenTribute),
            new BN(vnode.state.sharesRequested),
            details
          )
          // TODO: handling errors?
          .then((result) => done(result))
          .then(() => m.redraw())
          .catch((err) => notifyError(err.data?.message || err.message));
        return;
      } else if (proposalTypeEnum === ProposalType.CompoundProposal) {
        vnode.state.proposer = app.user?.activeAccount?.address;
        if (!vnode.state.proposer)
          throw new Error('Invalid address / not logged in');
        if (!vnode.state.description) throw new Error('Invalid description');

        const targets = [];
        const values = [];
        const calldatas = [];
        const signatures = [];

        for (let i = 0; i < vnode.state.aaveTabCount; i++) {
          const aaveProposal = vnode.state.aaveProposalState[i];
          if (aaveProposal.target) {
            targets.push(aaveProposal.target);
          } else {
            throw new Error(`No target for Call ${i + 1}`);
          }

          values.push(aaveProposal.value || '0');
          calldatas.push(aaveProposal.calldata || '');
          signatures.push(aaveProposal.signature || '');
        }

        // if they passed a title, use the JSON format for description.
        // otherwise, keep description raw
        let description = vnode.state.description;
        if (vnode.state.title) {
          description = JSON.stringify({
            description: vnode.state.description,
            title: vnode.state.title,
          });
        }
        const details: CompoundProposalArgs = {
          description,
          targets,
          values,
          calldatas,
          signatures,
        };
        (app.chain as Compound).governance
          .propose(details)
          .then((result: string) => {
            done(result);
            return result;
          })
          .then((result: string) => {
            notifySuccess(`Proposal ${result} created successfully!`);
            m.redraw();
          })
          .catch((err) => notifyError(err.data?.message || err.message));
        return;
      } else if (proposalTypeEnum === ProposalType.AaveProposal) {
        vnode.state.proposer = app.user?.activeAccount?.address;
        if (!vnode.state.proposer)
          throw new Error('Invalid address / not logged in');
        if (!vnode.state.executor) throw new Error('Invalid executor');
        if (!vnode.state.ipfsHash) throw new Error('No ipfs hash');

        const targets = [];
        const values = [];
        const calldatas = [];
        const signatures = [];
        const withDelegateCalls = [];

        for (let i = 0; i < vnode.state.aaveTabCount; i++) {
          const aaveProposal = vnode.state.aaveProposalState[i];
          if (aaveProposal.target) {
            targets.push(aaveProposal.target);
          } else {
            throw new Error(`No target for Call ${i + 1}`);
          }

          values.push(aaveProposal.value || '0');
          calldatas.push(aaveProposal.calldata || '');
          withDelegateCalls.push(aaveProposal.withDelegateCall || false);
          signatures.push(aaveProposal.signature || '');
        }
        // TODO: preload this ipfs value to ensure it's correct
        const ipfsHash = utils.formatBytes32String(vnode.state.ipfsHash);
        const details: AaveProposalArgs = {
          executor: vnode.state.executor as string,
          targets,
          values,
          calldatas,
          signatures,
          withDelegateCalls,
          ipfsHash,
        };
        (app.chain as Aave).governance
          .propose(details)
          .then((result) => done(result))
          .then(() => m.redraw())
          .catch((err) => notifyError(err.data?.message || err.message));
        return;
        // @TODO: Create Proposal via WebTx
      } else if (proposalTypeEnum === ProposalType.SputnikProposal) {
        // TODO: make type of proposal switchable
        const member = vnode.state.member;
        const description = vnode.state.description;
        let propArgs: NearSputnikProposalKind;
        if (
          vnode.state.sputnikProposalType ===
          SupportedSputnikProposalTypes.AddMemberToRole
        ) {
          propArgs = {
            AddMemberToRole: { role: 'council', member_id: member },
          };
        } else if (
          vnode.state.sputnikProposalType ===
          SupportedSputnikProposalTypes.RemoveMemberFromRole
        ) {
          propArgs = {
            RemoveMemberFromRole: { role: 'council', member_id: member },
          };
        } else if (
          vnode.state.sputnikProposalType ===
          SupportedSputnikProposalTypes.Transfer
        ) {
          // TODO: validate amount / token id
          const token_id = vnode.state.tokenId || '';
          let amount: string;
          // treat NEAR as in dollars but tokens as whole #s
          if (!token_id) {
            amount = app.chain.chain
              .coins(+vnode.state.payoutAmount, true)
              .asBN.toString();
          } else {
            amount = `${+vnode.state.payoutAmount}`;
          }
          propArgs = { Transfer: { receiver_id: member, token_id, amount } };
        } else if (
          vnode.state.sputnikProposalType === SupportedSputnikProposalTypes.Vote
        ) {
          propArgs = 'Vote';
        } else {
          throw new Error('unsupported sputnik proposal type');
        }
        (app.chain as NearSputnik).dao
          .proposeTx(description, propArgs)
          .then((result) => done(result))
          .then(() => m.redraw())
          .catch((err) => notifyError(err.message));
        return;
      } else if (proposalTypeEnum === ProposalType.SubstrateTreasuryTip) {
        if (!vnode.state.form.beneficiary)
          throw new Error('Invalid beneficiary address');
        const beneficiary = app.chain.accounts.get(
          vnode.state.form.beneficiary
        );
        args = [author, vnode.state.form.description, beneficiary];
      } else {
        throw new Error('Invalid proposal type');
      }
      Promise.resolve(createFunc(args))
        .then((modalData) => createTXModal(modalData))
        .then(done);
    };

    // default state options
    const motions = SubstrateCollectiveProposal.motions;
    if (!vnode.state.councilMotionType) {
      vnode.state.councilMotionType = motions[0].name;
      vnode.state.councilMotionDescription = motions[0].description;
    }

    // shorthands
    const isSubstrate = app.chain.base === ChainBase.Substrate;
    const asSubstrate = app.chain as Substrate;
    const isCosmos = app.chain.base === ChainBase.CosmosSDK;
    const asCosmos = app.chain as Cosmos;

    if (!dataLoaded) {
      if (
        app.chain?.base === ChainBase.Substrate &&
        (app.chain as Substrate).chain?.timedOut
      ) {
        return m(ErrorPage, {
          message: 'Could not connect to chain',
          title: 'Proposals',
        });
      }
      return m(Spinner, {
        fill: true,
        message: 'Connecting to chain...',
        size: 'xl',
        style: 'visibility: visible; opacity: 1;',
      });
    }

    const activeEntityInfo = app.chain.meta;

    const { activeAaveTabIndex, aaveProposalState } = vnode.state;

    return m(Form, { class: 'NewProposalForm' }, [
      m(Grid, [
        m(Col, [
          vnode.state.error && m('.error', vnode.state.error.message),
          hasCouncilMotionChooser && [
            m(DropdownFormField, {
              title: 'Motion',
              choices: motions.map((m_) => ({
                name: 'councilMotionType',
                value: m_.name,
                label: m_.label,
              })),
              callback: (result) => {
                vnode.state.councilMotionType = result;
                vnode.state.councilMotionDescription = motions.find(
                  (m_) => m_.name === result
                ).description;
                m.redraw();
              },
            }),
            vnode.state.councilMotionDescription &&
              m(
                '.council-motion-description',
                vnode.state.councilMotionDescription
              ),
          ],
          // actions
          hasAction && m(EdgewareFunctionPicker),
          hasTopics &&
            m(TopicSelector, {
              topics: app.chain.meta.topics,
              updateFormData: (topicName: string, topicId?: number) => {
                vnode.state.form.topicName = topicName;
                vnode.state.form.topicId = topicId;
              },
              tabindex: 3,
            }),
          hasBountyTitle && [
            m(FormGroup, [
              m(FormLabel, 'Title'),
              m(Input, {
                placeholder: 'Bounty title (stored on chain)',
                name: 'title',
                autofocus: true,
                autocomplete: 'off',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.form.title = result;
                  m.redraw();
                },
              }),
            ]),
          ],
          hasTitleAndDescription && [
            m(FormGroup, [
              m(FormLabel, 'Title'),
              m(Input, {
                placeholder: 'Enter a title',
                name: 'title',
                autofocus: true,
                autocomplete: 'off',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.form.title = result;
                  m.redraw();
                },
              }),
            ]),
            m(FormGroup, [
              m(FormLabel, 'Description'),
              m(TextArea, {
                name: 'description',
                placeholder: 'Enter a description',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  if (vnode.state.form.description === result) return;
                  vnode.state.form.description = result;
                  m.redraw();
                },
              }),
            ]),
          ],
          hasBeneficiary && [
            m(FormGroup, [
              m(FormLabel, 'Beneficiary'),
              m(Input, {
                name: 'beneficiary',
                placeholder: 'Beneficiary of proposal',
                defaultValue: author.address,
                oncreate: (vvnode) => {
                  vnode.state.form.beneficiary = author.address;
                },
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.form.beneficiary = result;
                  m.redraw();
                },
              }),
            ]),
          ],
          hasAmount && [
            m(FormGroup, [
              m(FormLabel, `Amount (${app.chain.chain.denom})`),
              m(Input, {
                name: 'amount',
                autofocus: true,
                placeholder: 'Amount of proposal',
                autocomplete: 'off',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.form.amount = app.chain.chain.coins(
                    parseFloat(result),
                    true
                  );
                  m.redraw();
                },
              }),
            ]),
            m('p', [
              'Bond: ',
              app.chain.chain
                .coins(
                  Math.max(
                    (vnode.state.form.amount?.inDollars || 0) *
                      (app.chain as Substrate).treasury.bondPct,
                    (app.chain as Substrate).treasury.bondMinimum.inDollars
                  ),
                  true
                )
                .format(),
              ` (${
                (app.chain as Substrate).treasury.bondPct * 100
              }% of requested amount, `,
              `minimum ${(
                app.chain as Substrate
              ).treasury.bondMinimum.format()})`,
            ]),
          ],
          hasPhragmenInfo &&
            m('.council-slot-info', [
              m('p', [
                'Becoming a candidate requires a deposit of ',
                formatCoin(
                  (app.chain as Substrate).phragmenElections.candidacyBond
                ),
                '. It will be returned if you are elected, or carried over to the next election if you are in the top ',
                `${
                  (app.chain as Substrate).phragmenElections.desiredRunnersUp
                } runners-up.`,
              ]),
            ]),
          hasToggle && [
            m(RadioSelectorFormField, {
              callback: async (value) => {
                vnode.state.toggleValue = value;
                vnode.attrs.onChangeSlugEnum(value);
                m.redraw();
              },
              choices: [
                { label: 'Create Proposal', value: 'proposal', checked: true },
                { label: 'Upload Preimage', value: 'preimage', checked: false },
                {
                  label: 'Upload Imminent Preimage',
                  value: 'imminent',
                  checked: false,
                },
              ],
              name: 'democracy-tx-switcher',
            }),
          ],
          hasBountyValue && [
            m(FormGroup, [
              m(FormLabel, `Value (${app.chain.chain.denom})`),
              m(Input, {
                name: 'value',
                placeholder: 'Amount allocated to bounty',
                autocomplete: 'off',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.form.value = app.chain.chain.coins(
                    parseFloat(result),
                    true
                  );
                  m.redraw();
                },
              }),
            ]),
          ],
          hasDepositChooser && [
            m(FormGroup, [
              m(
                FormLabel,
                `Deposit (${
                  app.chain.base === ChainBase.Substrate
                    ? app.chain.currency
                    : (app.chain as Cosmos).governance.minDeposit.denom
                })`
              ),
              m(Input, {
                name: 'deposit',
                placeholder: `Min: ${
                  app.chain.base === ChainBase.Substrate
                    ? (app.chain as Substrate).democracyProposals.minimumDeposit
                        .inDollars
                    : +(app.chain as Cosmos).governance.minDeposit
                }`,
                oncreate: (vvnode) =>
                  $(vvnode.dom).val(
                    app.chain.base === ChainBase.Substrate
                      ? (app.chain as Substrate).democracyProposals
                          .minimumDeposit.inDollars
                      : +(app.chain as Cosmos).governance.minDeposit
                  ),
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.deposit = parseFloat(result);
                  m.redraw();
                },
              }),
            ]),
          ],
          hasVotingPeriodAndDelaySelector && [
            m(FormGroup, [
              m(FormLabel, 'Voting Period'),
              m(Input, {
                name: 'voting_period',
                placeholder: 'Blocks (minimum enforced)',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.votingPeriod = +result;
                  m.redraw();
                },
              }),
            ]),
            m(FormGroup, [
              m(FormLabel, 'Enactment Delay'),
              m(Input, {
                name: 'enactment_delay',
                placeholder: 'Blocks (minimum enforced)',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.enactmentDelay = +result;
                  m.redraw();
                },
              }),
            ]),
          ],
          hasReferendumSelector &&
            m(DropdownFormField, {
              title: 'Referendum',
              choices: (app.chain as Substrate).democracy.store
                .getAll()
                .map((r) => ({
                  name: 'referendum',
                  value: r.identifier,
                  label: `${r.shortIdentifier}: ${r.title}`,
                })),
              callback: (result) => {
                vnode.state.referendumId = result;
                m.redraw();
              },
            }),
          hasExternalProposalSelector &&
            m(DropdownFormField, {
              title: 'Proposal',
              choices: (app.chain as Substrate).democracyProposals.nextExternal
                ? [
                    {
                      name: 'external_proposal',
                      value: (
                        app.chain as Substrate
                      ).democracyProposals.nextExternal[0].hash.toString(),
                      label: `${(
                        app.chain as Substrate
                      ).democracyProposals.nextExternal[0].hash
                        .toString()
                        .slice(0, 8)}...`,
                    },
                  ]
                : [],
              callback: (result) => {
                vnode.state.nextExternalProposalHash = result;
                m.redraw();
              },
            }),
          hasTreasuryProposalSelector &&
            m(DropdownFormField, {
              title: 'Treasury Proposal',
              choices: (app.chain as Substrate).treasury.store
                .getAll()
                .map((r) => ({
                  name: 'external_proposal',
                  value: r.identifier,
                  label: r.shortIdentifier,
                })),
              callback: (result) => {
                vnode.state.treasuryProposalIndex = result;
                m.redraw();
              },
            }),
          hasThreshold && [
            m(FormGroup, [
              m(FormLabel, 'Threshold'),
              m(Input, {
                name: 'threshold',
                placeholder: 'How many members must vote yes to execute?',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.threshold = +result;
                  m.redraw();
                },
              }),
            ]),
          ],
          hasMolochFields && [
            m(FormGroup, [
              m(FormLabel, 'Applicant Address (will receive Moloch shares)'),
              m(Input, {
                name: 'applicant_address',
                placeholder: 'Applicant Address',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.applicantAddress = result;
                  m.redraw();
                },
              }),
            ]),
            m(FormGroup, [
              m(
                FormLabel,
                'Token Tribute (offered to Moloch, must be pre-approved for transfer)'
              ),
              m(Input, {
                name: 'token_tribute',
                placeholder: 'Tribute in tokens',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.tokenTribute = +result;
                  m.redraw();
                },
              }),
            ]),
            m(FormGroup, [
              m(FormLabel, 'Shares Requested'),
              m(Input, {
                name: 'shares_requested',
                placeholder: 'Moloch shares requested',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.sharesRequested = +result;
                  m.redraw();
                },
              }),
            ]),
            m(FormGroup, [
              m(FormLabel, 'Proposal Title'),
              m(Input, {
                name: 'title',
                placeholder: 'Proposal Title',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.title = result;
                  m.redraw();
                },
              }),
            ]),
            m(FormGroup, [
              m(FormLabel, 'Proposal Description'),
              m(Input, {
                name: 'description',
                placeholder: 'Proposal Description',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.description = result;
                  m.redraw();
                },
              }),
            ]),
          ],

          hasCompoundFields &&
            m('.AaveGovernance', [
              m(FormGroup, [
                m(FormLabel, 'Proposer (you)'),
                m('', [
                  m(User, {
                    user: author,
                    linkify: true,
                    popover: true,
                    showAddressWithDisplayName: true,
                  }),
                ]),
              ]),
              m(FormGroup, [
                m(FormLabel, 'Proposal Title (leave blank for no title)'),
                m(Input, {
                  name: 'title',
                  placeholder: 'Proposal Title',
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.title = result;
                    m.redraw();
                  },
                }),
              ]),
              m(FormGroup, [
                m(FormLabel, 'Proposal Description'),
                m(TextArea, {
                  name: 'description',
                  placeholder: 'Proposal Description',
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.description = result;
                    m.redraw();
                  },
                }),
              ]),
              m('.tab-selector', [
                m(
                  Tabs,
                  {
                    align: 'left',
                    class: 'tabs',
                  },
                  [
                    aaveProposalState.map((_, index) =>
                      m(TabItem, {
                        key: index,
                        label: `Call ${index + 1}`,
                        active: activeAaveTabIndex === index,
                        onclick: () => {
                          vnode.state.activeAaveTabIndex = index;
                        },
                      })
                    ),
                  ]
                ),
                m(PopoverMenu, {
                  closeOnContentClick: true,
                  content: [
                    m(MenuItem, {
                      iconLeft: Icons.EDIT_2,
                      label: 'Add',
                      onclick: () => {
                        vnode.state.aaveTabCount++;
                        vnode.state.activeAaveTabIndex =
                          vnode.state.aaveTabCount - 1;
                        vnode.state.aaveProposalState.push({
                          target: null,
                          value: null,
                          calldata: null,
                          signature: null,
                          withDelegateCall: false,
                        });
                      },
                    }),
                    m(MenuItem, {
                      iconLeft: Icons.TRASH_2,
                      label: 'Delete',
                      disabled: vnode.state.activeAaveTabIndex === 0,
                      onclick: () => {
                        vnode.state.aaveTabCount--;
                        vnode.state.activeAaveTabIndex =
                          vnode.state.aaveTabCount - 1;
                        vnode.state.aaveProposalState.pop();
                      },
                    }),
                  ],
                  trigger: m(Button, {
                    iconLeft: Icons.MORE_HORIZONTAL,
                    basic: true,
                  }),
                }),
              ]),
              m(FormGroup, [
                m(FormLabel, 'Target Address'),
                m(Input, {
                  name: 'targets',
                  placeholder: 'Add Target',
                  value: aaveProposalState[activeAaveTabIndex].target,
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.aaveProposalState[activeAaveTabIndex].target =
                      result;
                    m.redraw();
                  },
                }),
              ]),
              m(FormGroup, [
                m(FormLabel, 'Value'),
                m(Input, {
                  name: 'values',
                  placeholder: 'Enter amount in wei',
                  value: aaveProposalState[activeAaveTabIndex].value,
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.aaveProposalState[activeAaveTabIndex].value =
                      result;
                    m.redraw();
                  },
                }),
              ]),
              m(FormGroup, [
                m(FormLabel, 'Calldata'),
                m(Input, {
                  name: 'calldatas',
                  placeholder: 'Add Calldata',
                  value: aaveProposalState[activeAaveTabIndex].calldata,
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.aaveProposalState[activeAaveTabIndex].calldata =
                      result;
                    m.redraw();
                  },
                }),
              ]),
              m(FormGroup, [
                m('.flex-label', [
                  m(FormLabel, 'Function Signature'),
                  m('.helper-text', 'Optional'),
                ]),
                m(Input, {
                  name: 'signatures',
                  placeholder: 'Add a signature',
                  value: aaveProposalState[activeAaveTabIndex].signature,
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.aaveProposalState[
                      activeAaveTabIndex
                    ].signature = result;
                    m.redraw();
                  },
                }),
              ]),
            ]),
          hasAaveFields &&
            m('.AaveGovernance', [
              m(FormGroup, [
                m(FormLabel, 'Proposer (you)'),
                m('', [
                  m(User, {
                    user: author,
                    linkify: true,
                    popover: true,
                    showAddressWithDisplayName: true,
                  }),
                ]),
              ]),
              // TODO: validate this is the correct length, or else hash it ourselves
              m(FormGroup, [
                m(FormLabel, 'IPFS Hash'),
                m(Input, {
                  name: 'ipfsHash',
                  placeholder: 'Proposal IPFS Hash',
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.ipfsHash = result;
                    m.redraw();
                  },
                }),
              ]),
              m(FormGroup, [
                m(FormLabel, 'Executor'),
                (app.chain as Aave).governance.api.Executors.map((r) =>
                  m(
                    `.executor ${
                      vnode.state.executor === r.address && '.selected-executor'
                    }`,
                    {
                      onclick: () => {
                        vnode.state.executor = r.address;
                      },
                    },
                    [
                      m('.label', 'Address'),
                      m('', r.address),
                      m('.label .mt-16', 'Time Delay'),
                      m('', `${r.delay / (60 * 60 * 24)} Day(s)`),
                    ]
                  )
                ),
              ]),
              // TODO: display offchain copy re AIPs and ARCs from https://docs.aave.com/governance/
              m('.tab-selector', [
                m(
                  Tabs,
                  {
                    align: 'left',
                    class: 'tabs',
                  },
                  [
                    aaveProposalState.map((_, index) =>
                      m(TabItem, {
                        key: index,
                        label: `Call ${index + 1}`,
                        active: activeAaveTabIndex === index,
                        onclick: () => {
                          vnode.state.activeAaveTabIndex = index;
                        },
                      })
                    ),
                  ]
                ),
                m(PopoverMenu, {
                  closeOnContentClick: true,
                  content: [
                    m(MenuItem, {
                      iconLeft: Icons.EDIT_2,
                      label: 'Add',
                      onclick: () => {
                        vnode.state.aaveTabCount++;
                        vnode.state.activeAaveTabIndex =
                          vnode.state.aaveTabCount - 1;
                        vnode.state.aaveProposalState.push({
                          target: null,
                          value: null,
                          calldata: null,
                          signature: null,
                          withDelegateCall: false,
                        });
                      },
                    }),
                    m(MenuItem, {
                      iconLeft: Icons.TRASH_2,
                      label: 'Delete',
                      disabled: vnode.state.activeAaveTabIndex === 0,
                      onclick: () => {
                        vnode.state.aaveTabCount--;
                        vnode.state.activeAaveTabIndex =
                          vnode.state.aaveTabCount - 1;
                        vnode.state.aaveProposalState.pop();
                      },
                    }),
                  ],
                  trigger: m(Button, {
                    iconLeft: Icons.MORE_HORIZONTAL,
                    basic: true,
                  }),
                }),
              ]),
              m(FormGroup, [
                m(FormLabel, 'Target Address'),
                m(Input, {
                  name: 'targets',
                  placeholder: 'Add Target',
                  value: aaveProposalState[activeAaveTabIndex].target,
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.aaveProposalState[activeAaveTabIndex].target =
                      result;
                    m.redraw();
                  },
                }),
              ]),
              m(FormGroup, [
                m(FormLabel, 'Value'),
                m(Input, {
                  name: 'values',
                  placeholder: 'Enter amount in wei',
                  value: aaveProposalState[activeAaveTabIndex].value,
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.aaveProposalState[activeAaveTabIndex].value =
                      result;
                    m.redraw();
                  },
                }),
              ]),
              m(FormGroup, [
                m(FormLabel, 'Calldata'),
                m(Input, {
                  name: 'calldatas',
                  placeholder: 'Add Calldata',
                  value: aaveProposalState[activeAaveTabIndex].calldata,
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.aaveProposalState[activeAaveTabIndex].calldata =
                      result;
                    m.redraw();
                  },
                }),
              ]),
              m(FormGroup, [
                m('.flex-label', [
                  m(FormLabel, 'Function Signature'),
                  m('.helper-text', 'Optional'),
                ]),
                m(Input, {
                  name: 'signatures',
                  placeholder: 'Add a signature',
                  value: aaveProposalState[activeAaveTabIndex].signature,
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.aaveProposalState[
                      activeAaveTabIndex
                    ].signature = result;
                    m.redraw();
                  },
                }),
              ]),
              m(FormGroup, [
                m(FormLabel, 'Delegate Call'),
                m('', [
                  m(Button, {
                    label: 'TRUE',
                    class: `button ${
                      aaveProposalState[activeAaveTabIndex].withDelegateCall ===
                        true && 'active'
                    }`,
                    onclick: () => {
                      vnode.state.aaveProposalState[
                        activeAaveTabIndex
                      ].withDelegateCall = true;
                    },
                  }),
                  m(Button, {
                    label: 'FALSE',
                    class: `ml-12 button ${
                      aaveProposalState[activeAaveTabIndex].withDelegateCall ===
                        false && 'active'
                    }`,
                    onclick: () => {
                      vnode.state.aaveProposalState[
                        activeAaveTabIndex
                      ].withDelegateCall = false;
                    },
                  }),
                ]),
              ]),
            ]),
          hasSputnikFields && [
            // TODO: add deposit copy
            m(DropdownFormField, {
              title: 'Proposal Type',
              value: vnode.state.sputnikProposalType,
              defaultValue: SupportedSputnikProposalTypes.AddMemberToRole,
              choices: Object.values(SupportedSputnikProposalTypes).map(
                (v) => ({
                  name: 'proposalType',
                  label: v,
                  value: v,
                })
              ),
              callback: (result) => {
                vnode.state.sputnikProposalType = result;
                m.redraw();
              },
            }),
            m(FormGroup, [
              vnode.state.sputnikProposalType !==
                SupportedSputnikProposalTypes.Vote && m(FormLabel, 'Member'),
              vnode.state.sputnikProposalType !==
                SupportedSputnikProposalTypes.Vote &&
                m(Input, {
                  name: 'member',
                  defaultValue: 'tokenfactory.testnet',
                  oncreate: (vvnode) => {
                    vnode.state.member = 'tokenfactory.testnet';
                  },
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.member = result;
                    m.redraw();
                  },
                }),
            ]),
            m(FormGroup, [
              m(FormLabel, 'Description'),
              m(Input, {
                name: 'description',
                defaultValue: '',
                oncreate: (vvnode) => {
                  vnode.state.description = '';
                },
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.description = result;
                  m.redraw();
                },
              }),
            ]),
            vnode.state.sputnikProposalType ===
              SupportedSputnikProposalTypes.Transfer &&
              m(FormGroup, [
                m(FormLabel, 'Token ID (leave blank for Ⓝ)'),
                m(Input, {
                  name: 'token_id',
                  defaultValue: '',
                  oncreate: (vvnode) => {
                    vnode.state.tokenId = '';
                  },
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.tokenId = result;
                    m.redraw();
                  },
                }),
              ]),
            vnode.state.sputnikProposalType ===
              SupportedSputnikProposalTypes.Transfer &&
              m(FormGroup, [
                m(FormLabel, 'Amount'),
                m(Input, {
                  name: 'amount',
                  defaultValue: '',
                  oncreate: (vvnode) => {
                    vnode.state.payoutAmount = '';
                  },
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.payoutAmount = result;
                    m.redraw();
                  },
                }),
              ]),
          ],
          hasCosmosFields && [
            m(DropdownFormField, {
              title: 'Proposal Type',
              value: vnode.state.cosmosProposalType,
              defaultValue: SupportedCosmosProposalTypes.Text,
              choices: Object.values(SupportedCosmosProposalTypes).map((v) => ({
                name: 'proposalType',
                label: v,
                value: v,
              })),
              callback: (result) => {
                vnode.state.cosmosProposalType = result;
                m.redraw();
              },
            }),
            m(FormGroup, [
              m(FormLabel, 'Title'),
              m(Input, {
                placeholder: 'Enter a title',
                name: 'title',
                autofocus: true,
                autocomplete: 'off',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.form.title = result;
                  m.redraw();
                },
              }),
            ]),
            m(FormGroup, [
              m(FormLabel, 'Description'),
              m(TextArea, {
                name: 'description',
                placeholder: 'Enter a description',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  if (vnode.state.form.description === result) return;
                  vnode.state.form.description = result;
                  m.redraw();
                },
              }),
            ]),
            m(FormGroup, [
              m(
                FormLabel,
                `Deposit (${(app.chain as Cosmos).governance.minDeposit.denom})`
              ),
              m(Input, {
                name: 'deposit',
                placeholder: `Min: ${+(app.chain as Cosmos).governance
                  .minDeposit}`,
                oncreate: (vvnode) =>
                  $(vvnode.dom).val(
                    +(app.chain as Cosmos).governance.minDeposit
                  ),
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.deposit = +result;
                  m.redraw();
                },
              }),
            ]),
            vnode.state.cosmosProposalType !==
              SupportedCosmosProposalTypes.Text &&
              m(FormGroup, [
                m(FormLabel, 'Recipient'),
                m(Input, {
                  name: 'recipient',
                  placeholder: app.user.activeAccount.address,
                  defaultValue: '',
                  oncreate: (vvnode) => {
                    vnode.state.recipient = '';
                  },
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.recipient = result;
                    m.redraw();
                  },
                }),
              ]),
            vnode.state.cosmosProposalType !==
              SupportedCosmosProposalTypes.Text &&
              m(FormGroup, [
                m(
                  FormLabel,
                  `Amount (${
                    (app.chain as Cosmos).governance.minDeposit.denom
                  })`
                ),
                m(Input, {
                  name: 'amount',
                  placeholder: '12345',
                  defaultValue: '',
                  oncreate: (vvnode) => {
                    vnode.state.payoutAmount = '';
                  },
                  oninput: (e) => {
                    const result = (e.target as any).value;
                    vnode.state.payoutAmount = result;
                    m.redraw();
                  },
                }),
              ]),
          ],
          hasTipsFields && [
            m(FormGroup, [
              m('.label', 'Finder'),
              m(User, {
                user: author,
                linkify: true,
                popover: true,
                showAddressWithDisplayName: true,
              }),
            ]),
            m(FormGroup, [
              m(FormLabel, 'Beneficiary'),
              m(Input, {
                name: 'beneficiary',
                placeholder: 'Beneficiary of treasury proposal',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  vnode.state.form.beneficiary = result;
                  m.redraw();
                },
              }),
            ]),
            m(FormGroup, [
              m(FormLabel, 'Reason'),
              m(TextArea, {
                name: 'reason',
                placeholder:
                  'What’s the reason you want to tip the beneficiary?',
                oninput: (e) => {
                  const result = (e.target as any).value;
                  if (vnode.state.form.description === result) return;
                  vnode.state.form.description = result;
                  m.redraw();
                },
              }),
            ]),
          ],
          m(FormGroup, [
            m(Button, {
              disabled:
                proposalTypeEnum === ProposalType.SubstrateCollectiveProposal &&
                !(author as SubstrateAccount).isCouncillor,
              intent: 'primary',
              rounded: true,
              label:
                proposalTypeEnum === ProposalType.OffchainThread
                  ? 'Create thread'
                  : 'Send transaction',
              onclick: (e) => {
                e.preventDefault();
                createNewProposal();
              },
              tabindex: 4,
              type: 'submit',
            }),
          ]),
        ]),
      ]),
    ]);
  },
}
Example #21
Source File: notification_settings.ts    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
NotificationSettingsPage: m.Component<
  {},
  {
    communities: ChainInfo[];
    subscriptions: NotificationSubscription[];
    selectedCommunity: ChainInfo;
    selectedCommunityId: string;
    selectableCommunityIds: string[];
    allCommunityIds: string[];
  }
> = {
  oninit: async (vnode) => {
    if (!app.isLoggedIn) {
      notifyError('Must be logged in to configure notifications');
      m.route.set('/');
    }
    vnode.state.subscriptions = [];
    vnode.state.communities = [];

    // initialize vnode.state.subscriptions
    $.get(`${app.serverUrl()}/viewSubscriptions`, {
      jwt: app.user.jwt,
    }).then(
      (result) => {
        vnode.state.subscriptions = [];
        result.result.forEach((sub) => {
          vnode.state.subscriptions.push(
            NotificationSubscription.fromJSON(sub)
          );
        });
        m.redraw();
      },
      (error) => {
        notifyError('Could not load notification settings');
        m.route.set('/');
      }
    );

    // initialize vnode.state.communities
    const selectableCommunityIds = app.user.roles
      .filter((role) => role.chain_id)
      .map((r) => r.chain_id);
    vnode.state.communities = _.uniq(
      app.config.chains
        .getAll()
        .filter((c) => selectableCommunityIds.includes(c.id))
    );

    // initialize vnode.state.allCommunityIds
    vnode.state.allCommunityIds = [];
    _.uniq(app.config.chains.getAll()).forEach((c) =>
      vnode.state.allCommunityIds.push(c.id)
    );
    vnode.state.communities.forEach((c) =>
      vnode.state.allCommunityIds.push(c.id)
    );

    // initialize selectableCommunityIds
    vnode.state.selectableCommunityIds = [ALL_COMMUNITIES];
    vnode.state.communities.forEach((c) =>
      vnode.state.selectableCommunityIds.push(c.name)
    );
    const chains = _.uniq(app.config.chains.getAll());
    const chainsWithRole = app.user.roles.map((r) => r.chain_id);
    chains.forEach((c) => {
      if (chainsWithRole.includes(c.id))
        vnode.state.selectableCommunityIds.push(c.name);
    });
    vnode.state.selectableCommunityIds.sort();

    // initialize vnode.state.selectedCommunity, vnode.state.selectedCommunityId
    vnode.state.selectedCommunityId = ALL_COMMUNITIES;
    vnode.state.selectedCommunity = null;
  },
  view: (vnode) => {
    const { communities, subscriptions } = vnode.state;
    const {
      selectedCommunity,
      selectedCommunityId,
      selectableCommunityIds,
      allCommunityIds,
    } = vnode.state;
    const chains = _.uniq(app.config.chains.getAll());
    if (!app.loginStatusLoaded())
      return m(PageLoading, {
        title: [
          'Notification Settings ',
          m(Tag, {
            size: 'xs',
            label: 'Beta',
            style: 'position: relative; top: -2px; margin-left: 6px',
          }),
        ],
      });
    if (!app.isLoggedIn())
      return m(PageError, {
        title: [
          'Notification Settings ',
          m(Tag, {
            size: 'xs',
            label: 'Beta',
            style: 'position: relative; top: -2px; margin-left: 6px',
          }),
        ],
        message: 'This page requires you to be logged in.',
      });
    if (subscriptions.length < 1)
      return m(PageLoading, {
        title: [
          'Notification Settings ',
          m(Tag, {
            size: 'xs',
            label: 'Beta',
            style: 'position: relative; top: -2px; margin-left: 6px',
          }),
        ],
      });
    return m(
      Sublayout,
      {
        class: 'NotificationSettingsPage',
        title: [
          'Notification Settings ',
          m(Tag, {
            size: 'xs',
            label: 'Beta',
            style: 'position: relative; top: -2px; margin-left: 6px',
          }),
        ],
      },
      m('.NotificationSettingsPage', [
        m(EmailIntervalConfiguration),
        communities &&
          subscriptions &&
          m('.CommunityNotifications', [
            m('.header', [
              m(SelectList, {
                class: 'CommunityNotificationSelectList',
                filterable: false,
                checkmark: false,
                emptyContent: null,
                popoverAttrs: {
                  transitionDuration: 0,
                },
                itemRender: (community: string) => {
                  return m(ListItem, {
                    label: community,
                    selected: vnode.state.selectedCommunityId === community,
                  });
                },
                items: selectableCommunityIds,
                trigger: m(Button, {
                  align: 'left',
                  compact: true,
                  rounded: true,
                  disabled: !app.user.emailVerified,
                  iconRight: Icons.CHEVRON_DOWN,
                  label: vnode.state.selectedCommunity
                    ? vnode.state.selectedCommunityId
                    : ALL_COMMUNITIES,
                }),
                onSelect: (community: string) => {
                  vnode.state.selectedCommunity =
                    communities.find((c) => c.name === community) ||
                    chains.find((c) => c.name === community);
                  vnode.state.selectedCommunityId =
                    vnode.state.selectedCommunity?.name || ALL_COMMUNITIES;
                  m.redraw();
                },
              }),
            ]),
            m(Table, { class: 'NotificationsTable' }, [
              // off-chain discussion notifications
              m('tr', [m('th', NOTIFICATION_TABLE_PRE_COPY), m('th', '')]),
              selectedCommunityId === ALL_COMMUNITIES && [
                m(AllCommunitiesNotifications, {
                  communities: allCommunityIds,
                  subscriptions,
                }),
                m(
                  'tr.on-chain-events-header',
                  m('th', { colspan: 2 }, 'Edgeware chain events')
                ),
                m(EdgewareChainEventNotifications),
                m(
                  'tr.on-chain-events-header',
                  m('th', { colspan: 2 }, 'Kulupu chain events')
                ),
                m(KulupuChainEventNotifications),
                m(
                  'tr.on-chain-events-header',
                  m('th', { colspan: 2 }, 'Kusama chain events')
                ),
                m(KusamaChainEventNotifications),
                m(
                  'tr.on-chain-events-header',
                  m('th', { colspan: 2 }, 'Polkadot chain events')
                ),
                m(PolkadotChainEventNotifications),
                m(
                  'tr.on-chain-events-header',
                  m('th', { colspan: 2 }, 'dYdX chain events')
                ),
                m(DydxChainEventNotifications),
              ],
              selectedCommunity &&
                m(IndividualCommunityNotifications, {
                  subscriptions,
                  community: selectedCommunity,
                }),
              // on-chain event notifications
              selectedCommunity instanceof ChainInfo && [
                m(
                  'tr.on-chain-events-header',
                  m('th', { colspan: 2 }, CHAIN_NOTIFICATION_TABLE_PRE_COPY)
                ),
                selectedCommunity.network === ChainNetwork.Edgeware &&
                  m(EdgewareChainEventNotifications),
                selectedCommunity.network === ChainNetwork.Kulupu &&
                  m(KulupuChainEventNotifications),
                selectedCommunity.network === ChainNetwork.Kusama &&
                  m(KusamaChainEventNotifications),
                selectedCommunity.network === ChainNetwork.Polkadot &&
                  m(PolkadotChainEventNotifications),
                selectedCommunity.network === ChainNetwork.Aave &&
                  m(DydxChainEventNotifications),
              ],
            ]),
          ]),
      ])
    );
  },
}
Example #22
Source File: create_comment.ts    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
CreateComment: m.Component<
  {
    callback: CallableFunction;
    cancellable?: boolean;
    getSetGlobalEditingStatus: CallableFunction;
    proposalPageState: IProposalPageState;
    parentComment?: OffchainComment<any>;
    rootProposal: AnyProposal | OffchainThread;
    tabindex?: number;
  },
  {
    quillEditorState: any;
    uploadsInProgress;
    error;
    saving: boolean;
    sendingComment;
  }
> = {
  view: (vnode) => {
    const {
      callback,
      cancellable,
      getSetGlobalEditingStatus,
      proposalPageState,
      rootProposal,
    } = vnode.attrs;
    let { parentComment } = vnode.attrs;
    let disabled =
      getSetGlobalEditingStatus(GlobalStatus.Get) ||
      vnode.state.quillEditorState?.editor?.editor?.isBlank();

    const author = app.user.activeAccount;
    const parentType =
      parentComment || proposalPageState.parentCommentId
        ? CommentParent.Comment
        : CommentParent.Proposal;
    if (!parentComment) parentComment = null;
    if (vnode.state.uploadsInProgress === undefined) {
      vnode.state.uploadsInProgress = 0;
    }

    const submitComment = async (e?) => {
      if (
        !vnode.state.quillEditorState ||
        !vnode.state.quillEditorState.editor
      ) {
        if (e) e.preventDefault();
        vnode.state.error = 'Editor not initialized, please try again';
        return;
      }
      if (vnode.state.quillEditorState.editor.editor.isBlank()) {
        if (e) e.preventDefault();
        vnode.state.error = 'Comment cannot be blank';
        return;
      }

      const { quillEditorState } = vnode.state;

      const mentionsEle = document.getElementsByClassName(
        'ql-mention-list-container'
      )[0];
      if (mentionsEle) (mentionsEle as HTMLElement).style.visibility = 'hidden';

      const commentText = quillEditorState.markdownMode
        ? quillEditorState.editor.getText()
        : JSON.stringify(quillEditorState.editor.getContents());

      const attachments = [];
      // const attachments = vnode.state.files ?
      //   vnode.state.files.map((f) => f.uploadURL.replace(/\?.*/, '')) : [];

      vnode.state.error = null;
      vnode.state.sendingComment = true;
      quillEditorState.editor.enable(false);
      const chainId = app.activeChainId();
      try {
        const res = await app.comments.create(
          author.address,
          rootProposal.uniqueIdentifier,
          chainId,
          commentText,
          proposalPageState.parentCommentId,
          attachments
        );
        callback();
        if (vnode.state.quillEditorState.editor) {
          vnode.state.quillEditorState.editor.enable();
          vnode.state.quillEditorState.editor.setContents();
          vnode.state.quillEditorState.clearUnsavedChanges();
        }
        vnode.state.sendingComment = false;
        proposalPageState.recentlySubmitted = res.id;
        // TODO: Instead of completely refreshing notifications, just add the comment to subscriptions
        // once we are receiving notifications from the websocket
        await app.user.notifications.refresh();
        m.redraw();
        jumpHighlightComment(res.id);
      } catch (err) {
        console.log(err);
        notifyError(err.message || 'Comment submission failed.');
        if (vnode.state.quillEditorState.editor) {
          vnode.state.quillEditorState.editor.enable();
        }
        vnode.state.error = err.message;
        vnode.state.sendingComment = false;
        m.redraw();
      }
      vnode.state.saving = false;

      proposalPageState.replying = false;
      proposalPageState.parentCommentId = null;
    };

    const activeTopicName =
      rootProposal instanceof OffchainThread ? rootProposal?.topic?.name : null;

    const isAdmin =
      app.user.isSiteAdmin ||
      app.user.isAdminOfEntity({ chain: app.activeChainId() });

    let parentScopedClass = 'new-thread-child';
    let parentAuthor: Account<any>;
    if (parentType === CommentParent.Comment) {
      parentScopedClass = 'new-comment-child';
      parentAuthor = app.chain.accounts.get(parentComment.author);
    }

    const { error, sendingComment, uploadsInProgress } = vnode.state;
    disabled =
      getSetGlobalEditingStatus(GlobalStatus.Get) ||
      vnode.state.quillEditorState?.editor?.editor?.isBlank() ||
      sendingComment ||
      uploadsInProgress ||
      !app.user.activeAccount;

    // token balance check if needed
    const tokenPostingThreshold: BN =
      TopicGateCheck.getTopicThreshold(activeTopicName);
    const userBalance: BN = TopicGateCheck.getUserBalance();
    const topicGated = TopicGateCheck.isGatedTopic(activeTopicName);
    disabled = disabled || (!isAdmin && topicGated);

    const decimals = app.chain?.meta?.decimals
      ? app.chain.meta.decimals
      : app.chain.network === ChainNetwork.ERC721
      ? 0
      : 18;
    return m(
      '.CreateComment',
      {
        class: parentScopedClass,
      },
      [
        m('.create-comment-avatar', [
          m(User, {
            user: author,
            popover: true,
            avatarOnly: true,
            avatarSize: 40,
          }),
        ]),
        m('.create-comment-body', [
          m('.reply-header', [
            m(
              'h3',
              parentType === CommentParent.Comment
                ? [
                    'Replying to ',
                    m(User, {
                      user: parentAuthor,
                      popover: true,
                      hideAvatar: true,
                    }),
                  ]
                : 'Reply'
            ),
          ]),
          m(User, { user: author, popover: true, hideAvatar: true }),
          rootProposal instanceof OffchainThread && rootProposal.readOnly
            ? m(Callout, {
                intent: 'primary',
                content:
                  'Commenting is disabled because this post has been locked.',
              })
            : [
                app.user.activeAccount?.profile &&
                  !app.user.activeAccount.profile.name &&
                  m(Callout, {
                    class: 'no-profile-callout',
                    intent: 'primary',
                    content: [
                      "You haven't set a display name yet. ",
                      m(
                        'a',
                        {
                          href:
                            `/${app.activeChainId()}/account/${
                              app.user.activeAccount.address
                            }?base=${app.user.activeAccount.chain}`,
                          onclick: (e) => {
                            e.preventDefault();
                            app.modals.create({
                              modal: EditProfileModal,
                              data: {
                                account: app.user.activeAccount,
                                refreshCallback: () => m.redraw(),
                              },
                            });
                          },
                        },
                        'Set a display name'
                      ),
                    ],
                  }),
                m(QuillEditor, {
                  contentsDoc: '',
                  oncreateBind: (state) => {
                    vnode.state.quillEditorState = state;
                  },
                  editorNamespace: `${document.location.pathname}-commenting`,
                  onkeyboardSubmit: () => {
                    submitComment();
                    m.redraw(); // ensure button is disabled
                  },
                  imageUploader: true,
                  tabindex: vnode.attrs.tabindex,
                }),
                m('.token-requirement', [
                  tokenPostingThreshold && tokenPostingThreshold.gt(new BN(0))
                    ? [
                        `Commenting in "${activeTopicName}" requires `,
                        `${weiToTokens(
                          tokenPostingThreshold.toString(),
                          decimals
                        )} `,
                        `${app.chain.meta.symbol}. `,
                        userBalance &&
                          app.user.activeAccount &&
                          `You have ${weiToTokens(
                            userBalance.toString(),
                            decimals
                          )} ${app.chain.meta.symbol}.`,
                      ]
                    : null,
                ]),
                m(
                  '.form-bottom',
                  {
                    onmouseover: () => m.redraw(), // keeps Quill's isBlank up to date
                  },
                  [
                    m('.form-buttons', [
                      m(Button, {
                        intent: 'primary',
                        type: 'submit',
                        compact: true,
                        disabled,
                        rounded: true,
                        onclick: submitComment,
                        label:
                          uploadsInProgress > 0 ? 'Uploading...' : 'Submit',
                      }),
                      cancellable &&
                        m(Button, {
                          intent: 'none',
                          type: 'cancel',
                          compact: true,
                          rounded: true,
                          onclick: (e) => {
                            e.preventDefault();
                            proposalPageState.replying = false;
                            proposalPageState.parentCommentId = null;
                          },
                          label: 'Cancel',
                        }),
                    ]),
                    error && m('.new-comment-error', error),
                  ]
                ),
              ],
        ]),
      ]
    );
  },
}
Example #23
Source File: proposals.tsx    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
ProposalsPage: m.Component<{}> = {
  oncreate: (vnode) => {
    const returningFromThread =
      app.lastNavigatedBack() && app.lastNavigatedFrom().includes('/proposal/');
    if (
      returningFromThread &&
      localStorage[`${app.activeChainId()}-proposals-scrollY`]
    ) {
      setTimeout(() => {
        window.scrollTo(
          0,
          Number(localStorage[`${app.activeChainId()}-proposals-scrollY`])
        );
      }, 100);
    }
  },

  view() {
    if (!app.chain || !app.chain.loaded) {
      if (
        app.chain?.base === ChainBase.Substrate &&
        (app.chain as Substrate).chain?.timedOut
      ) {
        return (
          <ErrorPage
            message="Could not connect to chain"
            title={[
              'Proposals',
              <Tag
                size="xs"
                label="Beta"
                style="position: relative; top: -2px; margin-left: 6px"
              />,
            ]}
          />
        );
      }
      if (app.chain?.failed)
        return (
          <PageNotFound
            title="Wrong Ethereum Provider Network!"
            message="Change Metamask to point to Ethereum Mainnet"
          />
        );
      return (
        <PageLoading
          message="Connecting to chain"
          title={[
            'Proposals',
            <Tag
              size="xs"
              label="Beta"
              style="position: relative; top: -2px; margin-left: 6px"
            />,
          ]}
          showNewProposalButton={true}
        />
      );
    }

    const onSubstrate = app.chain && app.chain.base === ChainBase.Substrate;
    const onMoloch = app.chain && app.chain.network === ChainNetwork.Moloch;
    const onCompound = app.chain && app.chain.network === ChainNetwork.Compound;
    const onAave = app.chain && app.chain.network === ChainNetwork.Aave;
    const onSputnik = app.chain && app.chain.network === ChainNetwork.Sputnik;

    const modLoading = loadSubstrateModules('Proposals', getModules);
    if (modLoading) return modLoading;

    // active proposals
    const activeDemocracyProposals =
      onSubstrate &&
      (app.chain as Substrate).democracyProposals.store
        .getAll()
        .filter((p) => !p.completed);
    const activeCouncilProposals =
      onSubstrate &&
      (app.chain as Substrate).council.store
        .getAll()
        .filter((p) => !p.completed);
    const activeCosmosProposals =
      app.chain &&
      app.chain.base === ChainBase.CosmosSDK &&
      (app.chain as Cosmos).governance.store
        .getAll()
        .filter((p) => !p.completed)
        .sort((a, b) => +b.identifier - +a.identifier);
    const activeMolochProposals =
      onMoloch &&
      (app.chain as Moloch).governance.store
        .getAll()
        .filter((p) => !p.completed)
        .sort((p1, p2) => +p2.data.timestamp - +p1.data.timestamp);
    const activeCompoundProposals =
      onCompound &&
      (app.chain as Compound).governance.store
        .getAll()
        .filter((p) => !p.completed)
        .sort((p1, p2) => +p2.startingPeriod - +p1.startingPeriod);
    const activeAaveProposals =
      onAave &&
      (app.chain as Aave).governance.store
        .getAll()
        .filter((p) => !p.completed)
        .sort((p1, p2) => +p2.startBlock - +p1.startBlock);
    const activeSputnikProposals =
      onSputnik &&
      (app.chain as NearSputnik).dao.store
        .getAll()
        .filter((p) => !p.completed)
        .sort((p1, p2) => p2.data.id - p1.data.id);

    const activeProposalContent =
      !activeDemocracyProposals?.length &&
      !activeCouncilProposals?.length &&
      !activeCosmosProposals?.length &&
      !activeMolochProposals?.length &&
      !activeCompoundProposals?.length &&
      !activeAaveProposals?.length &&
      !activeSputnikProposals?.length
        ? [<div class="no-proposals">No active proposals</div>]
        : [
            (activeDemocracyProposals || [])
              .map((proposal) => <ProposalCard proposal={proposal} />)
              .concat(
                (activeCouncilProposals || []).map((proposal) => (
                  <ProposalCard proposal={proposal} />
                ))
              )
              .concat(
                (activeCosmosProposals || []).map((proposal) => (
                  <ProposalCard proposal={proposal} />
                ))
              )
              .concat(
                (activeMolochProposals || []).map((proposal) => (
                  <ProposalCard proposal={proposal} />
                ))
              )
              .concat(
                (activeCompoundProposals || []).map((proposal) => (
                  <ProposalCard proposal={proposal} />
                ))
              )
              .concat(
                (activeAaveProposals || []).map((proposal) => (
                  <ProposalCard
                    proposal={proposal}
                    injectedContent={AaveProposalCardDetail}
                  />
                ))
              )
              .concat(
                (activeSputnikProposals || []).map((proposal) => (
                  <ProposalCard proposal={proposal} />
                ))
              ),
          ];

    // inactive proposals
    const inactiveDemocracyProposals =
      onSubstrate &&
      (app.chain as Substrate).democracyProposals.store
        .getAll()
        .filter((p) => p.completed);
    const inactiveCouncilProposals =
      onSubstrate &&
      (app.chain as Substrate).council.store
        .getAll()
        .filter((p) => p.completed);
    const inactiveCosmosProposals =
      app.chain &&
      app.chain.base === ChainBase.CosmosSDK &&
      (app.chain as Cosmos).governance.store
        .getAll()
        .filter((p) => p.completed)
        .sort((a, b) => +b.identifier - +a.identifier);
    const inactiveMolochProposals =
      onMoloch &&
      (app.chain as Moloch).governance.store
        .getAll()
        .filter((p) => p.completed)
        .sort((p1, p2) => +p2.data.timestamp - +p1.data.timestamp);
    const inactiveCompoundProposals =
      onCompound &&
      (app.chain as Compound).governance.store
        .getAll()
        .filter((p) => p.completed)
        .sort((p1, p2) => +p2.startingPeriod - +p1.startingPeriod);
    const inactiveAaveProposals =
      onAave &&
      (app.chain as Aave).governance.store
        .getAll()
        .filter((p) => p.completed)
        .sort((p1, p2) => +p2.startBlock - +p1.startBlock);
    const inactiveSputnikProposals =
      onSputnik &&
      (app.chain as NearSputnik).dao.store
        .getAll()
        .filter((p) => p.completed)
        .sort((p1, p2) => p2.data.id - p1.data.id);

    const inactiveProposalContent =
      !inactiveDemocracyProposals?.length &&
      !inactiveCouncilProposals?.length &&
      !inactiveCosmosProposals?.length &&
      !inactiveMolochProposals?.length &&
      !inactiveCompoundProposals?.length &&
      !inactiveAaveProposals?.length &&
      !inactiveSputnikProposals?.length
        ? [<div class="no-proposals">No past proposals</div>]
        : [
            (inactiveDemocracyProposals || [])
              .map((proposal) => <ProposalCard proposal={proposal} />)
              .concat(
                (inactiveCouncilProposals || []).map((proposal) => (
                  <ProposalCard proposal={proposal} />
                ))
              )
              .concat(
                (inactiveCosmosProposals || []).map((proposal) => (
                  <ProposalCard proposal={proposal} />
                ))
              )
              .concat(
                (inactiveMolochProposals || []).map((proposal) => (
                  <ProposalCard proposal={proposal} />
                ))
              )
              .concat(
                (inactiveCompoundProposals || []).map((proposal) => (
                  <ProposalCard proposal={proposal} />
                ))
              )
              .concat(
                (inactiveAaveProposals || []).map((proposal) => (
                  <ProposalCard
                    proposal={proposal}
                    injectedContent={AaveProposalCardDetail}
                  />
                ))
              )
              .concat(
                (inactiveSputnikProposals || []).map((proposal) => (
                  <ProposalCard proposal={proposal} />
                ))
              ),
          ];

    return (
      <Sublayout
        title={[
          'Proposals',
          <Tag
            size="xs"
            label="Beta"
            style="position: relative; top: -2px; margin-left: 6px"
          />,
        ]}
        showNewProposalButton={true}
      >
        <div class="ProposalsPage">
          {onSubstrate && (
            <SubstrateProposalStats
              nextLaunchBlock={
                (app.chain as Substrate).democracyProposals.nextLaunchBlock
              }
            />
          )}
          {onCompound && <CompoundProposalStats chain={app.chain} />}
          <CardsCollection content={activeProposalContent} header="Active" />
          <CardsCollection
            content={inactiveProposalContent}
            header="Inactive"
          />
        </div>
      </Sublayout>
    );
  }
}
Example #24
Source File: app.ts    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
// called by the user, when clicking on the chain/node switcher menu
// returns a boolean reflecting whether initialization of chain via the
// initChain fn ought to proceed or abort
export async function selectChain(
  chain?: ChainInfo,
  deferred = false
): Promise<boolean> {
  // Select the default node, if one wasn't provided
  if (!chain) {
    if (app.user.selectedChain) {
      chain = app.user.selectedChain;
    } else {
      chain = app.config.chains.getById(app.config.defaultChain);
    }
    if (!chain) {
      throw new Error('no chain available');
    }
  }

  // Check for valid chain selection, and that we need to switch
  if (app.chain && chain === app.chain.meta) {
    return;
  }

  // Shut down old chain if applicable
  await deinitChainOrCommunity();
  app.chainPreloading = true;
  document.title = `Commonwealth – ${chain.name}`;
  setTimeout(() => m.redraw()); // redraw to show API status indicator

  // Import top-level chain adapter lazily, to facilitate code split.
  let newChain;
  let initApi; // required for NEAR
  if (chain.base === ChainBase.Substrate) {
    const Substrate = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "substrate-main" */
        './controllers/chain/substrate/main'
      )
    ).default;
    newChain = new Substrate(chain, app);
  } else if (chain.base === ChainBase.CosmosSDK) {
    const Cosmos = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "cosmos-main" */
        './controllers/chain/cosmos/main'
      )
    ).default;
    newChain = new Cosmos(chain, app);
  } else if (chain.network === ChainNetwork.Ethereum) {
    const Ethereum = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "ethereum-main" */
        './controllers/chain/ethereum/main'
      )
    ).default;
    newChain = new Ethereum(chain, app);
  } else if (
    chain.network === ChainNetwork.NEAR ||
    chain.network === ChainNetwork.NEARTestnet
  ) {
    const Near = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "near-main" */
        './controllers/chain/near/main'
      )
    ).default;
    newChain = new Near(chain, app);
    initApi = true;
  } else if (chain.network === ChainNetwork.Sputnik) {
    const Sputnik = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "sputnik-main" */
        './controllers/chain/near/sputnik/adapter'
      )
    ).default;
    newChain = new Sputnik(chain, app);
    initApi = true;
  } else if (chain.network === ChainNetwork.Moloch) {
    const Moloch = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "moloch-main" */
        './controllers/chain/ethereum/moloch/adapter'
      )
    ).default;
    newChain = new Moloch(chain, app);
  } else if (chain.network === ChainNetwork.Compound) {
    const Compound = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "compound-main" */
        './controllers/chain/ethereum/compound/adapter'
      )
    ).default;
    newChain = new Compound(chain, app);
  } else if (chain.network === ChainNetwork.Aave) {
    const Aave = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "aave-main" */
        './controllers/chain/ethereum/aave/adapter'
      )
    ).default;
    newChain = new Aave(chain, app);
  } else if (
    chain.network === ChainNetwork.ERC20 ||
    chain.network === ChainNetwork.AxieInfinity
  ) {
    const ERC20 = (
      await import(
        //   /* webpackMode: "lazy" */
        //   /* webpackChunkName: "erc20-main" */
        './controllers/chain/ethereum/tokenAdapter'
      )
    ).default;
    newChain = new ERC20(chain, app);
  } else if (chain.network === ChainNetwork.ERC721) {
    const ERC721 = (
      await import(
        //   /* webpackMode: "lazy" */
        //   /* webpackChunkName: "erc721-main" */
        './controllers/chain/ethereum/NftAdapter'
      )
    ).default;
    newChain = new ERC721(chain, app);
  } else if (chain.network === ChainNetwork.SPL) {
    const SPL = (
      await import(
        //   /* webpackMode: "lazy" */
        //   /* webpackChunkName: "spl-main" */
        './controllers/chain/solana/tokenAdapter'
      )
    ).default;
    newChain = new SPL(chain, app);
  } else if (chain.base === ChainBase.Solana) {
    const Solana = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "solana-main" */
        './controllers/chain/solana/main'
      )
    ).default;
    newChain = new Solana(chain, app);
  } else if (chain.network === ChainNetwork.Commonwealth) {
    const Commonwealth = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "commonwealth-main" */
        './controllers/chain/ethereum/commonwealth/adapter'
      )
    ).default;
    newChain = new Commonwealth(chain, app);
  } else if (
    chain.base === ChainBase.Ethereum &&
    chain.type === ChainType.Offchain
  ) {
    const Ethereum = (
      await import(
        /* webpackMode: "lazy" */
        /* webpackChunkName: "ethereum-main" */
        './controllers/chain/ethereum/main'
      )
    ).default;
    newChain = new Ethereum(chain, app);
  } else {
    throw new Error('Invalid chain');
  }

  // Load server data without initializing modules/chain connection.
  const finalizeInitialization = await newChain.initServer();

  // If the user is still on the initializing node, finalize the
  // initialization; otherwise, abort, deinit, and return false.
  //
  // Also make sure the state is sufficiently reset so that the
  // next redraw cycle will reinitialize any needed chain.
  if (!finalizeInitialization) {
    console.log('Chain loading aborted');
    app.chainPreloading = false;
    app.chain = null;
    return false;
  } else {
    app.chain = newChain;
  }
  if (initApi) {
    await app.chain.initApi(); // required for loading NearAccounts
  }
  app.chainPreloading = false;
  app.chain.deferred = deferred;

  // Instantiate active addresses before chain fully loads
  await updateActiveAddresses(chain);

  // Update default on server if logged in
  if (app.isLoggedIn()) {
    await app.user.selectChain({
      chain: chain.id,
    });
  }

  // If the user was invited to a chain/community, we can now pop up a dialog for them to accept the invite
  handleInviteLinkRedirect();

  // Redraw with not-yet-loaded chain and return true to indicate
  // initialization has finalized.
  m.redraw();
  return true;
}
Example #25
Source File: sputnik_form.tsx    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
view(vnode) {
    return (
      <div class="CreateCommunityForm">
        <InputRow
          title="DAO Name"
          defaultValue={this.state.form.name}
          onChangeHandler={(v) => {
            this.state.form.name = v.toLowerCase();
          }}
          placeholder="genesis"
        />
        <ToggleRow
          title="Network"
          defaultValue={this.state.form.isMainnet}
          onToggle={(checked) => {
            vnode.state.isMainnet = checked;
            mixpanelBrowserTrack({
              event: MixpanelCommunityCreationEvent.CHAIN_SELECTED,
              chainBase: ChainBase.CosmosSDK,
              isCustomDomain: app.isCustomDomain(),
              communityType: CommunityType.SputnikDao,
            });
          }}
          label={(checked) => {
            if (checked !== this.state.form.isMainnet) {
              return 'Unknown network!';
            }
            return checked ? 'Mainnet' : 'Testnet';
          }}
        />
        {/* TODO: add divider to distinguish on-chain data */}
        {...defaultChainRows(this.state.form)}
        <CWButton
          label="Save changes"
          disabled={this.state.saving}
          onclick={async () => {
            const { iconUrl, name } = this.state.form;

            this.state.saving = true;

            const isMainnet = this.state.form.isMainnet;

            const id = isMainnet
              ? `${name}.sputnik-dao.near`
              : `${name}.sputnikv2.testnet`;

            const url = isMainnet
              ? 'https://rpc.mainnet.near.org'
              : 'https://rpc.testnet.near.org';

            const createChainArgs = {
              base: ChainBase.NEAR,
              icon_url: iconUrl,
              id,
              jwt: app.user.jwt,
              name: id,
              network: ChainNetwork.Sputnik,
              node_url: url,
              symbol: isMainnet ? 'NEAR' : 'tNEAR',
              type: ChainType.DAO,
              ...this.state.form,
            };
            mixpanelBrowserTrack({
              event: MixpanelCommunityCreationEvent.CREATE_COMMUNITY_ATTEMPTED,
              chainBase: null,
              isCustomDomain: app.isCustomDomain(),
              communityType: null,
            });

            try {
              // Gabe 2/14/22 Commenting this bit out because it isn't actually used, but maybe it will be someday?
              //
              // verify the DAO exists
              //   const config: ConnectConfig = {
              //     networkId: isMainnet ? 'mainnet' : 'testnet',
              //     nodeUrl: url,
              //     keyStore: new keyStores.BrowserLocalStorageKeyStore(
              //       localStorage
              //     ),
              //   };
              //   const api = await nearConnect(config);

              //   const rawResult = await api.connection.provider.query<CodeResult>(
              //     {
              //       request_type: 'call_function',
              //       account_id: id,
              //       method_name: 'get_last_proposal_id',
              //       args_base64: Buffer.from(JSON.stringify({})).toString(
              //         'base64'
              //       ),
              //       finality: 'optimistic',
              //     }
              //   );
              //   const _validResponse = JSON.parse(
              //     Buffer.from(rawResult.result).toString()
              //   );

              // POST object
              const res = await $.post(
                `${app.serverUrl()}/createChain`,
                createChainArgs
              );
              await initAppState(false);
              m.route.set(`${window.location.origin}/${res.result.chain.id}`);
            } catch (err) {
              notifyError(err.responseJSON?.error || 'Adding DAO failed.');
              console.error(err.responseJSON?.error || err.message);
              this.state.saving = false;
            }
          }}
        />
      </div>
    );
  }
Example #26
Source File: spl_token_form.tsx    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
view() {
    const disableField = !this.state.loaded;

    const updateTokenForum = async () => {
      this.state.status = '';
      this.state.error = '';
      let mintPubKey: solw3.PublicKey;
      try {
        mintPubKey = new solw3.PublicKey(this.state.form.mint);
      } catch (e) {
        this.state.error = 'Invalid mint address';
        return false;
      }
      if (!mintPubKey) return;
      this.state.loading = true;
      try {
        const url = solw3.clusterApiUrl(this.state.form.cluster);
        const connection = new solw3.Connection(url);
        const supply = await connection.getTokenSupply(mintPubKey);
        const { decimals, amount } = supply.value;
        this.state.form.decimals = decimals;
        this.state.loaded = true;
        this.state.status = `Found ${amount} supply!`;
      } catch (err) {
        this.state.error = `Error: ${err.message}` || 'Failed to load token';
      }
      this.state.loading = false;
      m.redraw();
    };

    return (
      <div class="CreateCommunityForm">
        <SelectRow
          title="Cluster"
          options={['mainnet-beta', 'testnet', 'devnet']}
          value={this.state.form.cluster}
          onchange={(value) => {
            this.state.form.cluster = value;
            this.state.loaded = false;
          }}
        />
        <InputRow
          title="Mint Address"
          defaultValue={this.state.form.mint}
          placeholder="2sgDUTgTP6e9CrJtexGdba7qZZajVVHf9TiaCtS9Hp3P"
          onChangeHandler={(v) => {
            this.state.form.mint = v.trim();
            this.state.loaded = false;
          }}
        />
        <CWButton
          label="Check address"
          disabled={this.state.saving || this.state.loading}
          onclick={async () => {
            await updateTokenForum();
          }}
        />
        <ValidationRow error={this.state.error} status={this.state.status} />
        <InputRow
          title="Name"
          defaultValue={this.state.form.name}
          disabled={disableField}
          onChangeHandler={(v) => {
            this.state.form.name = v;
            this.state.form.id = slugifyPreserveDashes(v);
          }}
        />
        <IdRow id={this.state.form.id} />
        <InputRow
          title="Symbol"
          disabled={disableField}
          defaultValue={this.state.form.symbol}
          placeholder="XYZ"
          onChangeHandler={(v) => {
            this.state.form.symbol = v;
          }}
        />
        <InputRow
          title="Decimals"
          defaultValue={`${this.state.form.decimals}`}
          disabled={true}
          onChangeHandler={(v) => {
            this.state.form.decimals = +v;
          }}
        />
        {...defaultChainRows(this.state.form, disableField)}
        <CWButton
          label="Save changes"
          disabled={this.state.saving || !this.state.loaded}
          onclick={async () => {
            const { cluster, iconUrl, mint } = this.state.form;
            this.state.saving = true;
            mixpanelBrowserTrack({
              event: MixpanelCommunityCreationEvent.CREATE_COMMUNITY_ATTEMPTED,
              chainBase: null,
              isCustomDomain: app.isCustomDomain(),
              communityType: null,
            });
            try {
              const res = await $.post(`${app.serverUrl()}/createChain`, {
                address: mint,
                base: ChainBase.Solana,
                icon_url: iconUrl,
                jwt: app.user.jwt,
                network: ChainNetwork.SPL,
                node_url: cluster,
                type: ChainType.Token,
                ...this.state.form,
              });
              await initAppState(false);
              m.route.set(`/${res.result.chain?.id}`);
            } catch (err) {
              notifyError(
                err.responseJSON?.error || 'Creating new ERC20 community failed'
              );
            } finally {
              this.state.saving = false;
            }
          }}
        />
      </div>
    );
  }
Example #27
Source File: eth_dao_form.tsx    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
view(vnode) {
    const validAddress = isAddress(this.state.form.address);
    const disableField = !validAddress || !this.state.loaded;

    const updateDAO = async () => {
      if (
        !this.state.form.address ||
        !this.state.form.ethChainId ||
        !this.state.form.nodeUrl
      )
        return;
      this.state.loading = true;
      this.state.status = '';
      this.state.error = '';
      try {
        if (this.state.form.network === ChainNetwork.Compound) {
          const provider = new Web3.providers.WebsocketProvider(
            this.state.form.nodeUrl
          );
          const compoundApi = new CompoundAPI(
            null,
            this.state.form.address,
            provider
          );
          await compoundApi.init(this.state.form.tokenName);
          if (!compoundApi.Token) {
            throw new Error(
              'Could not find governance token. Is "Token Name" field valid?'
            );
          }
          const govType = GovernorType[compoundApi.govType];
          const tokenType = GovernorTokenType[compoundApi.tokenType];
          this.state.status = `Found ${govType} with token type ${tokenType}`;
        } else if (this.state.form.network === ChainNetwork.Aave) {
          const provider = new Web3.providers.WebsocketProvider(
            this.state.form.nodeUrl
          );
          const aaveApi = new AaveApi(
            IAaveGovernanceV2__factory.connect,
            this.state.form.address,
            provider
          );
          await aaveApi.init();
          this.state.status = `Found Aave type DAO`;
        } else {
          throw new Error('invalid chain network');
        }
      } catch (e) {
        this.state.error = e.message;
        this.state.loading = false;
        m.redraw();
        return;
      }
      this.state.loaded = true;
      this.state.loading = false;
      m.redraw();
    };

    return (
      <div class="CreateCommunityForm">
        {...ethChainRows(vnode.attrs, this.state.form)}
        <SelectRow
          title="DAO Type"
          options={[ChainNetwork.Aave, ChainNetwork.Compound]}
          value={this.state.form.network}
          onchange={(value) => {
            this.state.form.network = value;
            this.state.loaded = false;
          }}
        />
        {this.state.form.network === ChainNetwork.Compound && (
          <InputRow
            title="Token Name (Case Sensitive)"
            defaultValue={this.state.form.tokenName}
            onChangeHandler={(v) => {
              this.state.form.tokenName = v;
              this.state.loaded = false;
            }}
          />
        )}
        <CWButton
          label="Test contract"
          disabled={
            this.state.saving ||
            !validAddress ||
            !this.state.form.ethChainId ||
            this.state.loading
          }
          onclick={async () => {
            await updateDAO();
          }}
        />
        <ValidationRow error={this.state.error} status={this.state.status} />
        <InputRow
          title="Name"
          defaultValue={this.state.form.name}
          disabled={disableField}
          onChangeHandler={(v) => {
            this.state.form.name = v;
            this.state.form.id = slugifyPreserveDashes(v);
          }}
        />
        <IdRow id={this.state.form.id} />
        <InputRow
          title="Symbol"
          disabled={disableField}
          defaultValue={this.state.form.symbol}
          placeholder="XYZ"
          onChangeHandler={(v) => {
            this.state.form.symbol = v;
          }}
        />
        {...defaultChainRows(this.state.form, disableField)}
        <CWButton
          label="Save changes"
          disabled={this.state.saving || !validAddress || !this.state.loaded}
          onclick={async () => {
            const { chainString, ethChainId, nodeUrl, tokenName } =
              this.state.form;
            this.state.saving = true;
            mixpanelBrowserTrack({
              event: MixpanelCommunityCreationEvent.CREATE_COMMUNITY_ATTEMPTED,
              chainBase: null,
              isCustomDomain: app.isCustomDomain(),
              communityType: null,
            });
            try {
              const res = await $.post(`${app.serverUrl()}/createChain`, {
                base: ChainBase.Ethereum,
                chain_string: chainString,
                eth_chain_id: ethChainId,
                jwt: app.user.jwt,
                node_url: nodeUrl,
                token_name: tokenName,
                type: ChainType.DAO,
                ...this.state.form,
              });
              await initAppState(false);
              // TODO: notify about needing to run event migration
              m.route.set(`/${res.result.chain?.id}`);
            } catch (err) {
              notifyError(
                err.responseJSON?.error ||
                  'Creating new ETH DAO community failed'
              );
            } finally {
              this.state.saving = false;
            }
          }}
        />
      </div>
    );
  }
Example #28
Source File: erc721_form.tsx    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
view(vnode) {
    const validAddress = isAddress(this.state.form.address);
    const disableField = !validAddress || !this.state.loaded;

    const updateTokenForum = async () => {
      if (!this.state.form.address || !this.state.form.ethChainId) return;
      this.state.status = '';
      this.state.error = '';
      this.state.loading = true;
      const args = {
        address: this.state.form.address,
        chain_id: this.state.form.ethChainId,
        chain_network: ChainNetwork.ERC721,
        url: this.state.form.nodeUrl,
        allowUncached: true,
      };
      try {
        console.log('Querying backend for token data');
        const res = await $.get(`${app.serverUrl()}/getTokenForum`, args);
        if (res.status === 'Success') {
          if (res?.token?.name) {
            this.state.form.name = res.token.name || '';
            this.state.form.id = res.token.id && slugify(res.token.id);
            this.state.form.symbol = res.token.symbol || '';
            this.state.form.iconUrl = res.token.icon_url || '';
            if (this.state.form.iconUrl.startsWith('/')) {
              this.state.form.iconUrl = `https://commonwealth.im${this.state.form.iconUrl}`;
            }
            this.state.form.description = res.token.description || '';
            this.state.form.website = res.token.website || '';
            this.state.form.discord = res.token.discord || '';
            this.state.form.element = res.token.element || '';
            this.state.form.telegram = res.token.telegram || '';
            this.state.form.github = res.token.github || '';
            this.state.status = 'Success!';
          } else {
            // attempt to query ERC721Detailed token info from chain
            console.log('Querying chain for ERC info');
            const provider = new Web3.providers.WebsocketProvider(args.url);
            try {
              const ethersProvider = new providers.Web3Provider(provider);
              const contract = IERC721Metadata__factory.connect(
                args.address,
                ethersProvider
              );
              const name = await contract.name();
              const symbol = await contract.symbol();
              const decimals = await contract.decimals();
              this.state.form.name = name || '';
              this.state.form.id = name && slugify(name);
              this.state.form.symbol = symbol || '';
              this.state.status = 'Success!';
            } catch (e) {
              this.state.form.name = '';
              this.state.form.id = '';
              this.state.form.symbol = '';
              this.state.status = 'Verified token but could not load metadata.';
            }
            this.state.form.iconUrl = '';
            this.state.form.description = '';
            this.state.form.website = '';
            this.state.form.discord = '';
            this.state.form.element = '';
            this.state.form.telegram = '';
            this.state.form.github = '';
            provider.disconnect(1000, 'finished');
          }
          this.state.loaded = true;
        } else {
          this.state.error = res.message || 'Failed to load Token Information';
        }
      } catch (err) {
        this.state.error =
          err.responseJSON?.error || 'Failed to load Token Information';
      }
      this.state.loading = false;
      m.redraw();
    };

    return (
      <div class="CreateCommunityForm">
        {...ethChainRows(vnode.attrs, this.state.form)}
        <CWButton
          label="Populate fields"
          disabled={
            this.state.saving ||
            !validAddress ||
            !this.state.form.ethChainId ||
            this.state.loading
          }
          onclick={async () => {
            await updateTokenForum();
          }}
        />
        <ValidationRow error={this.state.error} status={this.state.status} />
        <InputRow
          title="Name"
          defaultValue={this.state.form.name}
          disabled={disableField}
          onChangeHandler={(v) => {
            this.state.form.name = v;
            this.state.form.id = slugifyPreserveDashes(v);
          }}
        />
        <IdRow id={this.state.form.id} />
        <InputRow
          title="Symbol"
          disabled={disableField}
          defaultValue={this.state.form.symbol}
          placeholder="XYZ"
          onChangeHandler={(v) => {
            this.state.form.symbol = v;
          }}
        />
        {...defaultChainRows(this.state.form, disableField)}
        <CWButton
          label="Save changes"
          disabled={this.state.saving || !validAddress || !this.state.loaded}
          onclick={async () => {
            const { altWalletUrl, chainString, ethChainId, nodeUrl } =
              this.state.form;
            this.state.saving = true;
            mixpanelBrowserTrack({
              event: MixpanelCommunityCreationEvent.CREATE_COMMUNITY_ATTEMPTED,
              chainBase: null,
              isCustomDomain: app.isCustomDomain(),
              communityType: null,
            });

            try {
              const res = await $.post(`${app.serverUrl()}/createChain`, {
                alt_wallet_url: altWalletUrl,
                base: ChainBase.Ethereum,
                chain_string: chainString,
                eth_chain_id: ethChainId,
                jwt: app.user.jwt,
                network: ChainNetwork.ERC721,
                node_url: nodeUrl,
                type: ChainType.Token,
                ...this.state.form,
              });
              await initAppState(false);
              m.route.set(`/${res.result.chain?.id}`);
            } catch (err) {
              notifyError(
                err.responseJSON?.error ||
                  'Creating new ERC721 community failed'
              );
            } finally {
              this.state.saving = false;
            }
          }}
        />
      </div>
    );
  }
Example #29
Source File: erc20_form.tsx    From commonwealth with GNU General Public License v3.0 4 votes vote down vote up
view(vnode) {
    const validAddress = isAddress(this.state.form.address);
    const disableField = !validAddress || !this.state.loaded;

    const updateTokenForum = async () => {
      if (!this.state.form.address || !this.state.form.ethChainId) return;
      this.state.status = '';
      this.state.error = '';
      this.state.loading = true;
      const args = {
        address: this.state.form.address,
        chain_id: this.state.form.ethChainId,
        chain_network: ChainNetwork.ERC20,
        url: this.state.form.nodeUrl,
        allowUncached: true,
      };
      try {
        console.log('Querying backend for token data');
        const res = await $.get(`${app.serverUrl()}/getTokenForum`, args);
        if (res.status === 'Success') {
          if (res?.token?.name) {
            this.state.form.name = res.token.name || '';
            this.state.form.id = res.token.id && slugify(res.token.id);
            this.state.form.symbol = res.token.symbol || '';
            this.state.form.decimals = +res.token.decimals || 18;
            this.state.form.iconUrl = res.token.icon_url || '';
            if (this.state.form.iconUrl.startsWith('/')) {
              this.state.form.iconUrl = `https://commonwealth.im${this.state.form.iconUrl}`;
            }
            this.state.form.description = res.token.description || '';
            this.state.form.website = res.token.website || '';
            this.state.form.discord = res.token.discord || '';
            this.state.form.element = res.token.element || '';
            this.state.form.telegram = res.token.telegram || '';
            this.state.form.github = res.token.github || '';
            this.state.status = 'Success!';
          } else {
            // attempt to query ERC20Detailed token info from chain
            console.log('Querying chain for ERC info');
            const provider = new Web3.providers.WebsocketProvider(args.url);
            try {
              const ethersProvider = new providers.Web3Provider(provider);
              const contract = IERC20Metadata__factory.connect(
                args.address,
                ethersProvider
              );
              const name = await contract.name();
              const symbol = await contract.symbol();
              const decimals = await contract.decimals();
              this.state.form.name = name || '';
              this.state.form.id = name && slugify(name);
              this.state.form.symbol = symbol || '';
              this.state.form.decimals = decimals || 18;
              this.state.status = 'Success!';
            } catch (e) {
              this.state.form.name = '';
              this.state.form.id = '';
              this.state.form.symbol = '';
              this.state.form.decimals = 18;
              this.state.status = 'Verified token but could not load metadata.';
            }
            this.state.form.iconUrl = '';
            this.state.form.description = '';
            this.state.form.website = '';
            this.state.form.discord = '';
            this.state.form.element = '';
            this.state.form.telegram = '';
            this.state.form.github = '';
            provider.disconnect(1000, 'finished');
          }
          this.state.loaded = true;
        } else {
          this.state.error = res.message || 'Failed to load Token Information';
        }
      } catch (err) {
        this.state.error =
          err.responseJSON?.error || 'Failed to load Token Information';
      }
      this.state.loading = false;
      m.redraw();
    };

    return (
      <div class="CreateCommunityForm">
        {...ethChainRows(vnode.attrs, this.state.form)}
        <CWButton
          label="Populate fields"
          disabled={
            this.state.saving ||
            !validAddress ||
            !this.state.form.ethChainId ||
            this.state.loading
          }
          onclick={async () => {
            await updateTokenForum();
          }}
        />
        <ValidationRow error={this.state.error} status={this.state.status} />
        <InputRow
          title="Name"
          defaultValue={this.state.form.name}
          disabled={disableField}
          onChangeHandler={(v) => {
            this.state.form.name = v;
            this.state.form.id = slugifyPreserveDashes(v);
          }}
        />
        <IdRow id={this.state.form.id} />
        <InputRow
          title="Symbol"
          disabled={disableField}
          defaultValue={this.state.form.symbol}
          placeholder="XYZ"
          onChangeHandler={(v) => {
            this.state.form.symbol = v;
          }}
        />
        {...defaultChainRows(this.state.form, disableField)}
        <CWButton
          label="Save changes"
          disabled={this.state.saving || !validAddress || !this.state.loaded}
          onclick={async () => {
            const { altWalletUrl, chainString, ethChainId, nodeUrl } =
              this.state.form;
            this.state.saving = true;
            mixpanelBrowserTrack({
              event: MixpanelCommunityCreationEvent.CREATE_COMMUNITY_ATTEMPTED,
              chainBase: null,
              isCustomDomain: app.isCustomDomain(),
              communityType: null,
            });
            try {
              const res = await $.post(`${app.serverUrl()}/createChain`, {
                alt_wallet_url: altWalletUrl,
                base: ChainBase.Ethereum,
                chain_string: chainString,
                eth_chain_id: ethChainId,
                jwt: app.user.jwt,
                network: ChainNetwork.ERC20,
                node_url: nodeUrl,
                type: ChainType.Token,
                ...this.state.form,
              });
              await initAppState(false);
              m.route.set(`/${res.result.chain?.id}`);
            } catch (err) {
              notifyError(
                err.responseJSON?.error || 'Creating new ERC20 community failed'
              );
            } finally {
              this.state.saving = false;
            }
          }}
        />
      </div>
    );
  }