@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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
/**
* @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 |
/**
* 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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' />
{t<string>('identity')}
</div>
<Tag
color={
identity.isBad
? 'red'
: identity.isGood
? 'green'
: 'yellow'
}
isTag={false}
label={
<>
<b>{identity.judgements.length} </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 |
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 |
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 |
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 |
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> ); }