@polkadot/types/interfaces#BalanceOf TypeScript Examples

The following examples show how to use @polkadot/types/interfaces#BalanceOf. 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: useInfo.ts    From crust-apps with Apache License 2.0 6 votes vote down vote up
optGiltInfo = {
  defaultValue: {} as GiltInfo,
  transform: ([activeTotal, queueTotals]: [ActiveGiltsTotal, [u32, BalanceOf][]]): GiltInfo => ({
    activeIndex: activeTotal.index.isZero()
      ? null
      : activeTotal.index.sub(BN_ONE),
    activeTotal,
    queueTotals: queueTotals
      .map(([numItems, balance], index): QueueTotal => ({ balance, index: index + 1, numItems }))
      .filter(({ balance }) => !balance.isZero())
  })
}
Example #2
Source File: Address.tsx    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
async function queryForProxy(
  api: ApiPromise,
  allAccounts: string[],
  address: string,
  tx: SubmittableExtrinsic<'promise'>
): Promise<ProxyState | null> {
  if (isFunction(api.query.proxy?.proxies)) {
    const { isProxied } = extractExternal(address);
    const [_proxies] = await api.query.proxy.proxies<
      ITuple<[Vec<ITuple<[AccountId, ProxyType]> | ProxyDefinition>, BalanceOf]>
    >(address);
    const proxies =
      api.tx.proxy.addProxy.meta.args.length === 3
        ? (_proxies as ProxyDefinition[]).map(({ delegate, proxyType }): [string, ProxyType] => [
            delegate.toString(),
            proxyType,
          ])
        : (_proxies as [AccountId, ProxyType][]).map(([delegate, proxyType]): [string, ProxyType] => [
            delegate.toString(),
            proxyType,
          ]);
    const proxiesFilter = filterProxies(allAccounts, tx, proxies);

    if (proxiesFilter.length) {
      return { address, isProxied, proxies, proxiesFilter };
    }
  }

  return null;
}
Example #3
Source File: treasury.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
public async init(ChainInfo: SubstrateChain, Accounts: SubstrateAccounts): Promise<void> {
    this._disabled = !ChainInfo.api.query.treasury;
    if (this._initializing || this._initialized || this.disabled) return;
    this._initializing = true;
    this._Chain = ChainInfo;
    this._Accounts = Accounts;

    // load server proposals
    const entities = this.app.chain.chainEntities.store.getByType(SubstrateTypes.EntityKind.TreasuryProposal);
    entities.forEach((e) => this._entityConstructor(e));

    // save parameters
    this._bondPct = +(ChainInfo.api.consts.treasury.proposalBond as Permill) / 1_000_000;
    this._bondMinimum = this._Chain.coins(ChainInfo.api.consts.treasury.proposalBondMinimum as BalanceOf);
    this._spendPeriod = +(ChainInfo.api.consts.treasury.spendPeriod as BlockNumber);
    this._burnPct = +(ChainInfo.api.consts.treasury.burn as Permill) / 1_000_000;

    const TREASURY_ACCOUNT = u8aToHex(stringToU8a('modlpy/trsry'.padEnd(32, '\0')));
    const pot = await ChainInfo.api.derive.balances.account(TREASURY_ACCOUNT);
    this._pot = this._Chain.coins(pot.freeBalance);

    // register new chain-event handlers
    this.app.chain.chainEntities.registerEntityHandler(
      SubstrateTypes.EntityKind.TreasuryProposal, (entity, event) => {
        this.updateProposal(entity, event);
      }
    );

    // fetch proposals from chain
    await this.app.chain.chainEntities.fetchEntities(
      this.app.chain.id,
      chainToEventNetwork(this.app.chain.meta),
      () => this._Chain.fetcher.fetchTreasuryProposals(this.app.chain.block.height),
    );

    this._initialized = true;
    this._initializing = false;
  }
Example #4
Source File: democracy_proposal.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
public updateVoters = async () => {
    const depositOpt = await this._Chain.api.query.democracy.depositOf(this.data.index);
    if (!depositOpt.isSome) return;
    const depositorsTuple: ITuple<[ BalanceOf | Vec<AccountId>, BalanceOf | Vec<AccountId> ]> = depositOpt.unwrap();
    let depositors: Vec<AccountId>;
    if (isFunction((depositorsTuple[1] as BalanceOf).mul)) {
      depositors = depositorsTuple[0] as Vec<AccountId>;
    } else {
      depositors = depositorsTuple[1] as Vec<AccountId>;
    }
    for (const depositor of depositors) {
      const acct = this._Accounts.fromAddress(depositor.toString());
      const votes = this.getVotes(acct);
      if (!votes.length) {
        this.addOrUpdateVote(new DepositVote(acct, this._Chain.coins(this.data.deposit)));
      } else {
        // if they second a proposal multiple times, sum up the vote weight
        const vote = new DepositVote(acct, this._Chain.coins(votes[0].deposit.add(this.data.deposit)));
        this.addOrUpdateVote(vote);
      }
    }
  }
Example #5
Source File: Address.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
async function queryForProxy (api: ApiPromise, allAccounts: string[], address: string, tx: SubmittableExtrinsic<'promise'>): Promise<ProxyState | null> {
  if (isFunction(api.query.proxy?.proxies)) {
    const { isProxied } = extractExternal(address);
    const [_proxies] = await api.query.proxy.proxies<ITuple<[Vec<ITuple<[AccountId, ProxyType]> | ProxyDefinition>, BalanceOf]>>(address);
    const proxies = api.tx.proxy.addProxy.meta.args.length === 3
      ? (_proxies as ProxyDefinition[]).map(({ delegate, proxyType }): [string, ProxyType] => [delegate.toString(), proxyType])
      : (_proxies as [AccountId, ProxyType][]).map(([delegate, proxyType]): [string, ProxyType] => [delegate.toString(), proxyType]);
    const proxiesFilter = filterProxies(allAccounts, tx, proxies);

    if (proxiesFilter.length) {
      return { address, isProxied, proxies, proxiesFilter };
    }
  }

  return null;
}
Example #6
Source File: index.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
optExtractCandidates = {
  transform: (entries: [StorageKey<[AccountId]>, Option<ITuple<[BalanceOf, BidKind]>>][]): CandidateSuspend[] =>
    entries
      .filter(([{ args: [accountId] }, opt]) => opt.isSome && accountId)
      .map(([{ args: [accountId] }, opt]) => {
        const [balance, bid] = opt.unwrap();

        return { accountId, balance, bid };
      })
      .sort((a, b) => a.balance.cmp(b.balance))
}
Example #7
Source File: Upcoming.tsx    From crust-apps with Apache License 2.0 6 votes vote down vote up
optMulti = {
  defaultValue: {
    headHex: null,
    leases: null,
    lifecycle: null,
    manager: null
  },
  transform: ([optHead, optGenesis, optLifecycle, optInfo, leases]: [Option<HeadData>, Option<ParaGenesisArgs>, Option<ParaLifecycle>, Option<ParaInfo>, Vec<Option<ITuple<[AccountId, BalanceOf]>>>]): MultiState => ({
    headHex: optHead.isSome
      ? sliceHex(optHead.unwrap())
      : optGenesis.isSome
        ? sliceHex(optGenesis.unwrap().genesisHead)
        : null,
    leases: leases
      ? leases
        .map((optLease, period): LeaseInfo | null => {
          if (optLease.isNone) {
            return null;
          }

          const [accountId, balance] = optLease.unwrap();

          return {
            accountId,
            balance,
            period
          };
        })
        .filter((item): item is LeaseInfo => !!item)
      : [],
    lifecycle: optLifecycle.unwrapOr(null),
    manager: optInfo.isSome
      ? optInfo.unwrap().manager
      : null
  })
}
Example #8
Source File: tokenomics.ts    From community-repo with GNU General Public License v3.0 6 votes vote down vote up
async computeCouncilReward(
    roundNrBlocks: number,
    endHash: Hash
  ): Promise<number> {
    const payoutInterval = Number(
      (
        (await getCouncilPayoutInterval(
          this.api,
          endHash
        )) as Option<BlockNumber>
      ).unwrapOr(0)
    );
    const amountPerPayout = (
      (await getCouncilPayout(this.api, endHash)) as BalanceOf
    ).toNumber();

    const [
      announcingPeriod,
      votingPeriod,
      revealingPeriod,
      termDuration,
    ]: number[] = await getCouncilElectionDurations(this.api, endHash);

    const nrCouncilMembers = ((await getCouncil(this.api, endHash)) as Seats)
      .length;
    const totalCouncilRewardsPerBlock =
      amountPerPayout && payoutInterval
        ? (amountPerPayout * nrCouncilMembers) / payoutInterval
        : 0;

    const councilTermDurationRatio =
      termDuration /
      (termDuration + votingPeriod + revealingPeriod + announcingPeriod);
    const avgCouncilRewardPerBlock =
      councilTermDurationRatio * totalCouncilRewardsPerBlock;

    return avgCouncilRewardPerBlock * roundNrBlocks;
  }
Example #9
Source File: useProxies.ts    From crust-apps with Apache License 2.0 6 votes vote down vote up
export default function useProxies (address?: string | null): State {
  const { api } = useApi();
  const { allAccounts } = useAccounts();
  const mountedRef = useIsMountedRef();
  const [known, setState] = useState<State>(EMPTY_STATE);

  useEffect((): void => {
    setState(EMPTY_STATE);

    address && api.query.proxy &&
      api.query.proxy
        .proxies<ITuple<[Vec<ITuple<[AccountId, ProxyType]> | ProxyDefinition>, BalanceOf]>>(address)
        .then(([_proxies]): void => {
          const proxies = api.tx.proxy.addProxy.meta.args.length === 3
            ? (_proxies as ProxyDefinition[]).map(({ delay, delegate, proxyType }) =>
              createProxy(allAccounts, delegate, proxyType, delay)
            )
            : (_proxies as [AccountId, ProxyType][]).map(([delegate, proxyType]) =>
              createProxy(allAccounts, delegate, proxyType)
            );
          const owned = proxies.filter(({ isOwned }) => isOwned);

          mountedRef.current && setState({
            hasOwned: owned.length !== 0,
            owned,
            proxies
          });
        })
        .catch(console.error);
  }, [allAccounts, api, address, mountedRef]);

  return known;
}
Example #10
Source File: Attest.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function Attest ({ accountId, className, ethereumAddress, onSuccess, statementKind, systemChain }: Props): React.ReactElement<Props> | null {
  const accounts = useAccounts();
  const { t } = useTranslation();
  const { api } = useApi();
  const [claimValue, setClaimValue] = useState<BalanceOf | null>(null);
  const [isBusy, setIsBusy] = useState(false);

  useEffect((): void => {
    if (!ethereumAddress) {
      return;
    }

    setIsBusy(true);

    api.query.claims
      .claims<Option<BalanceOf>>(ethereumAddress)
      .then((claim): void => {
        setClaimValue(claim.unwrapOr(null));
        setIsBusy(false);
      })
      .catch((): void => setIsBusy(false));
  }, [api, ethereumAddress]);

  if (isBusy) {
    return null;
  }

  const hasClaim = claimValue && claimValue.gten(0);
  const statementSentence = getStatement(systemChain, statementKind)?.sentence;

  if (!hasClaim || !statementSentence) {
    return null;
  }

  // Attesting is impossible if the account is not owned.
  if (!accounts.isAccount(accountId)) {
    return (
      <Card isError>
        <div className={className}>
          {t<string>('We found a pre-claim with this Polkadot address. However, attesting requires signing with this account. To continue with attesting, please add this account as an owned account first.')}
          <h3>
            <FormatBalance
              label={t<string>('Account balance:')}
              value={claimValue}
            />
          </h3>
        </div>
      </Card>
    );
  }

  return (
    <Card isSuccess>
      <div className={className}>
        <Statement
          kind={statementKind}
          systemChain={systemChain}
        />
        <h3><FormatBalance label={t<string>('Account balance:')}
          value={claimValue} /></h3>
        <Button.Group>
          <TxButton
            accountId={accountId}
            icon='paper-plane'
            isDisabled={!statementSentence}
            label={t<string>('I agree')}
            onSuccess={onSuccess}
            params={[statementSentence]}
            tx={api.tx.claims.attest}
          />
        </Button.Group>
      </div>
    </Card>
  );
}
Example #11
Source File: phragmen_elections.ts    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
public async init(ChainInfo: SubstrateChain, Accounts: SubstrateAccounts): Promise<void> {
    this._disabled = !ChainInfo.api.query.elections;
    this._Chain = ChainInfo;
    this._Accounts = Accounts;

    const moduleName = ChainInfo.api.consts.elections
      ? 'elections'
      : ChainInfo.api.consts.phragmenElections
        ? 'phragmenElections'
        : 'electionsPhragmen';

    this._candidacyBond = this._Chain.coins(ChainInfo.api.consts[moduleName].candidacyBond as BalanceOf);
    this._votingBond = this._Chain.coins(ChainInfo.api.consts[moduleName].votingBond as BalanceOf);
    this._desiredMembers = +ChainInfo.api.consts[moduleName].desiredMembers;
    this._desiredRunnersUp = +ChainInfo.api.consts[moduleName].desiredRunnersUp;
    this._termDuration = +ChainInfo.api.consts[moduleName].termDuration;
    const members = (await ChainInfo.api.query[moduleName].members()) as Vec<any>;
    const runnersUp = (await ChainInfo.api.query[moduleName].runnersUp()) as Vec<any>;

    this._runnersUp = runnersUp.map((r) => ({
      who: r.who !== undefined ? r.who.toString() : r[0].toString(),
      // TODO: broken on KLP
      score: r.stake ? r.stake.toBn() : r[1].toBn()
    }));
    this._members = members.reduce((ms, r) => {
      const who = r.who !== undefined ? r.who : r[0];
      const bal = r.stake !== undefined ? r.stake : r[1];
      ms[who.toString()] = bal.toBn();
      return ms;
    }, {});

    const currentIndex = +(await ChainInfo.api.query[moduleName].electionRounds<u32>());
    const blockNumber = await ChainInfo.api.derive.chain.bestNumber();
    const termDuration = +ChainInfo.api.consts[moduleName].termDuration;
    const roundStartBlock = Math.floor((+blockNumber) / termDuration) * termDuration;
    const endBlock = roundStartBlock + termDuration;
    const p = {
      identifier: `${currentIndex}`,
      round: currentIndex,
      endBlock,
    };
    this._activeElection = new SubstratePhragmenElection(ChainInfo, Accounts, this, p, moduleName);
    this._initialized = true;
  }
Example #12
Source File: phragmen_election.ts    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
public updateVoters = async () => {
    // first, update candidates
    const candidates  = await this._Chain.api.query[this.moduleName].candidates() as Vec<AccountId>;
    const completed = this !== this._Elections.activeElection;
    this._exposedCandidates = candidates.map((c) => c.toString());
    if (completed) {
      this.complete();
    }

    const votingData: { [voter: string]: PhragmenElectionVote } = {};
    if (this._Chain.api.query[this.moduleName].voting) {
      const voting = await this._Chain.api.query[this.moduleName].voting.entries();
      for (const [ key, data ] of voting as Array<[StorageKey, any]>) {
        const votes = data.votes !== undefined ? data.votes : data[1];
        const stake = data.stake !== undefined ? data.stake : data[0];
        const voter = key.args[0].toString();
        const vote = new PhragmenElectionVote(
          this._Accounts.get(voter),
          votes.map((v) => v.toString()),
          stake ? this._Chain.coins(stake) : this._Chain.coins(0),
        );
        votingData[voter] = vote;
      }
    } else {
      // this branch is for edgeware
      const voting = await this._Chain.api.query[this.moduleName].votesOf();
      const [ voters, votes ] = voting as [ Vec<AccountId>, Vec<Vec<AccountId>> ] & Codec;
      const stakes = await this._Chain.api.queryMulti(
        voters.map((who) => [ this._Chain.api.query[this.moduleName].stakeOf, who ])
      ) as BalanceOf[];

      const voteDataArray = _.zip(voters, votes, stakes) as Array<[ AccountId, Vec<AccountId>, BalanceOf ]>;
      for (const [ voter, voterVotes, stake ] of voteDataArray) {
        const vote = new PhragmenElectionVote(
          this._Accounts.get(voter.toString()),
          voterVotes.map((v) => v.toString()),
          this._Chain.coins(stake)
        );
        votingData[voter.toString()] = vote;
      }
    }

    // first, remove all retracted voters
    for (const currentVoter of this.getVoters()) {
      if (!votingData[currentVoter]) {
        this.removeVote(this._Accounts.get(currentVoter));
      }
    }

    // then, add or update all votes
    for (const vote of Object.values(votingData)) {
      if (!this._Accounts.isZero(vote.account.address) && vote.stake.gtn(0)) {
        this.addOrUpdateVote(vote);
      }
    }
  }
Example #13
Source File: bountyTreasury.ts    From commonwealth with GNU General Public License v3.0 5 votes vote down vote up
public async init(ChainInfo: SubstrateChain, Accounts: SubstrateAccounts): Promise<void> {
    this._disabled = !ChainInfo.api.consts.bounties && !ChainInfo.api.consts.treasury;
    if (this._initializing || this._initialized || this.disabled) return;
    this._initializing = true;
    this._Chain = ChainInfo;
    this._Accounts = Accounts;

    // load server proposals
    const entities = this.app.chain.chainEntities.store.getByType(SubstrateTypes.EntityKind.TreasuryBounty);
    entities.forEach((e) => this._entityConstructor(e));

    // save parameters
    const bountyModule = ChainInfo.api.consts.bounties || ChainInfo.api.consts.treasury;
    this._bountyCuratorDeposit = this._Chain.coins(bountyModule.bountyCuratorDeposit as Permill);
    this._bountyDepositBase = this._Chain.coins(bountyModule.bountyDepositBase as BalanceOf);
    this._bountyDepositPayoutDelay = bountyModule.bountyDepositPayoutDelay as BlockNumber;
    this._bountyValueMinimum = this._Chain.coins(bountyModule.bountyValueMinimum as BalanceOf);

    // kick off subscriptions
    // const TREASURY_ACCOUNT = u8aToHex(stringToU8a('modlpy/trsry'.padEnd(32, '\0')));

    // register new chain-event handlers
    this.app.chain.chainEntities.registerEntityHandler(
      SubstrateTypes.EntityKind.TreasuryBounty, (entity, event) => {
        this.updateProposal(entity, event);
      }
    );

    // fetch proposals from chain
    await this.app.chain.chainEntities.fetchEntities(
      this.app.chain.id,
      chainToEventNetwork(this.app.chain.meta),
      () => this._Chain.fetcher.fetchBounties(this.app.chain.block.height),
    );

    // fetch extra metadata
    // TODO: this should be picked up by the chain-events system
    const extra = await ChainInfo.api.derive.bounties.bounties();
    extra.forEach((b) => {
      const index = b.index.toNumber();
      const bounty = this.store.getByIdentifier(index);
      if (!bounty) {
        console.log('Unexpected missing bounty, on chain but not returned by chain-events');
        return;
      }
      const data = {
        title: b.description,
        // state
        isActive: b.bounty.status.isActive,
        isApproved: b.bounty.status.isApproved,
        isCuratorProposed: b.bounty.status.isCuratorProposed,
        isFunded: b.bounty.status.isFunded,
        isPendingPayout: b.bounty.status.isPendingPayout,
        isProposed: b.bounty.status.isProposed,
        // metadata
        fee: b.bounty.fee,
        curatorDeposit: b.bounty.curatorDeposit,
        bond: b.bounty.bond,
        curator: b.bounty.status.isCuratorProposed ? b.bounty.status.asCuratorProposed?.curator
          : b.bounty.status.isActive ? b.bounty.status.asActive.curator
            : b.bounty.status.isPendingPayout ? b.bounty.status.asPendingPayout.curator : null,
        updateDue: b.bounty.status.isActive ? b.bounty.status.asActive.updateDue : null,
        beneficiary: b.bounty.status.isPendingPayout ? b.bounty.status.asPendingPayout.beneficiary : null,
        unlockAt: b.bounty.status.isPendingPayout ? b.bounty.status.asPendingPayout.unlockAt : null,
      };
      bounty.setStatus(data);
    });

    this._initialized = true;
    this._initializing = false;
  }
Example #14
Source File: Program.ts    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
/**
   * @param program Upload program data
   * @param meta Metadata
   * @returns ProgramId
   * @example
   * ```javascript
   * const code = fs.readFileSync('path/to/program.opt.wasm');
   * const meta = await getWasmMetadata(fs.readFileSync('path/to/program.meta.wasm'));
   * const api = await GearApi.create();
   * const { programId, salt, submitted } = api.program.submit({
   *   code,
   *   initPayload: {field: 'someValue'},
   *   gasLimit: 20_000_000,
   * }, meta)
   * api.program.signAndSend(account, (events) => {
   *   events.forEach(({event}) => console.log(event.toHuman()))
   * })
   * ```
   */
  submit(
    program: {
      code: Buffer | Uint8Array;
      salt?: `0x${string}`;
      initPayload?: string | any;
      gasLimit: u64 | AnyNumber;
      value?: BalanceOf | AnyNumber;
    },
    meta?: Metadata,
    messageType?: string,
  ): { programId: Hex; salt: Hex; submitted: SubmittableExtrinsic<'promise', ISubmittableResult> } {
    const salt = program.salt || randomAsHex(20);
    const code = this.createType.create('bytes', Array.from(program.code)) as Bytes;
    let payload = createPayload(this.createType, messageType || meta?.init_input, program.initPayload, meta);
    try {
      this.submitted = this.api.tx.gear.submitProgram(code, salt, payload, program.gasLimit, program.value || 0);
      const programId = generateProgramId(code, salt);
      return { programId, salt, submitted: this.submitted };
    } catch (error) {
      throw new SubmitProgramError();
    }
  }
Example #15
Source File: MessageReply.ts    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
/**
   * Sends reply message
   * @param message Message parameters
   * @param meta Metadata
   * @param messageType MessageType
   * @returns Submitted result
   * @example
   * ```javascript
   * const api = await GearApi.create()
   * const messageId = '0xd7540ae9da85e33b47276e2cb4efc2f0b58fef1227834f21ddc8c7cb551cced6'
   * api.reply.submit({
   *  replyToId: messageId,
   *  payload: 'Reply message',
   *  gasLimit: 20_000_000
   * }, undefiend, 'String')
   * api.reply.signAndSend(account, (events) => {
   *  events.forEach(({event}) => console.log(event.toHuman()))
   * })
   * ```
   */
  submit(
    message: {
      replyToId: H256 | string;
      payload: string | any;
      gasLimit: u64 | AnyNumber;
      value?: BalanceOf | AnyNumber;
    },
    meta?: Metadata,
    messageType?: string,
  ): SubmittableExtrinsic<'promise', ISubmittableResult> {
    let payload = createPayload(
      this.createType,
      messageType || meta?.async_handle_input || meta?.async_init_input,
      message.payload,
      meta,
    );

    try {
      this.submitted = this.api.tx.gear.sendReply(message.replyToId, payload, message.gasLimit, message.value);
      return this.submitted;
    } catch (error) {
      throw new SendReplyError();
    }
  }
Example #16
Source File: useBounties.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
export function useBounties (): BountyApi {
  const { api } = useApi();
  const bounties = useCall<DeriveBounties>(api.derive.bounties.bounties);
  const bountyIndex = useCall<BountyIndex>((api.query.bounties || api.query.treasury).bountyCount);
  const bestNumber = useBestNumber();
  const constsBase = api.consts.bounties || api.consts.treasury;
  const bountyCuratorDeposit = (constsBase.bountyCuratorDeposit as BalanceOf).toBn();
  const bountyDepositBase = (constsBase.bountyDepositBase as BalanceOf).toBn();
  const bountyValueMinimum = (constsBase.bountyValueMinimum as BalanceOf).toBn();
  const maximumReasonLength = constsBase.maximumReasonLength.toNumber();
  const dataDepositPerByte = (constsBase.dataDepositPerByte as BalanceOf).toBn();
  const bountyUpdatePeriod = constsBase.bountyUpdatePeriod;
  const proposeBounty = (api.tx.bounties || api.tx.treasury).proposeBounty;
  const proposeCurator = (api.tx.bounties || api.tx.treasury).proposeCurator;
  const claimBounty = (api.tx.bounties || api.tx.treasury).claimBounty;
  const acceptCurator = (api.tx.bounties || api.tx.treasury).acceptCurator;
  const approveBounty = (api.tx.bounties || api.tx.treasury).approveBounty;
  const closeBounty = (api.tx.bounties || api.tx.treasury).closeBounty;
  const extendBountyExpiry = (api.tx.bounties || api.tx.treasury).extendBountyExpiry;
  const unassignCurator = (api.tx.bounties || api.tx.treasury).unassignCurator;
  const awardBounty = (api.tx.bounties || api.tx.treasury).awardBounty;

  return {
    acceptCurator,
    approveBounty,
    awardBounty,
    bestNumber,
    bounties,
    bountyCuratorDeposit,
    bountyDepositBase,
    bountyIndex,
    bountyUpdatePeriod,
    bountyValueMinimum,
    claimBounty,
    closeBounty,
    dataDepositPerByte,
    extendBountyExpiry,
    maximumReasonLength,
    proposeBounty,
    proposeCurator,
    unassignCurator
  };
}
Example #17
Source File: Attest.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function Attest ({ accountId, className, ethereumAddress, onSuccess, statementKind, systemChain }: Props): React.ReactElement<Props> | null {
  const accounts = useAccounts();
  const { t } = useTranslation();
  const { api } = useApi();
  const [claimValue, setClaimValue] = useState<BalanceOf | null>(null);
  const [isBusy, setIsBusy] = useState(false);

  useEffect((): void => {
    if (!ethereumAddress) {
      return;
    }

    setIsBusy(true);

    api.query.claims
      .claims<Option<BalanceOf>>(ethereumAddress)
      .then((claim): void => {
        setClaimValue(claim.unwrapOr(null));
        setIsBusy(false);
      })
      .catch((): void => setIsBusy(false));
  }, [api, ethereumAddress]);

  if (isBusy) {
    return null;
  }

  const hasClaim = claimValue && claimValue.gten(0);
  const statementSentence = getStatement(systemChain, statementKind)?.sentence;

  if (!hasClaim || !statementSentence) {
    return null;
  }

  // Attesting is impossible if the account is not owned.
  if (!accounts.isAccount(accountId)) {
    return (
      <Card isError>
        <div className={className}>
          {t<string>('We found a pre-claim with this Polkadot address. However, attesting requires signing with this account. To continue with attesting, please add this account as an owned account first.')}
          <h3>
            <FormatBalance
              label={t<string>('Account balance:')}
              value={claimValue}
            />
          </h3>
        </div>
      </Card>
    );
  }

  return (
    <Card isSuccess>
      <div className={className}>
        <Statement
          kind={statementKind}
          systemChain={systemChain}
        />
        <h3><FormatBalance label={t<string>('Account balance:')}
          value={claimValue} /></h3>
        <Button.Group>
          <TxButton
            accountId={accountId}
            icon='paper-plane'
            isDisabled={!statementSentence}
            label={t<string>('I agree')}
            onSuccess={onSuccess}
            params={[statementSentence]}
            tx={api.tx.claims.attest}
          />
        </Button.Group>
      </div>
    </Card>
  );
}
Example #18
Source File: FundContribute.tsx    From crust-apps with Apache License 2.0 5 votes vote down vote up
function FundContribute ({ cap, className, paraId, raised }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const { hasAccounts } = useAccounts();
  const [isOpen, toggleOpen] = useToggle();
  const [accountId, setAccountId] = useState<string | null>(null);
  const [amount, setAmount] = useState<BN | undefined>();

  // TODO verifier signature

  const remaining = cap.sub(raised);
  const isAmountBelow = !amount || amount.lt(api.consts.crowdloan.minContribution as BalanceOf);
  const isAmountOver = !!(amount && amount.gt(remaining));
  const isAmountError = isAmountBelow || isAmountOver;

  return (
    <>
      <Button
        icon='plus'
        isDisabled={!hasAccounts}
        label={t<string>('Contribute')}
        onClick={toggleOpen}
      />
      {isOpen && (
        <Modal
          className={className}
          header={t<string>('Contribute to fund')}
          size='large'
        >
          <Modal.Content>
            <Modal.Columns hint={t<string>('This account will contribute to the crowdloan.')}>
              <InputAddress
                label={t<string>('contribute from')}
                onChange={setAccountId}
                type='account'
                value={accountId}
              />
            </Modal.Columns>
            <Modal.Columns hint={t<string>('The amount to contribute. Should be less than the remaining value and more than the minimum contribution amount.')}>
              <InputBalance
                autoFocus
                defaultValue={api.consts.crowdloan.minContribution as BalanceOf}
                isError={isAmountError}
                isZeroable={false}
                label={t<string>('contribution')}
                onChange={setAmount}
              />
              {isAmountBelow && (
                <MarkWarning content={t<string>('The amount is less than the minimum allowed contribution of {{value}}', { replace: { value: formatBalance(api.consts.crowdloan.minContribution as BlockNumber) } })} />
              )}
              {isAmountOver && (
                <MarkWarning content={t<string>('The amount is more than the remaining contribution needed {{value}}', { replace: { value: formatBalance(remaining) } })} />
              )}
            </Modal.Columns>
          </Modal.Content>
          <Modal.Actions onCancel={toggleOpen}>
            <TxButton
              accountId={accountId}
              icon='plus'
              isDisabled={isAmountError}
              label={t<string>('Contribute')}
              onStart={toggleOpen}
              params={[paraId, amount, null]}
              tx={api.tx.crowdloan.contribute}
            />
          </Modal.Actions>
        </Modal>
      )}
    </>
  );
}
Example #19
Source File: useOwnEraRewards.ts    From crust-apps with Apache License 2.0 4 votes vote down vote up
export function useOwnEraRewards (maxEras?: number, ownValidators?: StakerState[]): State {
  const { api } = useApi();
  const mountedRef = useIsMountedRef();
  const stashIds = useOwnStashIds();
  const allEras = useCall<EraIndex[]>(api.derive.staking?.erasHistoric);
  const [{ filteredEras, validatorEras }, setFiltered] = useState<Filtered>({ filteredEras: [], validatorEras: [] });
  const [state, setState] = useState<State>({ isLoadingRewards: true, rewardCount: 0 });
  const stakerRewards = useCall<[[string[]], DeriveStakerReward[][]]>(!ownValidators?.length && !!filteredEras.length && stashIds && api.derive.staking?.stakerRewardsMultiEras, [stashIds, filteredEras], { withParams: true });
  const erasPoints = useCall<DeriveEraPoints[]>(!!validatorEras.length && !!filteredEras.length && api.derive.staking._erasPoints, [filteredEras, false]);
  const erasRewards = useCall<DeriveEraRewards[]>(!!validatorEras.length && !!filteredEras.length && api.derive.staking._erasRewards, [filteredEras, false]);
  const stakingOverview = useCall<DeriveStakingOverview>(api.derive.staking.overview);
  const waitingInfo = useCall<DeriveStakingWaiting>(api.derive.staking.waitingInfo);
  const allValidators = stakingOverview && waitingInfo && [...waitingInfo.waiting, ...stakingOverview.nextElected];
  const stakingAccounts = useCall<DeriveStakingAccount[]>(allValidators && api.derive.staking.accounts, [allValidators]);
  const [eraStashExposure, setEraStashExposure] = useState<EraStashExposure[]>([]);
  const [eraStakingPayouts, setEraStakingPayouts] = useState<EraStakingPayout[]>();

  useEffect(() => {
    let unsub: (() => void) | undefined;

    if (allValidators && filteredEras && mountedRef.current) {
      const query: [EraIndex, string][] = [];

      for (const v of allValidators) {
        filteredEras.forEach((era) => query.push([era, v.toString()]));
      }

      const fns: any[] = [
        [api.query.staking.erasStakers.multi as any, query]
      ];

      api.combineLatest<Exposure[]>(fns, ([exposures]): void => {
        const tmp: EraStashExposure[] = [];

        if (Array.isArray(exposures) && mountedRef.current && exposures.length && exposures.length === query.length) {
          for (const [index, a] of query.entries()) {
            tmp.push({
              era: a[0],
              stashId: a[1],
              exposure: exposures[index]
            });
          }

          setEraStashExposure(tmp);
        }
      }).then((_unsub): void => {
        unsub = _unsub;
      }).catch(console.error);
    }

    return (): void => {
      unsub && unsub();
    };
  }, [filteredEras, allValidators]);

  useEffect(() => {
    let unsub: (() => void) | undefined;

    if (filteredEras && mountedRef.current) {
      const query: number[] = [];
      filteredEras.forEach((era) => query.push(era.toNumber()));

      const fns: any[] = [
        [api.query.staking.erasStakingPayout.multi as any, query]
      ];

      api.combineLatest<BalanceOf[]>(fns, ([balanceOfs]): void => {
        const tmp: EraStakingPayout[] = [];
        const result = JSON.parse(JSON.stringify(balanceOfs))

        if (Array.isArray(result) && mountedRef.current && result.length && result.length === query.length) {
          for (const [index, a] of query.entries()) {
            tmp.push({
              era: a,
              hasReward: !!result[index]
            });
          }

          setEraStakingPayouts(tmp);
        }
      }).then((_unsub): void => {
        unsub = _unsub;
      }).catch(console.error);
    }

    return (): void => {
      unsub && unsub();
    };
  }, [filteredEras]);

  useEffect((): void => {
    setState({ allRewards: null, isLoadingRewards: true, rewardCount: 0 });
  }, [maxEras, ownValidators]);

  useEffect((): void => {
    if (allEras && maxEras) {
      const filteredEras = allEras.slice(-1 * maxEras);
      const validatorEras: ValidatorWithEras[] = [];

      if (allEras.length === 0) {
        setState({
          allRewards: {},
          isLoadingRewards: false,
          rewardCount: 0
        });
        setFiltered({ filteredEras, validatorEras });
      } else if (ownValidators?.length) {
        ownValidators.forEach(({ stakingLedger, stashId }): void => {
          if (stakingLedger) {
            const eras = filteredEras.filter((era) => !stakingLedger.claimedRewards.some((c) => era.eq(c)));

            if (eras.length) {
              validatorEras.push({ eras, stashId });
            }
          }
        });

        // When we have just claimed, we have filtered eras, but no validator eras - set accordingly
        if (filteredEras.length && !validatorEras.length) {
          setState({
            allRewards: {},
            isLoadingRewards: false,
            rewardCount: 0
          });
        }
      }

      setFiltered({ filteredEras, validatorEras });
    }
  }, [allEras, maxEras, ownValidators]);

  useEffect((): void => {
    mountedRef.current && stakerRewards && !ownValidators && stakingAccounts && eraStakingPayouts && setState(
      getRewards(stakerRewards, stakingAccounts, eraStakingPayouts)
    );
  }, [mountedRef, ownValidators, stakerRewards, stakingAccounts, eraStakingPayouts]);

  useEffect((): void => {
    mountedRef && erasPoints && erasRewards && ownValidators && eraStashExposure && eraStakingPayouts && setState(
      getValRewards(api, validatorEras, erasPoints, erasRewards, eraStashExposure, eraStakingPayouts)
    );
  }, [api, erasPoints, erasRewards, mountedRef, ownValidators, validatorEras, eraStashExposure, eraStakingPayouts]);

  return state;
}
Example #20
Source File: Claim.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function Claim ({ accountId, className = '', ethereumAddress, ethereumSignature, ethereumTxHash, isOldClaimProcess, onSuccess, statementKind }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();
  const { api, systemChain } = useApi();
  const [claimValue, setClaimValue] = useState<BN | null>(null);
  const [isBusy, setIsBusy] = useState(false);
  const [claimedAddress, setClaimedAddress] = useState<string | undefined | null>(null);

  useEffect((): void => {
    if (!ethereumTxHash) {
      return;
    }

    setIsBusy(true);

    api.query.claims
      .claims<Option<BalanceOf>>(ethereumTxHash)
      .then((claim): void => {
        const claimOpt = JSON.parse(JSON.stringify(claim));

        if (claimOpt) {
          const claimBalance = new BN(Number(claimOpt[1])?.toString());

          setClaimValue(claimBalance);
          setClaimedAddress(claimOpt[0]);
          setIsBusy(false);
        }
      })
      .catch((): void => setIsBusy(false));
  }, [api, ethereumTxHash]);

  if (!ethereumTxHash || isBusy || !ethereumSignature || !ethereumAddress || !claimedAddress) {
    return null;
  }

  let hasClaim = claimValue && claimValue.gten(0);

  let isSignedAddr = true;

  if (claimedAddress.toString() !== ethereumAddress.toString()) {
    isSignedAddr = false;
    hasClaim = false;
  }

  if (!isSignedAddr) {
    return (<Card
      isError={!isSignedAddr}
    >
      <div className={className}>
        {t<string>('Your Sign account')}
        <h3>{addrToChecksum(ethereumAddress.toString())}</h3>
        <>
          {t<string>('is not samed as Your Transfer account')}
        </>
        <h3>{addrToChecksum(claimedAddress.toString())}</h3>
        {t<string>('Please make sure that the account you transfer in Ethereum is the same as the signature account.')}
      </div>
    </Card>);
  }

  return (
    <Card
      isError={!hasClaim}
      isSuccess={!!hasClaim}
    >
      <div className={className}>
        {t<string>('Your Ethereum account')}
        <h3>{addrToChecksum(ethereumAddress.toString())}</h3>
        {ethereumTxHash !== '' && hasClaim
          ? (
            <>
              {t<string>('has a valid claim for')}
              <h2><FormatBalance value={claimValue} /></h2>
              <Button.Group>
                <TxButton
                  icon='paper-plane'
                  isUnsigned
                  label={t('Claim')}
                  onSuccess={onSuccess}
                  {...constructTx(api, systemChain, accountId, ethereumSignature, statementKind, isOldClaimProcess, ethereumTxHash)}
                />
              </Button.Group>
            </>
          )
          : (
            <>
              {t<string>('does not appear to have a valid claim. Please double check that you have signed the transaction correctly on the correct ETH account.')}
            </>
          )}
      </div>
    </Card>
  );
}
Example #21
Source File: council.ts    From community-repo with GNU General Public License v3.0 4 votes vote down vote up
async function main() {
  // Initialise the provider to connect to the local node
  const provider = new WsProvider('ws://127.0.0.1:9944');

  //If you want to play around on our staging network, go ahead and connect to this staging network instead.
  //const provider = new WsProvider('wss://testnet-rpc-2-singapore.joystream.org');
  
  // Create the API and wait until ready
  const api = await ApiPromise.create({ provider, types })
  // made this example with historical data, so you can check different councils/stages
  const blocks :number[] = [259200, 259201]

  // discounting this voter
  const joystreamVoter = "5CJzTaCp5fuqG7NdJQ6oUCwdmFHKichew8w4RZ3zFHM8qSe6"

  const councils: CouncilData[] = []
  for (let i=0; i<blocks.length; i++) {
    const councilMembers: CouncilMemberData[] = []
    const blockHash = await api.rpc.chain.getBlockHash(blocks[i]) as Hash
    const electionStatus = await api.query.councilElection.stage.at(blockHash) as Option<ElectionStage>
    const electionRound = await api.query.councilElection.round.at(blockHash) as u32
    console.log(`
      at block: ${blocks[i]},
      the election stage was: ${electionStatus.value.toString()},
      of election round: ${electionRound.toNumber()},
    `)
    if (electionStatus.value instanceof ElectionStage) {
      const electionStage = electionStatus.unwrap()
      if (electionStage.value instanceof Announcing) {
        console.log("In 'Announcing' stage - ends at block", electionStage.value.toNumber())
      } else if (electionStage.value instanceof Voting) {
        console.log("In 'Voting' stage - ends at block", electionStage.value.toNumber())
      } else if (electionStage.value instanceof Revealing) {
        console.log("In 'Revealing' stage - ends at block", electionStage.value.toNumber())
      } else {
      }
    }
    const activeCouncil = await api.query.council.activeCouncil.at(blockHash) as Seats
    if (!activeCouncil.isEmpty) {
      const elected: Participant[] = []
      for (let member of activeCouncil) {
        let otherStake = 0
        let jsgStake = 0
        const councilMemberId = await getParticipantAt(api, member.member, blockHash)
        const voters: VoterData[] = []
        elected.push(councilMemberId)
        for (let backer of member.backers) {
          const voterId = await getParticipantAt(api, backer.member, blockHash)
          const voter: VoterData = {
            voterId,
            voterStake: backer.stake.toNumber(),
            stakeRatioExJSGvotes: 0,
            kpiRewardRatio: 0,
          }
          otherStake += backer.stake.toNumber()
          if (backer.member.toString() === joystreamVoter) {
            jsgStake += backer.stake.toNumber()
          }
          voters.push(voter)
        }
        const ownStake = member.stake.toNumber()
        const totalStakeExJSGvotes = member.stake.toNumber() + otherStake - jsgStake
        const totalStake = member.stake.toNumber() + otherStake
        const councilMember: CouncilMemberData = {
          councilMemberId,
          totalStake,
          totalStakeExJSGvotes,
          ownStake,
          otherStake,
          otherStakeExJSGvotes: otherStake - jsgStake,
          stakeRatioExJSGvotes: ownStake/totalStakeExJSGvotes,
          voters,
        }
        councilMembers.push(councilMember)
      }
      let totalStakes = 0
      let totalStakesExJSGvotes = 0
      let ownStakes = 0
      let otherStakes = 0
      let otherStakesExJSGvotes = 0
      
      for (let councilMember of councilMembers) {
        totalStakes += councilMember.totalStake
        totalStakesExJSGvotes += councilMember.totalStakeExJSGvotes
        ownStakes += councilMember.ownStake
        otherStakes += councilMember.otherStake
        otherStakesExJSGvotes += councilMember.otherStakeExJSGvotes
      }
      for (let councilMember of councilMembers) {
        councilMember.kpiRewardRatio = councilMember.ownStake/totalStakesExJSGvotes
        for (let voter of councilMember.voters) {
          if (voter.voterId.accountId != joystreamVoter) {
            voter.stakeRatioExJSGvotes = voter.voterStake/councilMember.totalStakeExJSGvotes
            voter.kpiRewardRatio = voter.voterStake/totalStakesExJSGvotes
          }
        }
      }
      const termEnd = (await api.query.council.termEndsAt.at(blockHash) as BlockNumber).toNumber()
      const announcing = (await api.query.councilElection.announcingPeriod.at(blockHash) as BlockNumber).toNumber()
      const voting = (await api.query.councilElection.votingPeriod.at(blockHash) as BlockNumber).toNumber()
      const revealing = (await api.query.councilElection.votingPeriod.at(blockHash) as BlockNumber).toNumber()
      const term = (await api.query.councilElection.newTermDuration.at(blockHash) as BlockNumber).toNumber()
 
      // this will not always be correct...
      const electedAtBlock = termEnd-term
      const newCouncilStartsAt = termEnd+announcing+voting+revealing
      const electedHash = await api.rpc.chain.getBlockHash(electedAtBlock) as Hash
      const getRewardInterval = await api.query.council.payoutInterval.at(electedHash) as Option<BlockNumber>

      const councilMint = await api.query.council.councilMint.at(electedHash) as MintId
      const mintAtStart = await api.query.minting.mints.at(electedHash,councilMint) as Mint
      const mintCapacityAtStart = mintAtStart.capacity.toNumber()
      
      let rewardInterval = 3600
      if (!(getRewardInterval.value instanceof Null)) {
        rewardInterval = getRewardInterval.unwrap().toNumber()
      }

      const rewardamountPerPayout = (await api.query.council.amountPerPayout.at(electedHash) as BalanceOf).toNumber()
      const expectedIndividualRewards = rewardamountPerPayout*term/rewardInterval

      const council: CouncilData = {
        electionCycle: electionRound.toNumber(),
        electedAtBlock,
        mintCapacityAtStart,
        rewardamountPerPayout,
        rewardInterval,
        termEnd: termEnd,
        expectedIndividualRewards,
        newCouncilStartsAt,
        totalStakes,
        totalStakesExJSGvotes,
        ownStakes,
        otherStakes,
        otherStakesExJSGvotes,
        elected,
        electionData: councilMembers,
      }
      const bestHeight = (await api.derive.chain.bestNumber()).toNumber()
      if (bestHeight>newCouncilStartsAt) {
        const endHash = await api.rpc.chain.getBlockHash(newCouncilStartsAt) as Hash
        const mintAtEnd = await api.query.minting.mints.at(endHash,councilMint) as Mint
        council.mintCapacityAtEnd = mintAtEnd.capacity.toNumber()
        council.councilSpending = mintAtEnd.total_minted.toNumber() - mintAtStart.total_minted.toNumber()
      }
      councils.push(council)
    }
  }
  console.log("councils",JSON.stringify(councils, null, 4))
  api.disconnect()
}
Example #22
Source File: index.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function CSMClaims (): React.ReactElement<Props> {
  const [didCopy, setDidCopy] = useState(false);
  const [ethereumAddress, setEthereumAddress] = useState<string | undefined | null>(null);
  const [signature, setSignature] = useState<EcdsaSignature | null>(null);
  const [step, setStep] = useState<Step>(Step.Account);
  const [accountId, setAccountId] = useState<string | null>(null);
  const { api, systemChain } = useApi();
  const { t } = useTranslation();
  const [statusOpen, setStatusOpen] = useState<boolean>(false);
  const [result, setResult] = useState<string>('');
  const [status, setStatus] = useState<string>('');
  const [ethereumTxHashValid, setEthereumTxHashValid] = useState<boolean>(false);
  const [isBusy, setIsBusy] = useState<boolean>(false);
  const [isValid, setIsValid] = useState(false);
  const [ethereumTxHash, setEthereumTxHash] = useState<string | undefined | null>(null);
  const csmClaimLimit = useCall<BalanceOf>(api.query.claims.csmClaimLimit);

  // This preclaimEthereumAddress holds the result of `api.query.claims.preclaims`:
  // - an `EthereumAddress` when there's a preclaim
  // - null if no preclaim
  // - `PRECLAIMS_LOADING` if we're fetching the results
  const [preclaimEthereumAddress, setPreclaimEthereumAddress] = useState<string | null | undefined | typeof PRECLAIMS_LOADING>(PRECLAIMS_LOADING);
  const isPreclaimed = !!preclaimEthereumAddress && preclaimEthereumAddress !== PRECLAIMS_LOADING;

  // Everytime we change account, reset everything, and check if the accountId
  // has a preclaim.
  useEffect(() => {
    if (!accountId) {
      return;
    }

    setStep(Step.Account);
    setEthereumAddress(null);
    setEthereumTxHash(null);
    setPreclaimEthereumAddress(PRECLAIMS_LOADING);

    if (!api.query.claims || !api.query.claims.preclaims) {
      return setPreclaimEthereumAddress(null);
    }

    api.query.claims
      .preclaims<Option<EthereumAddress>>(accountId)
      .then((preclaim): void => {
        const address = preclaim.unwrapOr(null)?.toString();

        setEthereumAddress(address);
        setPreclaimEthereumAddress(address);
      })
      .catch((): void => setPreclaimEthereumAddress(null));
  }, [accountId, api.query.claims, api.query.claims.preclaims]);

  // Old claim process used `api.tx.claims.claim`, and didn't have attest
  const isOldClaimProcess = !api.tx.claims.claimAttest;

  useEffect(() => {
    if (didCopy) {
      setTimeout((): void => {
        setDidCopy(false);
      }, 1000);
    }
  }, [didCopy]);

  const goToStepAccount = useCallback(() => {
    setStep(Step.Account);
    setEthereumTxHash("");
    setEthereumTxHashValid(false);
  }, []);

  const goToStepSign = useCallback(() => {
    setStep(Step.Sign);
  }, []);

  const goToStepClaim = useCallback(() => {
    setStep(Step.Claim);
  }, []);

  const handleAccountStep = useCallback(async () => {
    setIsBusy(true);
    const result = await httpPost("https://csm-bridge-api.crust.network/csmClaim/" + ethereumTxHash);

    setIsBusy(false);
    setResult(result.statusText);
    setStatus(result.status);

    if (result.code == 200) {
      setStatusOpen(true);
      setEthereumTxHashValid(true);
      goToStepSign();
    } else {
      api.query.claims
        .csmClaims<Option<BalanceOf>>(ethereumTxHash?.toString())
        .then((claim): void => {
          const claimOpt = JSON.parse(JSON.stringify(claim));

          if (claimOpt) {
            api.query.claims
              .csmClaimed<Option<BalanceOf>>(ethereumTxHash?.toString())
              .then((claimed): void => {
                const isClaimed = JSON.parse(JSON.stringify(claimed));

                if (isClaimed) {
                  setStatusOpen(true);
                } else {
                  setStatusOpen(true);
                  setResult('MintClaimSuccess');
                  setStatus('success');
                  setEthereumTxHashValid(true);
                  goToStepSign();
                }
              });
          } else {
            setStatusOpen(true);
          }
        })
        .catch((): void => setIsBusy(false));
    }
  }, [ethereumAddress, goToStepAccount, goToStepClaim, goToStepSign, isPreclaimed, isOldClaimProcess, ethereumTxHash]);

  const onChangeEthereumTxHash = useCallback((hex: string) => {
    let [isValid, value] = convertInput(hex);

    isValid = isValid && (
      length !== -1
        ? value.length === 32
        : value.length !== 0
    );
    setIsValid(isValid);
    setEthereumTxHash(hex.trim());
  }, [ethereumTxHash]);

  function convertInput (value: string): [boolean, Uint8Array] {
    if (value === '0x') {
      return [true, new Uint8Array([])];
    } else if (value.startsWith('0x')) {
      try {
        return [true, hexToU8a(value)];
      } catch (error) {
        return [false, new Uint8Array([])];
      }
    }

    // maybe it is an ss58?
    try {
      return [true, decodeAddress(value)];
    } catch (error) {
      // we continue
    }

    return isAscii(value)
      ? [true, stringToU8a(value)]
      : [value === '0x', new Uint8Array([])];
  }

  // Depending on the account, decide which step to show.
  // const handleAccountStep = useCallback(() => {
  //   if (isPreclaimed) {
  //     goToStepClaim();
  //   } else if (ethereumAddress || isOldClaimProcess) {
  //     goToStepSign();
  //   } else {
  //     setStep(Step.ETHAddress);
  //   }
  // }, [ethereumAddress, goToStepClaim, goToStepSign, isPreclaimed, isOldClaimProcess]);

  const onChangeSignature = useCallback((event: React.SyntheticEvent<Element>) => {
    const { value: signatureJson } = event.target as HTMLInputElement;

    const { ethereumAddress, signature } = recoverFromJSON(signatureJson);

    setEthereumAddress(ethereumAddress?.toString());
    setSignature(signature);
  }, []);

  const onChangeEthereumAddress = useCallback((value: string) => {
    // FIXME We surely need a better check than just a trim

    setEthereumAddress(value.trim());
  }, []);

  const onCopy = useCallback(() => {
    setDidCopy(true);
  }, []);

  // If it's 1/ not preclaimed and 2/ not the old claiming process, fetch the
  // statement kind to sign.
  const statementKind = useCall<StatementKind | null>(!isPreclaimed && !isOldClaimProcess && !!ethereumAddress && api.query.claims.signing, [ethereumAddress], transformStatement);

  const statementSentence = getStatement(systemChain, statementKind)?.sentence || '';

  // const prefix = u8aToString(api.consts.claims.CsmPrefix.toU8a(true));
  const prefix = 'Pay CSMs to the Crust account:'
  const payload = accountId
    ? `${prefix}${u8aToHex(decodeAddress(accountId), -1, false)}${statementSentence}${ethereumTxHash?.substring(2)}`
    : '';
  
  return (
    <main>
      {!isOldClaimProcess && <Warning />}
      <h1>
        <Trans>Claim your <em>CSM</em> tokens</Trans>
      </h1>
      <Columar>
        <Columar.Column>
          <Card withBottomMargin>
            <h3><span style={{"wordWrap": "break-word", "wordBreak": "break-all"}}>{t<string>(`0. Please make sure you have the authority to make signature with the private key of the wallet account `)}<span style={{ 'fontWeight': 'bold' }}>({t<string>('address: ')}<a href='https://etherscan.io/address/0x17a9037cdfb24ffcc13697d03c3bcd4dff34732b' target="_blank">0x17A9037cdFB24FfcC13697d03C3bcd4DFF34732b</a>)</span><span>{t<string>(', using an exchange account to sent a transfer (withdrawal) transaction will be invalidated and cause asset loss.')}</span> <span style={{ 'fontWeight': 'bold', 'color': 'red' }}>{t<string>(` You are responsible for the consequences!`)}</span></span></h3>
            <img style={{'marginLeft': 'auto', 'marginRight': 'auto', 'display': 'block', "width": "150px" }} src={claimPng as string} />
          </Card>
          {(<Card withBottomMargin>
            <h3>{t<string>(`1. Select your {{chain}} account and enter`, {
                replace: {
                  chain: systemChain
                }
              })} <a href='https://rinkeby.etherscan.io/token/0x7a1c61c526dae21c23b50a490c18d04f8077608f'>{t('ERC20 CSM')}</a> {t<string>('transfer tx hash')}, <span>{t<string>(`The remaining claim limit is `)}<span style={{'color': '#ff8812', 'textDecoration': 'underline', 'fontStyle': 'italic'}}>{formatBalance(csmClaimLimit, {  decimals: 12, withUnit: 'CSM' })}</span><span>{t<string>(`, If your claim amount is greater than the claim limit, please wait for the limit update`)}</span></span>
            </h3>

           <InputAddress
              defaultValue={accountId}
              help={t<string>('The account you want to claim to.')}
              isDisabled={ethereumTxHashValid}
              label={t<string>('claim to account')}
              onChange={setAccountId}
              type='account'
            />
            <Input
              autoFocus
              className='full'
              help={t<string>('The Ethereum CSM transfer tx hash (starting by "0x")')}
              isDisabled={ethereumTxHashValid}
              isError={!isValid}
              label={t<string>('Ethereum tx hash')}
              onChange={onChangeEthereumTxHash}
              placeholder={t<string>('0x prefixed hex, e.g. 0x1234 or ascii data')}
              value={ethereumTxHash || ''}
            />
            {(step === Step.Account) && (<Button.Group>
              <Button
                icon='sign-in-alt'
                isBusy={isBusy}
                isDisabled={preclaimEthereumAddress === PRECLAIMS_LOADING || ethereumTxHash === null || ethereumTxHash === '' || !isValid}
                label={preclaimEthereumAddress === PRECLAIMS_LOADING
                  ? t<string>('Loading')
                  : t<string>('Continue')
                }
                onClick={handleAccountStep}
              />
            </Button.Group>)}
            
            <HttpStatus
              isStatusOpen={statusOpen}
              message={result}
              setStatusOpen={setStatusOpen}
              status={status}
            />
          </Card>)}
          {
            // We need to know the ethereuem address only for the new process
            // to be able to know the statement kind so that the users can sign it
            (step >= Step.ETHAddress && !isPreclaimed && !isOldClaimProcess) && (
              <Card withBottomMargin>
                <h3>{t<string>('2. Enter the ETH address from the sale.')}</h3>
                <Input
                  autoFocus
                  className='full'
                  help={t<string>('The the Ethereum address you used during the pre-sale (starting by "0x")')}
                  label={t<string>('Pre-sale ethereum address')}
                  onChange={onChangeEthereumAddress}
                  value={ethereumAddress || ''}
                />
                {(step === Step.ETHAddress) && (
                  <Button.Group>
                    <Button
                      icon='sign-in-alt'
                      isDisabled={!ethereumAddress}
                      label={t<string>('Continue')}
                      onClick={goToStepSign}
                    />
                  </Button.Group>
                )}
              </Card>
            )}
          {(step >= Step.Sign && !isPreclaimed) && (
            <Card>
              <h3>{t<string>('{{step}}. Sign with your ETH address', { replace: { step: isOldClaimProcess ? '2' : '3' } })}</h3>
              {!isOldClaimProcess && (
                <Statement
                  kind={statementKind}
                  systemChain={systemChain}
                />
              )}
              <div>{t<string>('Copy the following string and sign it with the Ethereum account you used during the pre-sale in the wallet of your choice, using the string as the payload, and then paste the transaction signature object below:')}</div>
              <CopyToClipboard
                onCopy={onCopy}
                text={payload}
              >
                <Payload
                  data-for='tx-payload'
                  data-tip
                >
                  {payload}
                </Payload>
              </CopyToClipboard>
              <Tooltip
                place='right'
                text={didCopy ? t<string>('copied') : t<string>('click to copy')}
                trigger='tx-payload'
              />
              <div>{t<string>('Paste the signed message into the field below. The placeholder text is there as a hint to what the message should look like:')}</div>
              <Signature
                onChange={onChangeSignature}
                placeholder={`{\n  "address": "0x ...",\n  "msg": "${prefix}...",\n  "sig": "0x ...",\n  "version": "3",\n  "signer": "..."\n}`}
                rows={10}
              />
              {(step === Step.Sign) && (
                <Button.Group>
                  <Button
                    icon='sign-in-alt'
                    isDisabled={!accountId || !signature}
                    label={t<string>('Confirm claim')}
                    onClick={goToStepClaim}
                  />
                </Button.Group>
              )}
            </Card>
          )}
        </Columar.Column>
        <Columar.Column>
          {(step >= Step.Claim) && (
            isPreclaimed
              ? <AttestDisplay
                accountId={accountId}
                ethereumAddress={ethereumAddress}
                onSuccess={goToStepAccount}
                statementKind={statementKind}
                systemChain={systemChain}
              />
              : <ClaimDisplay
                accountId={accountId}
                ethereumAddress={ethereumAddress}
                ethereumSignature={signature}
                isOldClaimProcess={isOldClaimProcess}
                onSuccess={goToStepAccount}
                statementKind={statementKind}
                ethereumTxHash={ethereumTxHash}
              />
          )}
        </Columar.Column>
      </Columar>
    </main>
  );
}
Example #23
Source File: Identity.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function Identity ({ address, identity }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();
  const { api } = useApi();
  const { isRegistrar, registrars } = useRegistrars();
  const [isJudgementOpen, toggleIsJudgementOpen] = useToggle();
  const subs = useCall<[BalanceOf, AccountId[]]>(api.query.identity?.subsOf, [address])?.[1];

  if (!identity || !identity.isExistent || !api.query.identity?.identityOf) {
    return null;
  }

  return (
    <section>
      <div className='ui--AddressMenu-section ui--AddressMenu-identity'>
        <div className='ui--AddressMenu-sectionHeader'>
          <div>
            <Icon icon='address-card' />
            &nbsp;
            {t<string>('identity')}
          </div>
          <Tag
            color={
              identity.isBad
                ? 'red'
                : identity.isGood
                  ? 'green'
                  : 'yellow'
            }
            isTag={false}
            label={
              <>
                <b>{identity.judgements.length}&nbsp;</b>
                {
                  identity.judgements.length
                    ? identity.isBad
                      ? identity.isErroneous
                        ? t<string>('Erroneous')
                        : t<string>('Low quality')
                      : identity.isKnownGood
                        ? t<string>('Known good')
                        : t<string>('Reasonable')
                    : t<string>('No judgments')
                }
              </>
            }
            size='tiny'
          />
        </div>
        <div>
          <AvatarItem
            icon={
              // This won't work - images are IPFS hashes
              // identity.image
              //   ? <img src={identity.image} />
              //   : <i className='icon user ui--AddressMenu-identityIcon' />
              //
              <Icon
                className='ui--AddressMenu-identityIcon'
                icon='user'
              />
            }
            subtitle={identity.legal}
            title={identity.display}
          />
          <div className='ui--AddressMenu-identityTable'>
            {identity.parent && (
              <div className='tr parent'>
                <div className='th'>{t<string>('parent')}</div>
                <div className='td'>
                  <AddressMini
                    className='parent'
                    isPadded={false}
                    value={identity.parent}
                  />
                </div>
              </div>
            )}
            {identity.email && (
              <div className='tr'>
                <div className='th'>{t<string>('email')}</div>
                <div className='td'>
                  {isHex(identity.email) || !identity.isKnownGood
                    ? identity.email
                    : (
                      <a
                        href={`mailto:${identity.email as string}`}
                        rel='noopener noreferrer'
                        target='_blank'
                      >
                        {identity.email}
                      </a>
                    )}
                </div>
              </div>
            )}
            {identity.web && (
              <div className='tr'>
                <div className='th'>{t<string>('website')}</div>
                <div className='td'>
                  {isHex(identity.web) || !identity.isKnownGood
                    ? identity.web
                    : (
                      <a
                        href={(identity.web as string).replace(/^(https?:\/\/)?/g, 'https://')}
                        rel='noopener noreferrer'
                        target='_blank'
                      >
                        {identity.web}
                      </a>
                    )}
                </div>
              </div>
            )}
            {identity.twitter && (
              <div className='tr'>
                <div className='th'>{t<string>('twitter')}</div>
                <div className='td'>
                  {isHex(identity.twitter) || !identity.isKnownGood
                    ? identity.twitter
                    : (
                      <a
                        href={
                          (identity.twitter as string).startsWith('https://twitter.com/')
                            ? (identity.twitter as string)
                            : `https://twitter.com/${identity.twitter as string}`
                        }
                        rel='noopener noreferrer'
                        target='_blank'
                      >
                        {identity.twitter}
                      </a>
                    )}
                </div>
              </div>
            )}
            {identity.riot && (
              <div className='tr'>
                <div className='th'>{t<string>('riot')}</div>
                <div className='td'>
                  {identity.riot}
                </div>
              </div>
            )}
            {!!subs?.length && (
              <div className='tr subs'>
                {subs.length > 1
                  ? <div className='th top'>{t<string>('subs')}</div>
                  : <div className='th'>{t<string>('sub')}</div>
                }
                <div className='td'>
                  <Expander summary={`(${subs.length})`}>
                    <div className='body column'>
                      {subs.map((sub) =>
                        <AddressMini
                          className='subs'
                          isPadded={false}
                          key={sub.toString()}
                          value={sub}
                        />
                      )}
                    </div>
                  </Expander>
                </div>
              </div>)}
          </div>
        </div>
      </div>
      {isRegistrar && (
        <div className='ui--AddressMenu-section'>
          <div className='ui--AddressMenu-actions'>
            <ul>
              <li>
                <IconLink
                  icon='address-card'
                  label={t<string>('Add identity judgment')}
                  onClick={toggleIsJudgementOpen}
                />
              </li>
            </ul>
          </div>
        </div>
      )}
      {isJudgementOpen && isRegistrar && (
        <RegistrarJudgement
          address={address}
          key='modal-judgement'
          registrars={registrars}
          toggleJudgement={toggleIsJudgementOpen}
        />
      )}
    </section>
  );
}
Example #24
Source File: Claim.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function Claim ({ accountId, className = '', ethereumAddress, ethereumSignature, ethereumTxHash, isOldClaimProcess, onSuccess, statementKind }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();
  const { api, systemChain } = useApi();
  const [claimValue, setClaimValue] = useState<BN | null>(null);
  const [isBusy, setIsBusy] = useState(false);
  const [claimedAddress, setClaimedAddress] = useState<string | undefined | null>(null);

  useEffect((): void => {
    if (!ethereumTxHash) {
      return;
    }

    setIsBusy(true);

    api.query.claims
      .csmClaims<Option<BalanceOf>>(ethereumTxHash)
      .then((claim): void => {
        const claimOpt = JSON.parse(JSON.stringify(claim));

        if (claimOpt) {
          const claimBalance = new BN(Number(claimOpt[1])?.toString());

          setClaimValue(claimBalance);
          setClaimedAddress(claimOpt[0]);
          setIsBusy(false);
        }
      })
      .catch((): void => setIsBusy(false));
  }, [api, ethereumTxHash]);

  if (!ethereumTxHash || isBusy || !ethereumSignature || !ethereumAddress || !claimedAddress) {
    return null;
  }

  let hasClaim = claimValue && claimValue.gten(0);

  let isSignedAddr = true;

  if (claimedAddress.toString() !== ethereumAddress.toString()) {
    isSignedAddr = false;
    hasClaim = false;
  }

  if (!isSignedAddr) {
    return (<Card
      isError={!isSignedAddr}
    >
      <div className={className}>
        {t<string>('Your Sign account')}
        <h3>{addrToChecksum(ethereumAddress.toString())}</h3>
        <>
          {t<string>('is not samed as Your Transfer account')}
        </>
        <h3>{addrToChecksum(claimedAddress.toString())}</h3>
        {t<string>('Please make sure that the account you transfer in Ethereum is the same as the signature account.')}
      </div>
    </Card>);
  }

  return (
    <Card
      isError={!hasClaim}
      isSuccess={!!hasClaim}
    >
      <div className={className}>
        {t<string>('Your Ethereum account')}
        <h3>{addrToChecksum(ethereumAddress.toString())}</h3>
        {ethereumTxHash !== '' && hasClaim
          ? (
            <>
              {t<string>('has a valid claim for')}
              <h2><FormatCsmBalance value={claimValue} /></h2>
              <Button.Group>
                <TxButton
                  icon='paper-plane'
                  isUnsigned
                  label={t('Claim')}
                  onSuccess={onSuccess}
                  {...constructTx(api, systemChain, accountId, ethereumSignature, statementKind, isOldClaimProcess, ethereumTxHash)}
                />
              </Button.Group>
            </>
          )
          : (
            <>
              {t<string>('does not appear to have a valid claim. Please double check that you have signed the transaction correctly on the correct ETH account.')}
            </>
          )}
      </div>
    </Card>
  );
}
Example #25
Source File: index.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function MaxwellClaims (): React.ReactElement<Props> {
  const [didCopy, setDidCopy] = useState(false);
  const [ethereumAddress, setEthereumAddress] = useState<string | undefined | null>(null);
  const [signature, setSignature] = useState<EcdsaSignature | null>(null);
  const [step, setStep] = useState<Step>(Step.Account);
  const [accountId, setAccountId] = useState<string | null>(null);
  const { api, systemChain } = useApi();
  const { t } = useTranslation();
  const [statusOpen, setStatusOpen] = useState<boolean>(false);
  const [result, setResult] = useState<string>('');
  const [status, setStatus] = useState<string>('');
  const [ethereumTxHashValid, setEthereumTxHashValid] = useState<boolean>(false);
  const [isBusy, setIsBusy] = useState<boolean>(false);
  const [isValid, setIsValid] = useState(false);
  const [ethereumTxHash, setEthereumTxHash] = useState<string | undefined | null>(null);

  // This preclaimEthereumAddress holds the result of `api.query.claims.preclaims`:
  // - an `EthereumAddress` when there's a preclaim
  // - null if no preclaim
  // - `PRECLAIMS_LOADING` if we're fetching the results
  const [preclaimEthereumAddress, setPreclaimEthereumAddress] = useState<string | null | undefined | typeof PRECLAIMS_LOADING>(PRECLAIMS_LOADING);
  const isPreclaimed = !!preclaimEthereumAddress && preclaimEthereumAddress !== PRECLAIMS_LOADING;
  const claimLimit = useCall<BalanceOf>(api.query.claims.claimLimit);

  // Everytime we change account, reset everything, and check if the accountId
  // has a preclaim.
  useEffect(() => {
    if (!accountId) {
      return;
    }

    setStep(Step.Account);
    setEthereumAddress(null);
    setEthereumTxHash(null);
    setPreclaimEthereumAddress(PRECLAIMS_LOADING);

    if (!api.query.claims || !api.query.claims.preclaims) {
      return setPreclaimEthereumAddress(null);
    }

    api.query.claims
      .preclaims<Option<EthereumAddress>>(accountId)
      .then((preclaim): void => {
        const address = preclaim.unwrapOr(null)?.toString();

        setEthereumAddress(address);
        setPreclaimEthereumAddress(address);
      })
      .catch((): void => setPreclaimEthereumAddress(null));
  }, [accountId, api.query.claims, api.query.claims.preclaims]);

  // Old claim process used `api.tx.claims.claim`, and didn't have attest
  const isOldClaimProcess = !api.tx.claims.claimAttest;

  useEffect(() => {
    if (didCopy) {
      setTimeout((): void => {
        setDidCopy(false);
      }, 1000);
    }
  }, [didCopy]);

  const goToStepAccount = useCallback(() => {
    setStep(Step.Account);
    setEthereumTxHash("");
    setEthereumTxHashValid(false);
  }, []);

  const goToStepSign = useCallback(() => {
    setStep(Step.Sign);
  }, []);

  const goToStepClaim = useCallback(() => {
    setStep(Step.Claim);
  }, []);

  const handleAccountStep = useCallback(async () => {
    setIsBusy(true);
    const result = await httpPost("https://bridge-api.crust.network/claim/" + ethereumTxHash);

    setIsBusy(false);
    setResult(result.statusText);
    setStatus(result.status);

    if (result.code == 200) {
      setStatusOpen(true);
      setEthereumTxHashValid(true);
      goToStepSign();
    } else {
      api.query.claims
        .claims<Option<BalanceOf>>(ethereumTxHash?.toString())
        .then((claim): void => {
          const claimOpt = JSON.parse(JSON.stringify(claim));

          if (claimOpt) {
            api.query.claims
              .claimed<Option<BalanceOf>>(ethereumTxHash?.toString())
              .then((claimed): void => {
                const isClaimed = JSON.parse(JSON.stringify(claimed));

                if (isClaimed) {
                  setStatusOpen(true);
                } else {
                  setStatusOpen(true);
                  setResult('MintClaimSuccess');
                  setStatus('success');
                  setEthereumTxHashValid(true);
                  goToStepSign();
                }
              });
          } else {
            setStatusOpen(true);
          }
        })
        .catch((): void => setIsBusy(false));
    }
  }, [ethereumAddress, goToStepClaim, goToStepSign, isPreclaimed, isOldClaimProcess, ethereumTxHash]);

  const onChangeEthereumTxHash = useCallback((hex: string) => {
    let [isValid, value] = convertInput(hex);

    isValid = isValid && (
      length !== -1
        ? value.length === 32
        : value.length !== 0
    );
    setIsValid(isValid);
    setEthereumTxHash(hex.trim());
  }, [ethereumTxHash]);

  function convertInput (value: string): [boolean, Uint8Array] {
    if (value === '0x') {
      return [true, new Uint8Array([])];
    } else if (value.startsWith('0x')) {
      try {
        return [true, hexToU8a(value)];
      } catch (error) {
        return [false, new Uint8Array([])];
      }
    }

    // maybe it is an ss58?
    try {
      return [true, decodeAddress(value)];
    } catch (error) {
      // we continue
    }

    return isAscii(value)
      ? [true, stringToU8a(value)]
      : [value === '0x', new Uint8Array([])];
  }

  // Depending on the account, decide which step to show.
  // const handleAccountStep = useCallback(() => {
  //   if (isPreclaimed) {
  //     goToStepClaim();
  //   } else if (ethereumAddress || isOldClaimProcess) {
  //     goToStepSign();
  //   } else {
  //     setStep(Step.ETHAddress);
  //   }
  // }, [ethereumAddress, goToStepClaim, goToStepSign, isPreclaimed, isOldClaimProcess]);

  const onChangeSignature = useCallback((event: React.SyntheticEvent<Element>) => {
    const { value: signatureJson } = event.target as HTMLInputElement;

    const { ethereumAddress, signature } = recoverFromJSON(signatureJson);

    setEthereumAddress(ethereumAddress?.toString());
    setSignature(signature);
  }, []);

  const onChangeEthereumAddress = useCallback((value: string) => {
    // FIXME We surely need a better check than just a trim

    setEthereumAddress(value.trim());
  }, []);

  const onCopy = useCallback(() => {
    setDidCopy(true);
  }, []);

  // If it's 1/ not preclaimed and 2/ not the old claiming process, fetch the
  // statement kind to sign.
  const statementKind = useCall<StatementKind | null>(!isPreclaimed && !isOldClaimProcess && !!ethereumAddress && api.query.claims.signing, [ethereumAddress], transformStatement);

  const statementSentence = getStatement(systemChain, statementKind)?.sentence || '';

  const prefix = u8aToString(api.consts.claims.prefix.toU8a(true));
  const payload = accountId
    ? `${prefix}${u8aToHex(decodeAddress(accountId), -1, false)}${statementSentence}${ethereumTxHash?.substring(2)}`
    : '';

  return (
    <main>
      {!isOldClaimProcess && <Warning />}
      <h1>
        <Trans>Claim your <em>{TokenUnit.abbr}</em> tokens</Trans>
      </h1>
      <Columar>
        <Columar.Column>
          <Card withBottomMargin>
            <h3><span style={{"wordWrap": "break-word", "wordBreak": "break-all"}}>{t<string>(`0. Please make sure you have the authority to make signature with the private key of the wallet account `)}<span style={{ 'fontWeight': 'bold' }}>({t<string>('address: ')}<a href='https://etherscan.io/address/0x17a9037cdfb24ffcc13697d03c3bcd4dff34732b' target="_blank">0x17A9037cdFB24FfcC13697d03C3bcd4DFF34732b</a>)</span><span>{t<string>(', using an exchange account to sent a transfer (withdrawal) transaction will be invalidated and cause asset loss.')}</span> <span style={{ 'fontWeight': 'bold', 'color': 'red' }}>{t<string>(` This is Maxwell's claim. If you want to claim your tokens to the mainnet, please click on the upper left corner to switch to the mainnet, You are responsible for the consequences!`)}</span></span></h3>
            <img style={{'marginLeft': 'auto', 'marginRight': 'auto', 'display': 'block', "width": "150px" }} src={claimPng as string} />
          </Card>
          {(<Card withBottomMargin>
            <h3>{t<string>(`1. Select your {{chain}} account and enter`, {
                replace: {
                  chain: systemChain
                }
              })} <a href='https://etherscan.io/token/0x32a7C02e79c4ea1008dD6564b35F131428673c41'>{t('ERC20 CRU')}</a> {t<string>('transfer tx hash')}, <span>{t<string>(`The remaining claim limit is `)}<span style={{'color': '#ff8812', 'textDecoration': 'underline', 'fontStyle': 'italic'}}>{formatBalance(claimLimit, { withUnit: 'CRU' })}</span><span>{t<string>(`, If your claim amount is greater than the claim limit, please wait for the limit update`)}</span></span> </h3>
            <InputAddress
              defaultValue={accountId}
              help={t<string>('The account you want to claim to.')}
              isDisabled={ethereumTxHashValid}
              label={t<string>('claim to account')}
              onChange={setAccountId}
              type='account'
            />
            <Input
              autoFocus
              className='full'
              help={t<string>('The Ethereum CRU transfer tx hash (starting by "0x")')}
              isDisabled={ethereumTxHashValid}
              isError={!isValid}
              label={t<string>('Ethereum tx hash')}
              onChange={onChangeEthereumTxHash}
              placeholder={t<string>('0x prefixed hex, e.g. 0x1234 or ascii data')}
              value={ethereumTxHash || ''}
            />    
            {(step === Step.Account) && (<Button.Group>
              <Button
                icon='sign-in-alt'
                isBusy={isBusy}
                isDisabled={preclaimEthereumAddress === PRECLAIMS_LOADING || ethereumTxHash === null || ethereumTxHash === '' || !isValid}
                label={preclaimEthereumAddress === PRECLAIMS_LOADING
                  ? t<string>('Loading')
                  : t<string>('Continue')
                }
                onClick={handleAccountStep}
              />
            </Button.Group>)}   
            <HttpStatus
              isStatusOpen={statusOpen}
              message={result}
              setStatusOpen={setStatusOpen}
              status={status}
            />
          </Card>)}
          {
            // We need to know the ethereuem address only for the new process
            // to be able to know the statement kind so that the users can sign it
            (step >= Step.ETHAddress && !isPreclaimed && !isOldClaimProcess) && (
              <Card withBottomMargin>
                <h3>{t<string>('2. Enter the ETH address from the sale.')}</h3>
                <Input
                  autoFocus
                  className='full'
                  help={t<string>('The the Ethereum address you used during the pre-sale (starting by "0x")')}
                  label={t<string>('Pre-sale ethereum address')}
                  onChange={onChangeEthereumAddress}
                  value={ethereumAddress || ''}
                />
                {(step === Step.ETHAddress) && (
                  <Button.Group>
                    <Button
                      icon='sign-in-alt'
                      isDisabled={!ethereumAddress}
                      label={t<string>('Continue')}
                      onClick={goToStepSign}
                    />
                  </Button.Group>
                )}
              </Card>
            )}
          {(step >= Step.Sign && !isPreclaimed) && (
            <Card>
              <h3>{t<string>('{{step}}. Sign with your ETH address', { replace: { step: isOldClaimProcess ? '2' : '3' } })}</h3>
              {!isOldClaimProcess && (
                <Statement
                  kind={statementKind}
                  systemChain={systemChain}
                />
              )}
              <div>{t<string>('Copy the following string and sign it with the Ethereum account you used during the pre-sale in the wallet of your choice, using the string as the payload, and then paste the transaction signature object below:')}</div>
              <CopyToClipboard
                onCopy={onCopy}
                text={payload}
              >
                <Payload
                  data-for='tx-payload'
                  data-tip
                >
                  {payload}
                </Payload>
              </CopyToClipboard>
              <Tooltip
                place='right'
                text={didCopy ? t<string>('copied') : t<string>('click to copy')}
                trigger='tx-payload'
              />
              <div>{t<string>('Paste the signed message into the field below. The placeholder text is there as a hint to what the message should look like:')}</div>
              <Signature
                onChange={onChangeSignature}
                placeholder={`{\n  "address": "0x ...",\n  "msg": "${prefix}...",\n  "sig": "0x ...",\n  "version": "3",\n  "signer": "..."\n}`}
                rows={10}
              />
              {(step === Step.Sign) && (
                <Button.Group>
                  <Button
                    icon='sign-in-alt'
                    isDisabled={!accountId || !signature}
                    label={t<string>('Confirm claim')}
                    onClick={goToStepClaim}
                  />
                </Button.Group>
              )}
            </Card>
          )}
        </Columar.Column>
        <Columar.Column>
          
          {(step >= Step.Claim) && (
            isPreclaimed
              ? <AttestDisplay
                accountId={accountId}
                ethereumAddress={ethereumAddress}
                onSuccess={goToStepAccount}
                statementKind={statementKind}
                systemChain={systemChain}
              />
              : <ClaimDisplay
                accountId={accountId}
                ethereumAddress={ethereumAddress}
                ethereumSignature={signature}
                isOldClaimProcess={isOldClaimProcess}
                onSuccess={goToStepAccount}
                statementKind={statementKind}
                ethereumTxHash={ethereumTxHash}
              />
          )}
        </Columar.Column>
      </Columar>
    </main>
  );
}
Example #26
Source File: PreClaim.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function PreClaim ({ accountId, className = '', ethereumAddress, ethereumSignature, ethereumTxHash, isOldClaimProcess, onSuccess, statementKind }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();
  const { api } = useApi();
  const [claimValue, setClaimValue] = useState<BN | null>(null);
  const [isBusy, setIsBusy] = useState(true);
  const [claimed, setClaimed] = useState(false);

  useEffect((): void => {
    if (!ethereumAddress) {
      return;
    }
    api.query.claims
      .cru18PreClaims<Option<BalanceOf>>(ethereumAddress)
      .then((claim): void => {
        const claimOpt = JSON.parse(JSON.stringify(claim));
        if (claimOpt) {
          const claimBalance = new BN(Number(claimOpt)?.toString());
          api.query.claims
          .cru18Claimed<Option<BalanceOf>>(ethereumAddress)
          .then((claimed): void => {
            const claimedOpt = JSON.parse(JSON.stringify(claimed));
            setIsBusy(false)
            if (claimedOpt) {
              setClaimed(true)
            }
            setClaimValue(claimBalance);
          })
          .catch(() => setIsBusy(false));
        } else {
          setClaimValue(new BN(0));
          setIsBusy(false)
        }
      })
      .catch(() => setIsBusy(false));
  }, [api, ethereumSignature, ethereumAddress]);

  // if (!ethereumTxHash || isBusy || !ethereumSignature || !ethereumAddress || !claimedAddress) {
  //   return null;
  // }

  let hasClaim = claimValue && claimValue.gtn(0);

  if (!ethereumAddress || isBusy) {
    return null
  }

  return (
    !claimed ? 
    <Card
      isError={!hasClaim}
      isSuccess={!!hasClaim}
    >
      <div className={className}>
        {t<string>('Your Ethereum account')}
        <h3>{addrToChecksum(ethereumAddress.toString())}</h3>
        {hasClaim
          ? (
            <>
              {t<string>('has a valid claim for')}
              <h2>{formatBalance(new BN(Number(claimValue).toString()), {decimals: 12, withUnit: 'CRU18'})}</h2>
              <Button.Group>
                <TxButton
                  icon='paper-plane'
                  isUnsigned
                  label={t('Claim')}
                  onSuccess={onSuccess}
                  {...constructTx(api, accountId, ethereumSignature, statementKind, isOldClaimProcess, ethereumTxHash)}
                />
              </Button.Group>
            </>
          )
          : (
            <>
              {t<string>('does not appear to have a valid CRU18 claim. Please double check that you have signed the transaction correctly on the correct ETH account.')}
            </>
          )}
      </div>
    </Card> : <Card
      isError={claimed}
    >
      <div className={className}>
        {t<string>('Your Ethereum account')}
        <h3>{addrToChecksum(ethereumAddress.toString())}</h3>
        {t<string>('has already been claimed')}
      </div>
    </Card> 
  );
}
Example #27
Source File: index.tsx    From crust-apps with Apache License 2.0 4 votes vote down vote up
function Claims(): React.ReactElement<Props> {
  const [didCopy, setDidCopy] = useState(false);
  const [ethereumAddress, setEthereumAddress] = useState<string | undefined | null>(null);
  const [signature, setSignature] = useState<EcdsaSignature | null>(null);
  const [step, setStep] = useState<Step>(Step.Account);
  const [accountId, setAccountId] = useState<string | null>(null);
  const { api, systemChain } = useApi();
  const { t } = useTranslation();
  const [statusOpen, setStatusOpen] = useState<boolean>(false);
  const [result, setResult] = useState<string>('');
  const [status, setStatus] = useState<string>('');
  const [ethereumTxHashValid, setEthereumTxHashValid] = useState<boolean>(false);
  const [isBusy, setIsBusy] = useState<boolean>(false);
  const [isValid, setIsValid] = useState(false);
  const [ethereumTxHash, setEthereumTxHash] = useState<string | undefined | null>(null);

  // This preclaimEthereumAddress holds the result of `api.query.claims.preclaims`:
  // - an `EthereumAddress` when there's a preclaim
  // - null if no preclaim
  // - `PRECLAIMS_LOADING` if we're fetching the results
  const [preclaimEthereumAddress, setPreclaimEthereumAddress] = useState<string | null | undefined | typeof PRECLAIMS_LOADING>(PRECLAIMS_LOADING);
  const isPreclaimed = !!preclaimEthereumAddress && preclaimEthereumAddress !== PRECLAIMS_LOADING;
  const claimLimit = useCall<BalanceOf>(api.query.claims.claimLimit);

  // Everytime we change account, reset everything, and check if the accountId
  // has a preclaim.
  useEffect(() => {
    if (!accountId) {
      return;
    }

    setStep(Step.Account);
    setEthereumAddress(null);
    setEthereumTxHash(null);
    setPreclaimEthereumAddress(PRECLAIMS_LOADING);

    if (!api.query.claims || !api.query.claims.preclaims) {
      return setPreclaimEthereumAddress(null);
    }

    api.query.claims
      .preclaims<Option<EthereumAddress>>(accountId)
      .then((preclaim): void => {
        const address = preclaim.unwrapOr(null)?.toString();

        setEthereumAddress(address);
        setPreclaimEthereumAddress(address);
      })
      .catch((): void => setPreclaimEthereumAddress(null));
  }, [accountId, api.query.claims, api.query.claims.preclaims]);

  // Old claim process used `api.tx.claims.claim`, and didn't have attest
  const isOldClaimProcess = !api.tx.claims.claimAttest;

  useEffect(() => {
    if (didCopy) {
      setTimeout((): void => {
        setDidCopy(false);
      }, 1000);
    }
  }, [didCopy]);

  const goToStepAccount = useCallback(() => {
    setStep(Step.Account);
    setEthereumTxHash("");
    setEthereumTxHashValid(false);
  }, []);

  const goToStepSign = useCallback(() => {
    setStep(Step.Sign);
  }, []);

  const goToStepClaim = useCallback(() => {
    setStep(Step.Claim);
  }, []);

  const handleAccountStep = useCallback(async () => {
    setIsBusy(true);

    api.query.claims
      .claimed<Option<BalanceOf>>(ethereumTxHash?.toString())
      .then(async (claim): Promise<void> => {
        const isClaimed = JSON.parse(JSON.stringify(claim));

        if (isClaimed) {
          setResult('AlreadyClaimed');
          setStatus('error');
          setStatusOpen(true);
        } else {
          const result = await httpPost("https://claim.crust.network/claim/" + ethereumTxHash);
          setIsBusy(false);
          setResult(result.statusText);
          setStatus(result.status);

          if (result.code == 200) {
            setEthereumTxHashValid(true);
            goToStepSign();
          } else {
            api.query.claims
              .claims<Option<BalanceOf>>(ethereumTxHash?.toString())
              .then(async (claim): Promise<void> => {
                const claimOpt = JSON.parse(JSON.stringify(claim));
                if (claimOpt) {
                  setResult('MintClaimSuccess');
                  setStatus('success');
                  setEthereumTxHashValid(true);
                  goToStepSign();
                } else {
                  setResult('MintError, Please try again');
                  setStatus('error');
                }
              })
              .catch((): void => setIsBusy(false));
          }
          setStatusOpen(true);
        }
      })
      .catch((): void => setIsBusy(false))
      .finally(() =>  setIsBusy(false));
  }, [ethereumAddress, goToStepClaim, goToStepSign, isPreclaimed, isOldClaimProcess, ethereumTxHash]);

  const onChangeEthereumTxHash = useCallback((hex: string) => {
    let [isValid, value] = convertInput(hex);

    isValid = isValid && (
      length !== -1
        ? value.length === 32
        : value.length !== 0
    );
    setIsValid(isValid);
    setEthereumTxHash(hex.trim());
  }, [ethereumTxHash]);

  function convertInput(value: string): [boolean, Uint8Array] {
    if (value === '0x') {
      return [true, new Uint8Array([])];
    } else if (value.startsWith('0x')) {
      try {
        return [true, hexToU8a(value)];
      } catch (error) {
        return [false, new Uint8Array([])];
      }
    }

    // maybe it is an ss58?
    try {
      return [true, decodeAddress(value)];
    } catch (error) {
      // we continue
    }

    return isAscii(value)
      ? [true, stringToU8a(value)]
      : [value === '0x', new Uint8Array([])];
  }

  // Depending on the account, decide which step to show.
  // const handleAccountStep = useCallback(() => {
  //   if (isPreclaimed) {
  //     goToStepClaim();
  //   } else if (ethereumAddress || isOldClaimProcess) {
  //     goToStepSign();
  //   } else {
  //     setStep(Step.ETHAddress);
  //   }
  // }, [ethereumAddress, goToStepClaim, goToStepSign, isPreclaimed, isOldClaimProcess]);

  const onChangeSignature = useCallback((event: React.SyntheticEvent<Element>) => {
    const { value: signatureJson } = event.target as HTMLInputElement;

    const { ethereumAddress, signature } = recoverFromJSON(signatureJson);

    setEthereumAddress(ethereumAddress?.toString());
    setSignature(signature);
  }, []);

  const onChangeEthereumAddress = useCallback((value: string) => {
    // FIXME We surely need a better check than just a trim

    setEthereumAddress(value.trim());
  }, []);

  const onCopy = useCallback(() => {
    setDidCopy(true);
  }, []);

  // If it's 1/ not preclaimed and 2/ not the old claiming process, fetch the
  // statement kind to sign.
  const statementKind = useCall<StatementKind | null>(!isPreclaimed && !isOldClaimProcess && !!ethereumAddress && api.query.claims.signing, [ethereumAddress], transformStatement);

  const statementSentence = getStatement(systemChain, statementKind)?.sentence || '';

  const prefix = u8aToString(api.consts.claims.prefix.toU8a(true));
  const payload = accountId
    ? `${prefix}${u8aToHex(decodeAddress(accountId), -1, false)}${statementSentence}${ethereumTxHash?.substring(2)}`
    : '';

  return (
    <main>
      {!isOldClaimProcess && <Warning />}
      <h2>
        {t<string>('Claim your {{token}} tokens', {
          replace: {
            token: TokenUnit.abbr
          }
        })}
      </h2>
      <Columar>
        <Columar.Column>
          <Card withBottomMargin>
            <h3>{t<string>('0. Burn your ')}<a href='https://etherscan.io/token/0x32a7C02e79c4ea1008dD6564b35F131428673c41'>{t('ERC20 CRU')}</a>{t<string>(', transfer to address ')} <a href='https://etherscan.io/address/0x0000000000000000000000000000000000000001' target="_blank">0x0000000000000000000000000000000000000001</a></h3>
            <Banner type='warning'>
              <p>{t<string>('Please make sure you have the authority to make signature with the private key of the wallet account, using an exchange account to sent a transfer (withdrawal) transaction will be invalidated and cause asset loss, you are responsible for the consequences')}</p>
            </Banner>
            <img style={{'marginLeft': 'auto', 'marginRight': 'auto', 'display': 'block', "width": "150px" }} src={burnPng as string} />
          </Card>
          {(<Card withBottomMargin>
            <h3>{t<string>(`1. Select your {{chain}} account and enter`, {
              replace: {
                chain: systemChain
              }
            })} <a href='https://etherscan.io/token/0x32a7C02e79c4ea1008dD6564b35F131428673c41'>{t('ERC20 CRU')}</a>
             {t<string>(' transfer tx hash')}<span>
               {t<string>(`, If your claim amount is greater than the claim limit `)}
               <span style={{ 'color': '#ff8812', 'textDecoration': 'underline', 'fontStyle': 'italic' }}>({formatBalance(claimLimit, { withUnit: 'CRU' })})</span>
               {t<string>(', please wait for the limit update')}
               </span>
            </h3>
            <InputAddress
              defaultValue={accountId}
              help={t<string>('The account you want to claim to.')}
              isDisabled={ethereumTxHashValid}
              label={t<string>('claim to account')}
              onChange={setAccountId}
              type='account'
            />
            <Input
              autoFocus
              className='full'
              help={t<string>('The Ethereum CRU transfer tx hash (starting by "0x")')}
              isDisabled={ethereumTxHashValid}
              isError={!isValid}
              label={t<string>('Ethereum tx hash')}
              onChange={onChangeEthereumTxHash}
              placeholder={t<string>('0x prefixed hex, e.g. 0x1234 or ascii data')}
              value={ethereumTxHash || ''}
            />
            {(step === Step.Account) && (<Button.Group>
              <Button
                icon='sign-in-alt'
                isBusy={isBusy}
                isDisabled={preclaimEthereumAddress === PRECLAIMS_LOADING || ethereumTxHash === null || ethereumTxHash === '' || !isValid}
                label={preclaimEthereumAddress === PRECLAIMS_LOADING
                  ? t<string>('Loading')
                  : t<string>('Continue')
                }
                onClick={handleAccountStep}
              />
            </Button.Group>)}
            <HttpStatus
              isStatusOpen={statusOpen}
              message={result}
              setStatusOpen={setStatusOpen}
              status={status}
            />
          </Card>)}
          {
            // We need to know the ethereuem address only for the new process
            // to be able to know the statement kind so that the users can sign it
            (step >= Step.ETHAddress && !isPreclaimed && !isOldClaimProcess) && (
              <Card withBottomMargin>
                <h3>{t<string>('2. Enter the ETH address from the sale.')}</h3>
                <Input
                  autoFocus
                  className='full'
                  help={t<string>('The the Ethereum address you burnt your ERC20 CRU in step 0 (starting by "0x")')}
                  label={t<string>('Pre-sale ethereum address')}
                  onChange={onChangeEthereumAddress}
                  value={ethereumAddress || ''}
                />
                {(step === Step.ETHAddress) && (
                  <Button.Group>
                    <Button
                      icon='sign-in-alt'
                      isDisabled={!ethereumAddress}
                      label={t<string>('Continue')}
                      onClick={goToStepSign}
                    />
                  </Button.Group>
                )}
              </Card>
            )}
            {(step >= Step.Sign && !isPreclaimed) && (
              <Card>
                <h3>{t<string>('{{step}}. Sign with your ETH address', { replace: { step: isOldClaimProcess ? '2' : '3' } })}</h3>
                {!isOldClaimProcess && (
                  <Statement
                    kind={statementKind}
                    systemChain={systemChain}
                  />
                )}
                <div>{t<string>('Copy the following string and sign it with the Ethereum account you burnt your ERC20 CRU in step 0, using the string as the payload, and then paste the transaction signature object below:')}</div>
                <CopyToClipboard
                  onCopy={onCopy}
                  text={payload}
                >
                  <Payload
                    data-for='tx-payload'
                    data-tip
                  >
                    {payload}
                  </Payload>
                </CopyToClipboard>
                <Tooltip
                  place='right'
                  text={didCopy ? t<string>('copied') : t<string>('click to copy')}
                  trigger='tx-payload'
                />
                <div>{t<string>('Paste the signed message into the field below. The placeholder text is there as a hint to what the message should look like:')}</div>
                <Signature
                  onChange={onChangeSignature}
                  placeholder={`{\n  "address": "0x ...",\n  "msg": "0x ...",\n  "sig": "...",\n  "version": "3",\n  "signer": "..."\n}`}
                  rows={10}
                />
                {(step === Step.Sign) && (
                  <Button.Group>
                    <Button
                      icon='sign-in-alt'
                      isDisabled={!accountId || !signature}
                      label={t<string>('Confirm claim')}
                      onClick={goToStepClaim}
                    />
                  </Button.Group>
                )}
              </Card>
            )}
        </Columar.Column>
        <Columar.Column>
            {(step >= Step.Claim) && (
              isPreclaimed
                ? <AttestDisplay
                  accountId={accountId}
                  ethereumAddress={ethereumAddress}
                  onSuccess={goToStepAccount}
                  statementKind={statementKind}
                  systemChain={systemChain}
                />
                : <ClaimDisplay
                  accountId={accountId}
                  ethereumAddress={ethereumAddress}
                  ethereumSignature={signature}
                  isOldClaimProcess={isOldClaimProcess}
                  onSuccess={goToStepAccount}
                  statementKind={statementKind}
                  ethereumTxHash={ethereumTxHash}
                />
            )}
        </Columar.Column>
      </Columar>
    </main>
  );
}