├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── deploy-dev.yml │ ├── deploy-prd.yml │ ├── deploy-stg.yml │ └── test.yml ├── .gitignore ├── .nvmrc ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_ZH.md ├── commitlint.config.js ├── craco.config.js ├── cypress.json ├── cypress ├── fixtures │ └── example.json ├── integration │ ├── polkadotjs.spec.ts │ └── wallets.spec.ts ├── plugins │ └── index.ts ├── support │ ├── commands.ts │ └── index.ts └── tsconfig.json ├── docs ├── 10_approve.png ├── 11_progress.png ├── 12_cancel.png ├── 13_select_network.png ├── 1_download.png ├── 2_auth.png ├── 3_create_wallet.png ├── 4_add_members.png ├── 5_view_members.png ├── 5_view_members==.png ├── 6_enter_detail.png ├── 7_account_ops.png ├── 8_initial_extrinsic.png ├── 9_pending.png ├── btn_detail.png ├── btn_member.png ├── btn_pending.png └── grants_badge.png ├── i18next-scanner.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── favicon.png ├── icons │ ├── active-accounts.svg │ ├── arrow-left.svg │ ├── arrow-right.svg │ ├── asset.svg │ ├── bell.svg │ ├── chart.svg │ ├── detail-arrow.svg │ ├── donate.svg │ ├── download.svg │ ├── dropdown-arrow.svg │ ├── earth.svg │ ├── email-black.svg │ ├── email-grey.svg │ ├── email.svg │ ├── era.svg │ ├── external-link.svg │ ├── failed.svg │ ├── filter.svg │ ├── finalized.svg │ ├── from-to-arrow.svg │ ├── github-black.svg │ ├── github-grey.svg │ ├── inflation.svg │ ├── latest-blocks.svg │ ├── medium-black.svg │ ├── medium-grey.svg │ ├── menu-basic.svg │ ├── menu.svg │ ├── module-events.svg │ ├── pending.svg │ ├── riot-black.svg │ ├── riot-grey.svg │ ├── sandglass.svg │ ├── search.svg │ ├── signed-extrinsics.svg │ ├── success.svg │ ├── transfers.svg │ ├── triangle-down.svg │ ├── twitter-black.svg │ ├── twitter-grey.svg │ └── zh.svg ├── image │ ├── 404.png │ ├── aye-circle.png │ ├── aye.png │ ├── ckton.png │ ├── ckton.svg │ ├── close-btn.png │ ├── cring.png │ ├── cring.svg │ ├── ethereum.png │ ├── home-bg-mobile.png │ ├── home-bg.jpg │ ├── interlay.png │ ├── kintsugi.png │ ├── kton.png │ ├── kton.svg │ ├── kusama-banner-mobile.png │ ├── kusama-banner.png │ ├── kusama-button-mobile.png │ ├── kusama-button.png │ ├── kusama.png │ ├── kusama.svg │ ├── logo-black.png │ ├── logo@2x.png │ ├── nay-circle.png │ ├── nay.png │ ├── no-data.png │ ├── no-related-data.png │ ├── parallel.svg │ ├── polka-check.png │ ├── polka-cross.png │ ├── polkadot-banner-mobile.png │ ├── polkadot-banner.png │ ├── polkadot-button-mobile.png │ ├── polkadot-button.png │ ├── polkadot.png │ ├── polkadot.svg │ ├── polkassembly.png │ ├── pring.svg │ ├── ring.png │ └── ring.svg ├── index.html ├── locales │ ├── en │ │ ├── react-components.json │ │ ├── react-params.json │ │ ├── react-query.json │ │ ├── react-signer.json │ │ └── translation.json │ └── zh │ │ └── translation.json ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.tsx ├── __tests__ │ ├── Language.test.tsx │ ├── ThemeSwitch.test.tsx │ └── utils │ │ └── address.test.ts ├── assets │ └── images │ │ ├── icon_add_filled.svg │ │ ├── icon_down.svg │ │ ├── icon_down_filled.svg │ │ ├── icon_members.svg │ │ ├── icon_question.svg │ │ └── subscan_logo.png ├── components │ ├── AddContactModal.tsx │ ├── Anime │ │ ├── Anime.scss │ │ └── Anime.tsx │ ├── Args.tsx │ ├── Entries.tsx │ ├── ExtrinsicLaunch.tsx │ ├── ExtrinsicRecords.tsx │ ├── Fee.tsx │ ├── Footer.tsx │ ├── HeadAccounts.tsx │ ├── Language.tsx │ ├── Members.tsx │ ├── PolkadotApiTest.tsx │ ├── Status.tsx │ ├── SubscanLink.tsx │ ├── ThemeSwitch.tsx │ ├── TxApprove.tsx │ ├── TxCancel.tsx │ ├── TxProgressAndParameters.tsx │ ├── WalletForm.tsx │ ├── WalletState.tsx │ ├── Wallets.tsx │ ├── expandIcon.tsx │ ├── icons │ │ ├── MoonIcon.tsx │ │ ├── add.tsx │ │ ├── close.tsx │ │ ├── copy.tsx │ │ ├── donate.tsx │ │ ├── down.tsx │ │ ├── earth.tsx │ │ ├── email.tsx │ │ ├── export.tsx │ │ ├── icon-factory.tsx │ │ ├── import.tsx │ │ ├── index.ts │ │ ├── right-circle.tsx │ │ ├── swap-crab.tsx │ │ ├── swap-pangolin.tsx │ │ ├── swap.tsx │ │ └── view-browser.tsx │ └── modals │ │ ├── AddCustomNetwork.tsx │ │ ├── ConfirmDialog.tsx │ │ ├── InputCallDataModal.tsx │ │ ├── SelectNetworkModal.tsx │ │ ├── Transfer.tsx │ │ └── TxPreviewModal.tsx ├── config │ ├── chains.ts │ ├── chains │ │ ├── heiko.json │ │ ├── interlay.json │ │ ├── kintsugi.json │ │ ├── kusama.json │ │ ├── parallel.json │ │ └── polkadot.json │ ├── constant.ts │ ├── i18n.ts │ ├── index.ts │ ├── network.ts │ ├── query.ts │ ├── routes.ts │ ├── theme.ts │ ├── types.ts │ └── validate-msg.ts ├── global.d.ts ├── hooks │ ├── AxiosRequest.ts │ ├── api.tsx │ ├── cancelablePromise.ts │ ├── combineQuery.ts │ ├── contact.ts │ ├── fee.ts │ ├── index.ts │ ├── injected.ts │ ├── mountedState.ts │ ├── multiApprove.ts │ ├── multisig.ts │ ├── multisigContext.tsx │ ├── subquery.ts │ └── subscan.ts ├── icomoon-selection.json ├── index.scss ├── index.tsx ├── model │ ├── account.ts │ ├── common.ts │ ├── index.ts │ ├── network.ts │ ├── storage.ts │ ├── tx.ts │ └── util.ts ├── packages │ ├── react-api │ │ ├── .skip-build │ │ ├── .skip-npm │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── Api.tsx │ │ │ ├── ApiContext.ts │ │ │ ├── hoc │ │ │ │ ├── api.tsx │ │ │ │ ├── call.tsx │ │ │ │ ├── callDiv.tsx │ │ │ │ ├── calls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── multi.ts │ │ │ │ ├── observable.tsx │ │ │ │ ├── onlyOn.tsx │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ ├── transform │ │ │ │ └── echo.ts │ │ │ ├── typeRegistry.ts │ │ │ ├── types.ts │ │ │ ├── urlTypes.ts │ │ │ └── util │ │ │ │ ├── getEnvironment.ts │ │ │ │ ├── historic.ts │ │ │ │ ├── index.ts │ │ │ │ ├── intervalObservable.ts │ │ │ │ ├── isEqual.ts │ │ │ │ └── triggerChange.ts │ │ └── test │ │ │ ├── enzyme.js │ │ │ └── observable.js │ ├── react-components │ │ ├── .skip-build │ │ ├── .skip-npm │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ └── src │ │ │ ├── AccountIndex.tsx │ │ │ ├── AccountName.tsx │ │ │ ├── AddressInfo.tsx │ │ │ ├── AddressMini.tsx │ │ │ ├── AddressRow.tsx │ │ │ ├── AddressSmall.tsx │ │ │ ├── AddressToggle.tsx │ │ │ ├── Available.tsx │ │ │ ├── AvatarItem.tsx │ │ │ ├── Badge.tsx │ │ │ ├── Balance.tsx │ │ │ ├── BatchWarning.tsx │ │ │ ├── Bonded.tsx │ │ │ ├── Button │ │ │ ├── Button.tsx │ │ │ ├── Group.tsx │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ │ ├── ButtonCancel.tsx │ │ │ ├── Call.tsx │ │ │ ├── CallExpander.tsx │ │ │ ├── Card.tsx │ │ │ ├── CardSummary.tsx │ │ │ ├── ChainImg.tsx │ │ │ ├── ChainLock.tsx │ │ │ ├── Chart │ │ │ ├── Base.tsx │ │ │ ├── Doughnut.tsx │ │ │ ├── HorizBar.tsx │ │ │ ├── Line.tsx │ │ │ ├── index.ts │ │ │ └── types.ts │ │ │ ├── Checkbox.tsx │ │ │ ├── Columar.tsx │ │ │ ├── ConvictionDropdown.tsx │ │ │ ├── CopyButton.tsx │ │ │ ├── CryptoType.tsx │ │ │ ├── DemocracyLocks.tsx │ │ │ ├── Digits.tsx │ │ │ ├── Dropdown.tsx │ │ │ ├── EditButton.tsx │ │ │ ├── Editor.tsx │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── Event.tsx │ │ │ ├── Expander.tsx │ │ │ ├── Extrinsic.tsx │ │ │ ├── FilterOverlay.tsx │ │ │ ├── Forget.tsx │ │ │ ├── HelpOverlay.tsx │ │ │ ├── Icon.tsx │ │ │ ├── IconLink.tsx │ │ │ ├── InfoForInput.tsx │ │ │ ├── Input.tsx │ │ │ ├── InputAddress │ │ │ ├── KeyPair.tsx │ │ │ ├── createHeader.tsx │ │ │ ├── createItem.tsx │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ │ ├── InputAddressMulti │ │ │ ├── Available.tsx │ │ │ ├── Selected.tsx │ │ │ ├── SelectedDrag.tsx │ │ │ └── index.tsx │ │ │ ├── InputAddressSimple.tsx │ │ │ ├── InputBalance.tsx │ │ │ ├── InputConsts │ │ │ ├── SelectKey.tsx │ │ │ ├── SelectSection.tsx │ │ │ ├── index.tsx │ │ │ ├── options │ │ │ │ ├── key.tsx │ │ │ │ └── section.ts │ │ │ └── types.ts │ │ │ ├── InputExtrinsic │ │ │ ├── LinkedWrapper.tsx │ │ │ ├── SelectMethod.tsx │ │ │ ├── SelectSection.tsx │ │ │ ├── index.tsx │ │ │ └── options │ │ │ │ ├── method.tsx │ │ │ │ └── section.ts │ │ │ ├── InputFile.tsx │ │ │ ├── InputNumber.tsx │ │ │ ├── InputRpc │ │ │ ├── SelectMethod.tsx │ │ │ ├── SelectSection.tsx │ │ │ ├── index.tsx │ │ │ ├── options │ │ │ │ ├── method.tsx │ │ │ │ └── section.ts │ │ │ ├── rpcs.ts │ │ │ └── useRpcs.ts │ │ │ ├── InputStorage │ │ │ ├── SelectKey.tsx │ │ │ ├── SelectSection.tsx │ │ │ ├── index.tsx │ │ │ └── options │ │ │ │ ├── key.tsx │ │ │ │ └── section.ts │ │ │ ├── InputTags.tsx │ │ │ ├── InputWasm.tsx │ │ │ ├── Inset.tsx │ │ │ ├── Label.tsx │ │ │ ├── LabelHelp.tsx │ │ │ ├── Labelled.tsx │ │ │ ├── LinkExternal.tsx │ │ │ ├── LockedVote.tsx │ │ │ ├── MarkError.tsx │ │ │ ├── MarkWarning.tsx │ │ │ ├── Menu.tsx │ │ │ ├── Modal │ │ │ ├── Actions.tsx │ │ │ ├── Columns.tsx │ │ │ ├── index.tsx │ │ │ └── types.tsx │ │ │ ├── Nonce.tsx │ │ │ ├── Output.tsx │ │ │ ├── ParaLink.tsx │ │ │ ├── Params │ │ │ ├── Call.tsx │ │ │ ├── Extrinsic.tsx │ │ │ ├── OpaqueCall.tsx │ │ │ ├── Proposal.tsx │ │ │ └── index.ts │ │ │ ├── Password.tsx │ │ │ ├── PasswordStrength.tsx │ │ │ ├── Popup.tsx │ │ │ ├── Progress.tsx │ │ │ ├── ProposedAction.tsx │ │ │ ├── Row.tsx │ │ │ ├── Sidebar.tsx │ │ │ ├── Spinner.png │ │ │ ├── Spinner.tsx │ │ │ ├── StakingBonded.tsx │ │ │ ├── StakingRedeemable.tsx │ │ │ ├── StakingUnbonding.tsx │ │ │ ├── Static.tsx │ │ │ ├── Status │ │ │ ├── Context.ts │ │ │ ├── Queue.tsx │ │ │ ├── constants.ts │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ │ ├── SummaryBox.tsx │ │ │ ├── Table │ │ │ ├── Body.tsx │ │ │ ├── Foot.tsx │ │ │ ├── Head.tsx │ │ │ └── index.tsx │ │ │ ├── Tabs │ │ │ ├── CurrentSection.tsx │ │ │ ├── Tab.tsx │ │ │ ├── TabsSectionDelimiter.tsx │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ │ ├── Tag.tsx │ │ │ ├── Tags.tsx │ │ │ ├── TextArea.tsx │ │ │ ├── Toggle.tsx │ │ │ ├── ToggleGroup.tsx │ │ │ ├── Tooltip.tsx │ │ │ ├── TreasuryProposal.tsx │ │ │ ├── TxButton.tsx │ │ │ ├── TxButtonMultisig.tsx │ │ │ ├── VoteAccount.tsx │ │ │ ├── VoteValue.tsx │ │ │ ├── constants.ts │ │ │ ├── i18n │ │ │ ├── Backend.ts │ │ │ ├── cache.ts │ │ │ └── index.ts │ │ │ ├── index.tsx │ │ │ ├── media.ts │ │ │ ├── styles │ │ │ ├── components.ts │ │ │ ├── form.ts │ │ │ ├── index.ts │ │ │ ├── media.ts │ │ │ ├── rx.ts │ │ │ ├── semantic.ts │ │ │ └── theme.ts │ │ │ ├── translate.ts │ │ │ ├── types.ts │ │ │ └── util │ │ │ ├── checkVisibility.tsx │ │ │ ├── getAddressMeta.ts │ │ │ ├── getAddressName.ts │ │ │ ├── getAddressTags.ts │ │ │ ├── getContractAbi.ts │ │ │ ├── index.ts │ │ │ ├── isTreasuryProposalVote.ts │ │ │ ├── toAddress.ts │ │ │ ├── toShortAddress.ts │ │ │ └── types.ts │ ├── react-hooks │ │ ├── .skip-build │ │ ├── .skip-npm │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ └── src │ │ │ ├── index.ts │ │ │ ├── translate.ts │ │ │ ├── types.ts │ │ │ ├── useAccountId.ts │ │ │ ├── useAccountInfo.ts │ │ │ ├── useAccounts.ts │ │ │ ├── useAddresses.ts │ │ │ ├── useApi.ts │ │ │ ├── useApiUrl.ts │ │ │ ├── useAvailableSlashes.ts │ │ │ ├── useBestHash.ts │ │ │ ├── useBestNumber.ts │ │ │ ├── useBlockTime.ts │ │ │ ├── useBlocksPerDays.ts │ │ │ ├── useCacheKey.ts │ │ │ ├── useCall.ts │ │ │ ├── useCallMulti.ts │ │ │ ├── useDebounce.ts │ │ │ ├── useEventTrigger.ts │ │ │ ├── useExtrinsicTrigger.ts │ │ │ ├── useFavorites.ts │ │ │ ├── useFormField.ts │ │ │ ├── useIncrement.ts │ │ │ ├── useInflation.ts │ │ │ ├── useIpfs.ts │ │ │ ├── useIsMountedRef.ts │ │ │ ├── useLedger.ts │ │ │ ├── useLoadingDelay.ts │ │ │ ├── useMapEntries.ts │ │ │ ├── useMapKeys.ts │ │ │ ├── useMembers.ts │ │ │ ├── useModal.ts │ │ │ ├── useNonEmptyString.ts │ │ │ ├── useNonZeroBn.ts │ │ │ ├── useOwnEraRewards.ts │ │ │ ├── useOwnStashInfos.ts │ │ │ ├── useOwnStashes.ts │ │ │ ├── useParaApi.ts │ │ │ ├── useParaEndpoints.ts │ │ │ ├── usePassword.ts │ │ │ ├── useRegistrars.ts │ │ │ ├── useSavedFlags.ts │ │ │ ├── useStepper.ts │ │ │ ├── useSudo.ts │ │ │ ├── useTeleport.ts │ │ │ ├── useToggle.ts │ │ │ ├── useTreasury.ts │ │ │ ├── useTxBatch.ts │ │ │ ├── useVotingStatus.ts │ │ │ ├── useWeight.ts │ │ │ └── useWeightFee.ts │ ├── react-params │ │ ├── .skip-build │ │ ├── .skip-npm │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ └── src │ │ │ ├── Holder.tsx │ │ │ ├── Param │ │ │ ├── Account.tsx │ │ │ ├── Amount.tsx │ │ │ ├── Balance.tsx │ │ │ ├── Bare.tsx │ │ │ ├── Base.tsx │ │ │ ├── BaseBytes.tsx │ │ │ ├── Bool.tsx │ │ │ ├── Bytes.tsx │ │ │ ├── Call.tsx │ │ │ ├── Code.tsx │ │ │ ├── DispatchError.tsx │ │ │ ├── DispatchResult.tsx │ │ │ ├── Enum.tsx │ │ │ ├── File.tsx │ │ │ ├── Hash160.tsx │ │ │ ├── Hash256.tsx │ │ │ ├── Hash512.tsx │ │ │ ├── KeyValue.tsx │ │ │ ├── KeyValueArray.tsx │ │ │ ├── Moment.tsx │ │ │ ├── Null.tsx │ │ │ ├── OpaqueCall.tsx │ │ │ ├── Option.tsx │ │ │ ├── Raw.tsx │ │ │ ├── Static.tsx │ │ │ ├── Struct.tsx │ │ │ ├── Text.tsx │ │ │ ├── Tuple.tsx │ │ │ ├── Unknown.tsx │ │ │ ├── Vector.tsx │ │ │ ├── VectorFixed.tsx │ │ │ ├── Vote.tsx │ │ │ ├── VoteThreshold.tsx │ │ │ ├── findComponent.ts │ │ │ ├── index.tsx │ │ │ └── useParamDefs.ts │ │ │ ├── ParamComp.tsx │ │ │ ├── Params.tsx │ │ │ ├── index.ts │ │ │ ├── initValue.ts │ │ │ ├── translate.ts │ │ │ ├── types.ts │ │ │ ├── valueToText.tsx │ │ │ └── values.ts │ ├── react-query │ │ ├── .skip-build │ │ ├── .skip-npm │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ └── src │ │ │ ├── Available.tsx │ │ │ ├── Balance.tsx │ │ │ ├── BalanceFree.tsx │ │ │ ├── BalanceVoting.tsx │ │ │ ├── BestFinalized.tsx │ │ │ ├── BestNumber.tsx │ │ │ ├── BlockAuthors.tsx │ │ │ ├── BlockToTime.tsx │ │ │ ├── Bonded.tsx │ │ │ ├── Chain.tsx │ │ │ ├── Elapsed.tsx │ │ │ ├── Events.tsx │ │ │ ├── FormatBalance.tsx │ │ │ ├── LockedVote.tsx │ │ │ ├── NodeName.tsx │ │ │ ├── NodeVersion.tsx │ │ │ ├── Nonce.tsx │ │ │ ├── SessionToTime.tsx │ │ │ ├── TimeNow.tsx │ │ │ ├── TotalIssuance.tsx │ │ │ ├── index.ts │ │ │ ├── translate.ts │ │ │ └── types.ts │ └── react-signer │ │ ├── .skip-build │ │ ├── .skip-npm │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ └── src │ │ ├── Address.tsx │ │ ├── Password.tsx │ │ ├── PaymentInfo.tsx │ │ ├── Qr.tsx │ │ ├── SignFields.tsx │ │ ├── Tip.tsx │ │ ├── Transaction.tsx │ │ ├── TxSigned.tsx │ │ ├── TxUnsigned.tsx │ │ ├── index.tsx │ │ ├── signers │ │ ├── AccountSigner.ts │ │ ├── ApiSigner.ts │ │ ├── LedgerSigner.ts │ │ ├── QrSigner.ts │ │ └── index.ts │ │ ├── translate.ts │ │ ├── types.ts │ │ └── util.ts ├── pages │ ├── Connecting.tsx │ ├── Extrinsic.tsx │ ├── Home.tsx │ ├── PolkadotJs.tsx │ └── Wallet.tsx ├── providers │ ├── api-provider.tsx │ ├── gql-provider.tsx │ ├── index.ts │ └── multisig-provider.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── theme │ ├── antd │ │ ├── dark.json │ │ ├── index.less │ │ ├── light.json │ │ ├── vars.json │ │ └── vars.less │ └── network │ │ └── dark │ │ ├── kusama.json │ │ └── polkadot.json └── utils │ ├── helper │ ├── address.ts │ ├── args.ts │ ├── balance.ts │ ├── common.ts │ ├── copy.ts │ ├── index.ts │ ├── multisig.ts │ ├── storage.ts │ ├── time.ts │ ├── types.ts │ ├── url.ts │ ├── validate.ts │ └── weight.ts │ └── index.ts ├── stylelint.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.paths.json └── vercel.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # don't ever lint node_modules 2 | node_modules 3 | # don't lint build output (make sure it's set to your correct build folder name) 4 | dist 5 | # don't lint nyc coverage output 6 | coverage 7 | 8 | cypress/ 9 | 10 | *.config.js -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @sxlwar @carlhong @vzxh 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | /.vscode 26 | 27 | # cypress 28 | cypress/integration/1-getting-started/ 29 | cypress/integration/2-advanced-examples/ 30 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "tabWidth": 2, 5 | "printWidth": 120, 6 | "endOfLine": "lf", 7 | "useTabs": false, 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const { commitlint } = require('@darwinia/lints'); 2 | 3 | module.exports = commitlint; 4 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3008", 3 | "component": { 4 | "componentFolder": "src", 5 | "testFiles": "__tests__/**/*.test.{js,ts,jsx,tsx}" 6 | }, 7 | "env": { 8 | "product": "https://multisig.subscan.io/", 9 | "staging": "https://multisig.subscan.io.l2me.com/" 10 | }, 11 | "ignoreTestFiles": "*/examples/**", 12 | "nodeVersion": "system", 13 | "video": false 14 | } 15 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /cypress/integration/polkadotjs.spec.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | describe('Create Multisig Extrinsic', () => { 4 | before(() => { 5 | cy.visit(Cypress.config().baseUrl + '/ci/polkadotjs#r%3Dwss%3A%2F%2Fkusama-rpc.dwellir.com'); 6 | }); 7 | 8 | it('should display success div', () => { 9 | cy.get('.polkadotjs', { timeout: 120 * 1000 }).should('be.visible'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /cypress/integration/wallets.spec.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | describe.skip('Wallets display and create', () => { 4 | before(() => { 5 | cy.visit(Cypress.config().baseUrl); 6 | }); 7 | 8 | it('should display create wallets button', () => { 9 | cy.get('a[href="/wallet#r%3Dwss%3A%2F%2Frpc.polkadot.io"]', { timeout: 20 * 1000 }) 10 | .should('be.visible') 11 | .find('button') 12 | .should('have.class', 'ant-btn'); 13 | }); 14 | 15 | it('should navigate to wallet create page', () => { 16 | cy.get('a[href="/wallet#r%3Dwss%3A%2F%2Frpc.polkadot.io"]') 17 | .click() 18 | .then(() => { 19 | cy.url().should('include', 'wallet'); 20 | }); 21 | }); 22 | 23 | it('should display create wallet correctly', () => { 24 | cy.get('#wallet_name').should('be.visible'); 25 | cy.get('#wallet_threshold').should('be.visible').and('have.value', 2); 26 | cy.get('input[type=search]').should('have.length', 3); 27 | cy.get('button[type=submit]').should('exist'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /cypress/plugins/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | const path = require('path'); 16 | const cracoPlugin = require('@cypress/react/plugins/craco'); 17 | const cracoConf = require(path.join(__dirname, '../../craco.config.js')); 18 | 19 | /** 20 | * @type {Cypress.PluginConfig} 21 | */ 22 | // eslint-disable-next-line no-unused-vars 23 | export default (on: any, config: any) => { 24 | cracoPlugin(on, config, cracoConf); 25 | 26 | return config; 27 | }; 28 | -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /cypress/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.paths.json", 3 | "compilerOptions": { 4 | "lib": ["es5", "dom"], 5 | "target": "es5", 6 | "types": ["cypress", "node"] 7 | }, 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /docs/10_approve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/10_approve.png -------------------------------------------------------------------------------- /docs/11_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/11_progress.png -------------------------------------------------------------------------------- /docs/12_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/12_cancel.png -------------------------------------------------------------------------------- /docs/13_select_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/13_select_network.png -------------------------------------------------------------------------------- /docs/1_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/1_download.png -------------------------------------------------------------------------------- /docs/2_auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/2_auth.png -------------------------------------------------------------------------------- /docs/3_create_wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/3_create_wallet.png -------------------------------------------------------------------------------- /docs/4_add_members.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/4_add_members.png -------------------------------------------------------------------------------- /docs/5_view_members.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/5_view_members.png -------------------------------------------------------------------------------- /docs/5_view_members==.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/5_view_members==.png -------------------------------------------------------------------------------- /docs/6_enter_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/6_enter_detail.png -------------------------------------------------------------------------------- /docs/7_account_ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/7_account_ops.png -------------------------------------------------------------------------------- /docs/8_initial_extrinsic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/8_initial_extrinsic.png -------------------------------------------------------------------------------- /docs/9_pending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/9_pending.png -------------------------------------------------------------------------------- /docs/btn_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/btn_detail.png -------------------------------------------------------------------------------- /docs/btn_member.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/btn_member.png -------------------------------------------------------------------------------- /docs/btn_pending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/btn_pending.png -------------------------------------------------------------------------------- /docs/grants_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/docs/grants_badge.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/favicon.png -------------------------------------------------------------------------------- /public/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/bell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 形状结合 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/icons/detail-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 矩形 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/icons/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon/download 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/icons/dropdown-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 形状结合 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/icons/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 全局/分享 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/icons/failed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon/错 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/icons/latest-blocks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon/blocks 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/icons/menu-basic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon/账号信息 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/icons/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Icon/菜单黑 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/icons/pending.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon/等待 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon/stretch 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/icons/success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon/对 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/icons/triangle-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 矩形 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/icons/zh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | icon/语言 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/image/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/404.png -------------------------------------------------------------------------------- /public/image/aye-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/aye-circle.png -------------------------------------------------------------------------------- /public/image/aye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/aye.png -------------------------------------------------------------------------------- /public/image/ckton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/ckton.png -------------------------------------------------------------------------------- /public/image/close-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/close-btn.png -------------------------------------------------------------------------------- /public/image/cring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/cring.png -------------------------------------------------------------------------------- /public/image/ethereum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/ethereum.png -------------------------------------------------------------------------------- /public/image/home-bg-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/home-bg-mobile.png -------------------------------------------------------------------------------- /public/image/home-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/home-bg.jpg -------------------------------------------------------------------------------- /public/image/interlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/interlay.png -------------------------------------------------------------------------------- /public/image/kintsugi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/kintsugi.png -------------------------------------------------------------------------------- /public/image/kton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/kton.png -------------------------------------------------------------------------------- /public/image/kusama-banner-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/kusama-banner-mobile.png -------------------------------------------------------------------------------- /public/image/kusama-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/kusama-banner.png -------------------------------------------------------------------------------- /public/image/kusama-button-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/kusama-button-mobile.png -------------------------------------------------------------------------------- /public/image/kusama-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/kusama-button.png -------------------------------------------------------------------------------- /public/image/kusama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/kusama.png -------------------------------------------------------------------------------- /public/image/logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/logo-black.png -------------------------------------------------------------------------------- /public/image/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/logo@2x.png -------------------------------------------------------------------------------- /public/image/nay-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/nay-circle.png -------------------------------------------------------------------------------- /public/image/nay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/nay.png -------------------------------------------------------------------------------- /public/image/no-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/no-data.png -------------------------------------------------------------------------------- /public/image/no-related-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/no-related-data.png -------------------------------------------------------------------------------- /public/image/polka-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/polka-check.png -------------------------------------------------------------------------------- /public/image/polka-cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/polka-cross.png -------------------------------------------------------------------------------- /public/image/polkadot-banner-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/polkadot-banner-mobile.png -------------------------------------------------------------------------------- /public/image/polkadot-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/polkadot-banner.png -------------------------------------------------------------------------------- /public/image/polkadot-button-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/polkadot-button-mobile.png -------------------------------------------------------------------------------- /public/image/polkadot-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/polkadot-button.png -------------------------------------------------------------------------------- /public/image/polkadot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/polkadot.png -------------------------------------------------------------------------------- /public/image/polkassembly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/polkassembly.png -------------------------------------------------------------------------------- /public/image/ring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/image/ring.png -------------------------------------------------------------------------------- /public/locales/en/react-params.json: -------------------------------------------------------------------------------- 1 | { 2 | "0x prefixed hex, e.g. 0x1234 or ascii data": "0x prefixed hex, e.g. 0x1234 or ascii data", 3 | "": "", 4 | "Add item": "Add item", 5 | "Aye": "Aye", 6 | "Locked1x": "Locked1x", 7 | "Locked2x": "Locked2x", 8 | "Locked3x": "Locked3x", 9 | "Locked4x": "Locked4x", 10 | "Locked5x": "Locked5x", 11 | "Locked6x": "Locked6x", 12 | "Nay": "Nay", 13 | "No": "No", 14 | "None": "None", 15 | "Remove item": "Remove item", 16 | "Yes": "Yes", 17 | "aye: bool": "aye: bool", 18 | "conviction: Conviction": "conviction: Conviction", 19 | "details": "details", 20 | "file upload": "file upload", 21 | "hash a file": "hash a file", 22 | "include option": "include option", 23 | "type": "type", 24 | "{{count}} key/value pairs encoded for submission": "{{count}} key/value pairs encoded for submission" 25 | } 26 | -------------------------------------------------------------------------------- /public/locales/en/react-query.json: -------------------------------------------------------------------------------- 1 | { 2 | "Unknown": "Unknown", 3 | "everything{{labelPost}}": "everything{{labelPost}}" 4 | } 5 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Subscan Multisig", 3 | "name": "Subscan Multisig", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/__tests__/Language.test.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | import { mount } from '@cypress/react'; 3 | import { Language } from '../components/Language'; 4 | // import '../index.scss'; 5 | 6 | describe('render', () => { 7 | it('should render the language switch with network color', () => { 8 | mount( 9 | 10 | 11 | 12 | ); 13 | 14 | cy.get('.ant-btn').should('be.visible'); 15 | cy.get('.anticon').should('have.css', 'color'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/__tests__/ThemeSwitch.test.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { mount } from '@cypress/react'; 4 | import { ThemeSwitch } from '../components/ThemeSwitch'; 5 | 6 | describe('should switch global theme', () => { 7 | it('should switch global theme', () => { 8 | mount(); 9 | 10 | cy.get('.ant-switch').should('be.visible'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/__tests__/utils/address.test.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { dvmAddressToAccountId, isSS58Address, convertToDvm } from '../../utils'; 4 | 5 | const SS58_ACCOUNT = '2roTfbmrgZ8iHeuhzULpa1nM48L86XX6xNmmRdydTSRYLpY8'; // 6 | const DVM_ADDRESS = '0xE2faa0277EF9264C8AFc10A556D438C54a718B07'; // 5ELRpquT7C3mWtjesi2zZxs2L1n22HZJqW8qLyE4yZL7KNsB 7 | 8 | it('should get account id', () => { 9 | const account = dvmAddressToAccountId(DVM_ADDRESS); 10 | 11 | expect(account.toString()).to.eq('5ELRpquT7C3mWtjesi2zZxs2L1n22HZJqW8qLyE4yZL7KNsB'); 12 | }); 13 | 14 | it('should convert ss58 address to dvm address', () => { 15 | const address = convertToDvm(SS58_ACCOUNT); 16 | 17 | expect(address).to.eq('0xa09c083ca783d2f2621ae7e2ee8d285c8cf103303f309b031521967db57bda14'); 18 | }); 19 | 20 | it('should predicate address correctly', () => { 21 | expect(isSS58Address(SS58_ACCOUNT)).to.eq(true); 22 | expect(isSS58Address(DVM_ADDRESS)).to.eq(false); 23 | }); 24 | -------------------------------------------------------------------------------- /src/assets/images/icon_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 矩形 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/images/icon_down_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 矩形 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/images/subscan_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/src/assets/images/subscan_logo.png -------------------------------------------------------------------------------- /src/components/Fee.tsx: -------------------------------------------------------------------------------- 1 | import { SyncOutlined } from '@ant-design/icons'; 2 | import { SubmittableExtrinsic } from '@polkadot/api/promise/types'; 3 | import React, { useEffect } from 'react'; 4 | import { useTranslation } from 'react-i18next'; 5 | import { useFee } from '../hooks'; 6 | 7 | interface FeeProps { 8 | extrinsic?: SubmittableExtrinsic; 9 | } 10 | 11 | export function Fee({ extrinsic }: FeeProps) { 12 | const { t } = useTranslation(); 13 | const { fee, calcFee, setFee } = useFee(); 14 | 15 | useEffect(() => { 16 | if (extrinsic) { 17 | calcFee(extrinsic); 18 | } else { 19 | setFee('Insufficient parameters'); 20 | } 21 | }, [calcFee, extrinsic, setFee]); 22 | 23 | return {fee === 'calculating' ? : t(fee)}; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/icons/MoonIcon.tsx: -------------------------------------------------------------------------------- 1 | import IcomoonReact from 'icomoon-react'; 2 | 3 | import iconSet from '../../icomoon-selection.json'; 4 | 5 | export const MoonIcon = (props: any) => ; 6 | -------------------------------------------------------------------------------- /src/components/icons/export.tsx: -------------------------------------------------------------------------------- 1 | import { svgIconFactory } from './icon-factory'; 2 | 3 | function Export(props: any) { 4 | return ( 5 | 12 | 13 | 14 | 15 | 20 | 25 | 26 | ); 27 | } 28 | 29 | export const ExportIcon = svgIconFactory(Export); 30 | -------------------------------------------------------------------------------- /src/components/icons/import.tsx: -------------------------------------------------------------------------------- 1 | import { svgIconFactory } from './icon-factory'; 2 | 3 | function Import(props: any) { 4 | return ( 5 | 12 | 13 | 14 | 15 | 20 | 25 | 26 | ); 27 | } 28 | 29 | export const ImportIcon = svgIconFactory(Import); 30 | -------------------------------------------------------------------------------- /src/components/icons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './close'; 2 | export * from './copy'; 3 | export * from './donate'; 4 | export * from './down'; 5 | export * from './earth'; 6 | export * from './email'; 7 | export * from './right-circle'; 8 | export * from './swap'; 9 | export * from './swap-crab'; 10 | export * from './swap-pangolin'; 11 | export * from './view-browser'; 12 | export * from './add'; 13 | -------------------------------------------------------------------------------- /src/config/chains.ts: -------------------------------------------------------------------------------- 1 | import { NetworkConfigV2 } from 'src/model'; 2 | 3 | const configs = require.context('./chains', false, /\.json$/); 4 | 5 | const update: NetworkConfigV2 = {}; 6 | configs.keys().forEach((k) => { 7 | const c = configs(k); 8 | update[c.name] = c; 9 | }); 10 | 11 | export const chains = update; 12 | -------------------------------------------------------------------------------- /src/config/chains/heiko.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parallel-heiko", 3 | "displayName": "Parallel Heiko", 4 | "rpc": "wss://heiko-rpc.parallel.fi", 5 | "api": { 6 | "subql": "" 7 | }, 8 | "logo": "/image/parallel.svg", 9 | "explorerHostName": "parallel-heiko", 10 | "themeColor": "#42d5de" 11 | } 12 | -------------------------------------------------------------------------------- /src/config/chains/interlay.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interlay", 3 | "displayName": "Interlay", 4 | "rpc": "wss://api.interlay.io:443/parachain", 5 | "api": { 6 | "subql": "" 7 | }, 8 | "logo": "/image/interlay.png", 9 | "explorerHostName": "interlay", 10 | "themeColor": "#075ABC" 11 | } 12 | -------------------------------------------------------------------------------- /src/config/chains/kintsugi.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kintsugi", 3 | "displayName": "Kintsugi", 4 | "rpc": "wss://api-kusama.interlay.io:443/parachain", 5 | "api": { 6 | "subql": "" 7 | }, 8 | "logo": "/image/kintsugi.png", 9 | "explorerHostName": "kintsugi", 10 | "themeColor": "#041333" 11 | } 12 | -------------------------------------------------------------------------------- /src/config/chains/kusama.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kusama", 3 | "displayName": "Kusama", 4 | "rpc": "wss://kusama-rpc.dwellir.com", 5 | "api": { 6 | "subql": "https://api.subquery.network/sq/itering/multisig-kusama", 7 | "subscan": "https://kusama.webapi.subscan.io" 8 | }, 9 | "logo": "/image/kusama-button-mobile.png", 10 | "explorerHostName": "kusama", 11 | "themeColor": "#000000" 12 | } 13 | -------------------------------------------------------------------------------- /src/config/chains/parallel.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parallel", 3 | "displayName": "Parallel", 4 | "rpc": "wss://rpc.parallel.fi", 5 | "api": { 6 | "subql": "" 7 | }, 8 | "logo": "/image/parallel.svg", 9 | "explorerHostName": "parallel", 10 | "themeColor": "#ef18ac" 11 | } 12 | -------------------------------------------------------------------------------- /src/config/chains/polkadot.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polkadot", 3 | "displayName": "Polkadot", 4 | "rpc": "wss://rpc.polkadot.io", 5 | "api": { 6 | "subql": "https://api.subquery.network/sq/itering/multisig-polkadot", 7 | "subscan": "https://polkadot.webapi.subscan.io" 8 | }, 9 | "logo": "/image/polkadot-button-mobile.png", 10 | "explorerHostName": "polkadot", 11 | "themeColor": "#e6007a" 12 | } 13 | -------------------------------------------------------------------------------- /src/config/constant.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-magic-numbers */ 2 | export const LONG_DURATION = 10 * 1000; 3 | 4 | export const DONATE_ADDRESS = '14RYaXRSqb9rPqMaAVp1UZW2czQ6dMNGMbvukwfifi6m8ZgZ'; 5 | -------------------------------------------------------------------------------- /src/config/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import detector from 'i18next-browser-languagedetector'; 3 | import Backend from '@polkadot/react-components/i18n/Backend'; 4 | import { initReactI18next } from 'react-i18next'; 5 | 6 | i18n 7 | .use(detector) 8 | .use(Backend) 9 | .use(initReactI18next) // passes i18n down to react-i18next 10 | .init({ 11 | fallbackLng: 'en', 12 | debug: false, 13 | saveMissing: true, 14 | interpolation: { 15 | escapeValue: false, // react already safes from xss 16 | }, 17 | backend: {}, 18 | ns: ['react-components', 'react-params', 'react-query', 'react-signer'], 19 | }); 20 | 21 | export default i18n; 22 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constant'; 2 | export * from './i18n'; 3 | export * from './network'; 4 | export * from './query'; 5 | export * from './theme'; 6 | export * from './types'; 7 | export * from './validate-msg'; 8 | export * from './chains'; 9 | -------------------------------------------------------------------------------- /src/config/routes.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { RouteProps } from 'react-router-dom'; 3 | import { Home } from '../pages/Home'; 4 | 5 | const Extrinsic = React.lazy(() => import('../pages/Extrinsic')); 6 | const Wallet = React.lazy(() => import('../pages/Wallet')); 7 | const PolkadotJs = React.lazy(() => import('../pages/PolkadotJs')); 8 | 9 | export enum Path { 10 | root = '/', 11 | wallet = '/wallet', 12 | extrinsic = '/account', 13 | polkadotjs = '/ci/polkadotjs', 14 | } 15 | 16 | export const routes: (RouteProps & { disable?: boolean })[] = [ 17 | { 18 | exact: true, 19 | path: '/', 20 | component: Home, 21 | }, 22 | { 23 | exact: true, 24 | path: '/wallet', 25 | component: Wallet, 26 | }, 27 | { 28 | exact: true, 29 | path: '/account/:account', 30 | component: Extrinsic, 31 | }, 32 | { 33 | exact: true, 34 | path: '/ci/polkadotjs', 35 | component: PolkadotJs, 36 | disable: !process.env.REACT_APP_MULTISIG_MEMBER_MNEMONICS, 37 | }, 38 | { 39 | exact: true, 40 | path: '*', 41 | component: Home, 42 | }, 43 | ]; 44 | -------------------------------------------------------------------------------- /src/config/theme.ts: -------------------------------------------------------------------------------- 1 | import dark from '../theme/antd/dark.json'; 2 | import light from '../theme/antd/light.json'; 3 | import vars from '../theme/antd/vars.json'; 4 | import { chains } from '.'; 5 | 6 | export type ThemeVariable = 7 | | '@btn-border-radius-base' 8 | | '@btn-default-bg' 9 | | '@btn-default-border' 10 | | '@btn-primary-bg' 11 | | '@card-radius' 12 | | '@layout-header-background' 13 | | '@project-main-bg' 14 | | '@project-radius-base' 15 | | '@tabs-active-color' 16 | | '@tabs-highlight-color' 17 | | '@tabs-hover-color' 18 | | '@tabs-ink-bar-color' 19 | | '@link-color'; 20 | 21 | export const SKIN_THEME = { 22 | dark, 23 | light, 24 | vars, 25 | }; 26 | 27 | export enum THEME { 28 | LIGHT = 'light', 29 | DARK = 'dark', 30 | } 31 | 32 | export function getThemeColor(network: string) { 33 | let networkTheme = chains[network]; 34 | if (!networkTheme) { 35 | networkTheme = chains['polkadot']; 36 | } 37 | 38 | return networkTheme?.themeColor; 39 | } 40 | 41 | export function getLinkColor(_: string) { 42 | return '#4572DE'; 43 | } 44 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: Record; 3 | } 4 | 5 | export declare global { 6 | interface Window { 7 | /* eslint-disable */ 8 | less: any; 9 | web3: Web3; 10 | ethereum: any; 11 | /* eslint-enable */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/api.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { ApiContext, ApiCtx } from '../providers'; 3 | 4 | export const useApi = () => useContext(ApiContext) as Exclude; 5 | -------------------------------------------------------------------------------- /src/hooks/cancelablePromise.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { useMountedState } from './mountedState'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | export const useCancelablePromise = () => { 6 | const isMounted = useMountedState(); 7 | 8 | return useCallback( 9 | (promise: Promise, onCancel?: () => void) => 10 | new Promise((resolve, reject) => { 11 | promise 12 | .then((result: T) => { 13 | if (isMounted()) { 14 | resolve(result); 15 | } 16 | }) 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | .catch((error: any) => { 19 | if (isMounted()) { 20 | reject(error); 21 | } 22 | }) 23 | .finally(() => { 24 | if (!isMounted() && onCancel) { 25 | onCancel(); 26 | } 27 | }); 28 | }), 29 | [isMounted] 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/hooks/contact.ts: -------------------------------------------------------------------------------- 1 | import keyring from '@polkadot/ui-keyring'; 2 | import { KeyringAddress } from '@polkadot/ui-keyring/types'; 3 | import { useCallback, useEffect, useState } from 'react'; 4 | import { useApi } from './api'; 5 | 6 | export function useContacts() { 7 | const { networkStatus } = useApi(); 8 | 9 | const [contacts, setContacts] = useState(null); 10 | 11 | const queryContacts = useCallback(() => { 12 | const keyringAddresses = keyring.getAddresses(); 13 | setContacts(keyringAddresses); 14 | }, []); 15 | 16 | useEffect(() => { 17 | if (networkStatus !== 'success') { 18 | return; 19 | } 20 | queryContacts(); 21 | }, [networkStatus, queryContacts]); 22 | 23 | return { contacts, queryContacts }; 24 | } 25 | -------------------------------------------------------------------------------- /src/hooks/fee.ts: -------------------------------------------------------------------------------- 1 | import { SubmittableExtrinsic } from '@polkadot/api/promise/types'; 2 | import { useCallback, useState } from 'react'; 3 | import { accuracyFormat } from '../utils'; 4 | import { useApi } from './api'; 5 | import { useMultisig } from './multisig'; 6 | 7 | export function useFee() { 8 | const { chain } = useApi(); 9 | const { multisigAccount } = useMultisig(); 10 | const [fee, setFee] = useState(''); 11 | const calcFee = useCallback( 12 | async (tx: SubmittableExtrinsic) => { 13 | // eslint-disable-next-line 14 | // @ts-ignore 15 | const { partialFee } = await tx?.paymentInfo(multisigAccount?.address); 16 | const { decimal, symbol } = chain.tokens[0]; 17 | 18 | setFee(accuracyFormat(partialFee?.toJSON(), decimal) + ' ' + symbol); 19 | }, 20 | [chain.tokens, multisigAccount?.address] 21 | ); 22 | 23 | return { fee, calcFee, setFee }; 24 | } 25 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './cancelablePromise'; 3 | export * from './fee'; 4 | export * from './injected'; 5 | export * from './multiApprove'; 6 | export * from './multisig'; 7 | export * from './contact'; 8 | export * from './combineQuery'; 9 | -------------------------------------------------------------------------------- /src/hooks/injected.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { convertToSS58 } from '../utils'; 3 | import { useApi } from './api'; 4 | 5 | export function useIsInjected() { 6 | const { accounts: extensionAccounts } = useApi(); 7 | 8 | return useCallback( 9 | (address) => extensionAccounts?.find((acc) => convertToSS58(acc.address, 0) === convertToSS58(address, 0)), 10 | [extensionAccounts] 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/hooks/mountedState.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useCallback } from 'react'; 2 | 3 | export const useMountedState = () => { 4 | const mountedRef = useRef(false); 5 | 6 | useEffect(() => { 7 | mountedRef.current = true; 8 | return () => { 9 | mountedRef.current = false; 10 | }; 11 | }, []); 12 | 13 | return useCallback(() => mountedRef.current, []); 14 | }; 15 | -------------------------------------------------------------------------------- /src/hooks/multisigContext.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { MultisigContext } from '../providers/multisig-provider'; 3 | 4 | export const useMultisigContext = () => useContext(MultisigContext); 5 | -------------------------------------------------------------------------------- /src/model/account.ts: -------------------------------------------------------------------------------- 1 | import type ExtType from '@polkadot/extension-inject/types'; 2 | import { WithOptional } from './common'; 3 | import { Network } from './network'; 4 | 5 | export type IAccountMeta = WithOptional; 6 | 7 | export type InjectedAccountWithMeta = ExtType.InjectedAccountWithMeta; 8 | 9 | export interface AddressPair { 10 | address: string; 11 | name: string; 12 | key: number; 13 | } 14 | 15 | interface Member { 16 | name: string; 17 | address: string; 18 | } 19 | 20 | export enum ShareScope { 21 | all = 1, 22 | current, 23 | custom, 24 | } 25 | 26 | export interface WalletFormValue { 27 | name: string; 28 | threshold: number; 29 | members: Member[]; 30 | share: ShareScope; 31 | scope?: Network[]; 32 | rememberExternal: boolean; 33 | } 34 | 35 | export interface ContactFormValue { 36 | name: string; 37 | address: string; 38 | } 39 | 40 | export interface StoredScope { 41 | publicKey: string; 42 | scope: Network[]; 43 | } 44 | 45 | export interface MultisigAccountConfig { 46 | name: string; 47 | members: { 48 | name: string; 49 | address: string; 50 | }[]; 51 | threshold: number; 52 | scope: Network[] | ShareScope; 53 | } 54 | -------------------------------------------------------------------------------- /src/model/common.ts: -------------------------------------------------------------------------------- 1 | export interface Action { 2 | type: U; 3 | payload: T; 4 | } 5 | 6 | export type Config = { [key in T]: U }; 7 | 8 | export type Assets = 'ring' | 'kton'; 9 | 10 | export type WithOptional = Omit & Partial>; 11 | -------------------------------------------------------------------------------- /src/model/index.ts: -------------------------------------------------------------------------------- 1 | export * from './account'; 2 | export * from './common'; 3 | export * from './network'; 4 | export * from './storage'; 5 | export * from './tx'; 6 | export * from './util'; 7 | -------------------------------------------------------------------------------- /src/model/storage.ts: -------------------------------------------------------------------------------- 1 | import { THEME } from '../config'; 2 | import { Network, NetConfigV2 } from './network'; 3 | 4 | export interface StorageInfo { 5 | network?: Network; 6 | theme?: THEME; 7 | customNetwork?: NetConfigV2; 8 | addedCustomNetworks?: NetConfigV2[]; 9 | selectedRpc?: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/model/tx.ts: -------------------------------------------------------------------------------- 1 | import { AnyJson } from '@polkadot/types/types'; 2 | import { PartialQueueTxExtrinsic } from '@polkadot/react-components/Status/types'; 3 | import { KeyringAddress } from '@polkadot/ui-keyring/types'; 4 | 5 | export interface When { 6 | height: number; 7 | index: number; 8 | } 9 | 10 | export interface Entry { 11 | when: When; 12 | depositor: string; 13 | approvals: string[]; 14 | address: string; 15 | callHash: string | null; 16 | blockHash?: string; 17 | extrinsicIdx?: string; 18 | // callData: Call | null; 19 | callDataJson: any; 20 | meta: Record | null; 21 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 22 | [key: string]: any; 23 | } 24 | 25 | export type TxActionType = 'pending' | 'approve' | 'cancel' | 'execute'; 26 | 27 | interface TxOperation { 28 | type: TxActionType; 29 | entry: Entry | null; 30 | accounts: string[]; 31 | } 32 | 33 | export interface TxOperationComponentProps { 34 | account: KeyringAddress; 35 | entry: Entry; 36 | txSpy?: (tx: PartialQueueTxExtrinsic | null) => void; 37 | onOperation?: (operation: TxOperation) => void; 38 | beforeOperation?: (operation: TxOperation, cb: () => void) => void; 39 | isExecute?: boolean; 40 | } 41 | -------------------------------------------------------------------------------- /src/model/util.ts: -------------------------------------------------------------------------------- 1 | export type ValueOf = T[keyof T]; 2 | -------------------------------------------------------------------------------- /src/packages/react-api/.skip-build: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/src/packages/react-api/.skip-build -------------------------------------------------------------------------------- /src/packages/react-api/.skip-npm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/src/packages/react-api/.skip-npm -------------------------------------------------------------------------------- /src/packages/react-api/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/react-api 2 | 3 | WARNING: This is is not deemed stable yet for external use in React-based apps and still included in the [polkadot-js/apps](https://github.com/polkadot-js/apps) repo. Since these are generic HOC components for React, they will move to the [polkadot-js/ui](https://github.com/polkadot-js/ui) repo once deemed stable and usable by external projects. 4 | 5 | For the existing sharable components usable in external React-based projects, take a look at the [polkadot-js/ui documentation](https://polkadot.js.org/ui/) 6 | -------------------------------------------------------------------------------- /src/packages/react-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polkadot/react-api", 3 | "private": true, 4 | "type": "module", 5 | "version": "0.93.2-0", 6 | "license": "Apache-2.0", 7 | "dependencies": { 8 | "@babel/runtime": "^7.14.0", 9 | "@polkadot/api": "^4.14.1", 10 | "@polkadot/extension-dapp": "^0.38.5", 11 | "@polkadot/x-rxjs": "^6.8.1", 12 | "fflate": "^0.7.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/packages/react-api/src/ApiContext.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import React from 'react'; 5 | import type { ApiProps } from './types'; 6 | 7 | const ApiContext: React.Context = React.createContext({} as unknown as ApiProps); 8 | const ApiConsumer: React.Consumer = ApiContext.Consumer; 9 | const ApiProvider: React.Provider = ApiContext.Provider; 10 | 11 | export default ApiContext; 12 | 13 | export { ApiConsumer, ApiProvider }; 14 | -------------------------------------------------------------------------------- /src/packages/react-api/src/hoc/calls.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import React from 'react'; 6 | import type { ApiProps, SubtractProps } from '../types'; 7 | import withCall from './call'; 8 | import type { Options } from './types'; 9 | 10 | type Call = string | [string, Options]; 11 | 12 | export default function withCalls

( 13 | ...calls: Call[] 14 | ): (Component: React.ComponentType

) => React.ComponentType> { 15 | return (Component: React.ComponentType

): React.ComponentType => { 16 | // NOTE: Order is reversed so it makes sense in the props, i.e. component 17 | // after something can use the value of the preceding version 18 | return calls.reverse().reduce((Comp, call): React.ComponentType => { 19 | return Array.isArray(call) ? withCall(...call)(Comp as any) : withCall(call)(Comp as any); 20 | }, Component); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/packages/react-api/src/hoc/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default as withApi } from './api'; 5 | export { default as withCall } from './call'; 6 | export { default as withCalls } from './calls'; 7 | export { default as withCallDiv } from './callDiv'; 8 | export { default as withMulti } from './multi'; 9 | export { default as withObservable } from './observable'; 10 | export * from './onlyOn'; 11 | -------------------------------------------------------------------------------- /src/packages/react-api/src/hoc/multi.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import React from 'react'; 6 | 7 | type HOC = (Component: React.ComponentType) => React.ComponentType; 8 | 9 | export default function withMulti(Component: React.ComponentType, ...hocs: HOC[]): React.ComponentType { 10 | // NOTE: Order is reversed so it makes sense in the props, i.e. component 11 | // after something can use the value of the preceding version 12 | return hocs.reverse().reduce((Comp, hoc): React.ComponentType => hoc(Comp), Component); 13 | } 14 | -------------------------------------------------------------------------------- /src/packages/react-api/src/hoc/onlyOn.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/app-accounts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | /* eslint-disable @typescript-eslint/no-explicit-any */ 4 | 5 | import { ComponentType } from 'react'; 6 | import type { Environment } from '../types'; 7 | import { getEnvironment } from '../util'; 8 | 9 | const onlyOn = 10 | (environment: Environment) => 11 | >(component: T): T | (() => null) => { 12 | if (getEnvironment() === environment) { 13 | return component; 14 | } 15 | 16 | return () => null; 17 | }; 18 | 19 | export const onlyOnWeb = onlyOn('web'); 20 | export const onlyOnApp = onlyOn('app'); 21 | -------------------------------------------------------------------------------- /src/packages/react-api/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import Api, { api, DEFAULT_DECIMALS, DEFAULT_SS58 } from './Api'; 5 | import ApiContext from './ApiContext'; 6 | import { withApi, withCallDiv, withCalls, withMulti, withObservable } from './hoc'; 7 | 8 | export { 9 | api, 10 | Api, 11 | ApiContext, 12 | DEFAULT_DECIMALS, 13 | DEFAULT_SS58, 14 | withApi, 15 | withCalls, 16 | withCallDiv, 17 | withMulti, 18 | withObservable, 19 | }; 20 | -------------------------------------------------------------------------------- /src/packages/react-api/src/transform/echo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 5 | export default function echoTransform(x: T, _: number): T { 6 | return x; 7 | } 8 | -------------------------------------------------------------------------------- /src/packages/react-api/src/typeRegistry.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { TypeRegistry } from '@polkadot/types/create'; 5 | 6 | const registry = new TypeRegistry(); 7 | 8 | export default registry; 9 | -------------------------------------------------------------------------------- /src/packages/react-api/src/util/getEnvironment.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | // Copyright 2017-2021 @polkadot/app-accounts authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import type { Environment } from '../types'; 6 | 7 | // https://github.com/electron/electron/issues/2288 8 | function isElectron() { 9 | if ((process?.versions as any)?.electron) { 10 | return true; 11 | } 12 | 13 | if ((window?.process as any)?.type === 'renderer') { 14 | return true; 15 | } 16 | 17 | return navigator?.userAgent?.indexOf('Electron') >= 0; 18 | } 19 | 20 | export default function getEnvironment(): Environment { 21 | if (isElectron()) { 22 | return 'app'; 23 | } 24 | 25 | return 'web'; 26 | } 27 | -------------------------------------------------------------------------------- /src/packages/react-api/src/util/historic.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Hash } from '@polkadot/types/interfaces'; 5 | import type { Codec } from '@polkadot/types/types'; 6 | 7 | // eslint-disable-next-line 8 | type AtQuery = (hash: string | Uint8Array, ...params: I) => Promise; 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | export default async function getHistoric( 12 | atQuery: AtQuery, 13 | params: I, 14 | hashes: Hash[] 15 | ): Promise<[Hash, T][]> { 16 | return Promise.all(hashes.map((hash): Promise => atQuery(hash, ...params) as Promise)).then( 17 | (results): [Hash, T][] => results.map((value, index): [Hash, T] => [hashes[index], value]) 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/packages/react-api/src/util/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import getEnvironment from './getEnvironment'; 5 | import getHistoric from './historic'; 6 | import intervalObservable from './intervalObservable'; 7 | import isEqual from './isEqual'; 8 | import triggerChange from './triggerChange'; 9 | 10 | export { getHistoric, intervalObservable, isEqual, triggerChange, getEnvironment }; 11 | -------------------------------------------------------------------------------- /src/packages/react-api/src/util/intervalObservable.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-magic-numbers */ 2 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | import { interval, Subscription } from '@polkadot/x-rxjs'; 6 | import type { CallState } from '../types'; 7 | 8 | const interval$ = interval(500); 9 | 10 | export default function intervalObservable( 11 | that: React.Component 12 | ): Subscription { 13 | return interval$.subscribe((): void => { 14 | const elapsed = Date.now() - ((that.state.callUpdatedAt as number) || 0); 15 | const callUpdated = elapsed <= 1500; 16 | 17 | if (callUpdated !== that.state.callUpdated) { 18 | that.setState({ 19 | callUpdated, 20 | }); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/packages/react-api/src/util/isEqual.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | function flatten(key: string | null, value?: unknown): unknown { 5 | return !value 6 | ? value 7 | : (value as Record).$$typeof 8 | ? '' 9 | : Array.isArray(value) 10 | ? value.map((item) => flatten(null, item)) 11 | : value; 12 | } 13 | 14 | export default function isEqual(a?: T, b?: T): boolean { 15 | return JSON.stringify({ test: a }, flatten) === JSON.stringify({ test: b }, flatten); 16 | } 17 | -------------------------------------------------------------------------------- /src/packages/react-api/src/util/triggerChange.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { isFunction, isObservable } from '@polkadot/util'; 5 | import type { OnChangeCb } from '../types'; 6 | 7 | export default function triggerChange(value?: unknown, ...callOnResult: (OnChangeCb | undefined)[]): void { 8 | if (!callOnResult || !callOnResult.length) { 9 | return; 10 | } 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-shadow 13 | callOnResult.forEach((callOnResult): void => { 14 | if (isObservable(callOnResult)) { 15 | callOnResult.next(value); 16 | } else if (isFunction(callOnResult)) { 17 | callOnResult(value); 18 | } 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/packages/react-api/test/enzyme.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | const Enzyme = require('enzyme'); 7 | 8 | Enzyme.configure({ 9 | adapter: new Adapter(), 10 | }); 11 | 12 | module.exports = Enzyme; 13 | -------------------------------------------------------------------------------- /src/packages/react-api/test/observable.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-magic-numbers */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | // Copyright 2017-2021 @polkadot/react-api authors & contributors 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | const createObservable = require('@polkadot/api-rx/observable'); 7 | 8 | module.exports = function observable(method) { 9 | const fn = () => Promise.resolve(12345); 10 | 11 | fn.unsubscribe = () => Promise.resolve(true); 12 | 13 | return createObservable(`section_${method}`, method, { 14 | [method]: fn, 15 | })(); 16 | }; 17 | -------------------------------------------------------------------------------- /src/packages/react-components/.skip-build: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/src/packages/react-components/.skip-build -------------------------------------------------------------------------------- /src/packages/react-components/.skip-npm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subscan-explorer/subscan-multisig-react/20c580c14fd077ab6327ced9a573973330d47324/src/packages/react-components/.skip-npm -------------------------------------------------------------------------------- /src/packages/react-components/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/react-components 2 | 3 | WARNING: This is an internal package to [polkadot-js/apps](https://github.com/polkadot-js/apps) so is not inteded (yet) for broad use. 4 | 5 | For the existing sharable components usable in external React-based projects, take a look at the [polkadot-js/ui documentation](https://polkadot.js.org/ui/) 6 | -------------------------------------------------------------------------------- /src/packages/react-components/src/Available.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-components authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces'; 5 | 6 | import React from 'react'; 7 | 8 | import { Available } from '@polkadot/react-query'; 9 | 10 | export interface Props { 11 | className?: string; 12 | label?: React.ReactNode; 13 | params?: AccountId | AccountIndex | Address | string | Uint8Array | null; 14 | } 15 | 16 | function AvailableDisplay({ className = '', label, params }: Props): React.ReactElement | null { 17 | if (!params) { 18 | return null; 19 | } 20 | 21 | return ; 22 | } 23 | 24 | export default React.memo(AvailableDisplay); 25 | -------------------------------------------------------------------------------- /src/packages/react-components/src/BatchWarning.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/app-accounts authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import React from 'react'; 5 | 6 | import { isFunction } from '@polkadot/util'; 7 | import { useApi } from '@polkadot/react-hooks'; 8 | 9 | import { useTranslation } from './translate'; 10 | import { MarkWarning } from '.'; 11 | 12 | function BatchWarning(): React.ReactElement | null { 13 | const { t } = useTranslation(); 14 | const { api } = useApi(); 15 | 16 | if (isFunction(api.tx.utility.batchAll)) { 17 | return null; 18 | } 19 | 20 | return ( 21 | ( 23 | 'This chain does not yet support atomic batch operations. This means that if the transaction gets executed and one of the operations do fail (due to invalid data or lack of available funds) some of the changes made may not be applied.' 24 | )} 25 | /> 26 | ); 27 | } 28 | 29 | export default React.memo(BatchWarning); 30 | -------------------------------------------------------------------------------- /src/packages/react-components/src/Bonded.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-components authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type BN from 'bn.js'; 5 | import type { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces'; 6 | 7 | import React from 'react'; 8 | 9 | import { Bonded } from '@polkadot/react-query'; 10 | 11 | import { renderProvided } from './Balance'; 12 | 13 | export interface Props { 14 | bonded?: BN | BN[]; 15 | className?: string; 16 | label?: React.ReactNode; 17 | params?: AccountId | AccountIndex | Address | string | Uint8Array | null; 18 | withLabel?: boolean; 19 | } 20 | 21 | function BondedDisplay(props: Props): React.ReactElement | null { 22 | const { bonded, className = '', label, params } = props; 23 | 24 | if (!params) { 25 | return null; 26 | } 27 | 28 | return bonded ? ( 29 | <>{renderProvided({ className, label, value: bonded })} 30 | ) : ( 31 | 32 | ); 33 | } 34 | 35 | export default React.memo(BondedDisplay); 36 | -------------------------------------------------------------------------------- /src/packages/react-components/src/Button/Group.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-components authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import React from 'react'; 5 | import styled from 'styled-components'; 6 | import type { GroupProps } from './types'; 7 | 8 | function ButtonGroup({ children, className = '', isCentered }: GroupProps): React.ReactElement { 9 | return

{children}
; 10 | } 11 | 12 | export default React.memo(styled(ButtonGroup)` 13 | margin: 1rem 0; 14 | text-align: right; 15 | 16 | &.isCentered { 17 | margin-bottom: 0.5rem; 18 | text-align: center; 19 | } 20 | 21 | & + .ui--Table { 22 | margin-top: 1.5rem; 23 | } 24 | 25 | .ui--Button { 26 | margin: 0 0.25rem; 27 | } 28 | 29 | .ui--CopyButton { 30 | display: inline-block; 31 | } 32 | `); 33 | -------------------------------------------------------------------------------- /src/packages/react-components/src/Button/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-components authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { ButtonType } from './types'; 5 | 6 | import IButton from './Button'; 7 | import Group from './Group'; 8 | 9 | const Button = IButton as unknown as ButtonType; 10 | 11 | Button.Group = Group; 12 | 13 | export default Button; 14 | -------------------------------------------------------------------------------- /src/packages/react-components/src/Button/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-components authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { IconName } from '@fortawesome/fontawesome-svg-core'; 5 | import type { BareProps } from '../types'; 6 | 7 | export type Button$Callback = () => void | Promise; 8 | 9 | export interface ButtonProps { 10 | children?: React.ReactNode; 11 | className?: string; 12 | dataTestId?: string; 13 | icon: IconName; 14 | isBasic?: boolean; 15 | isBusy?: boolean; 16 | isCircular?: boolean; 17 | isDisabled?: boolean; 18 | isFull?: boolean; 19 | isIcon?: boolean; 20 | isSelected?: boolean; 21 | isToplevel?: boolean; 22 | label?: React.ReactNode; 23 | onClick?: Button$Callback; 24 | onMouseEnter?: Button$Callback; 25 | onMouseLeave?: Button$Callback; 26 | tabIndex?: number; 27 | tooltip?: React.ReactNode; 28 | withoutLink?: boolean; 29 | } 30 | 31 | export type DividerProps = BareProps; 32 | 33 | export interface GroupProps { 34 | children?: React.ReactNode; 35 | className?: string; 36 | isCentered?: boolean; 37 | } 38 | 39 | export type ButtonType = React.ComponentType & { 40 | Group: React.ComponentType; 41 | }; 42 | -------------------------------------------------------------------------------- /src/packages/react-components/src/ButtonCancel.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2021 @polkadot/react-components authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import React from 'react'; 5 | 6 | import Button from './Button'; 7 | import { useTranslation } from './translate'; 8 | 9 | interface Props { 10 | className?: string; 11 | isDisabled?: boolean; 12 | label?: string; 13 | onClick: () => void; 14 | tabIndex?: number; 15 | } 16 | 17 | function ButtonCancel({ className = '', isDisabled, label, onClick, tabIndex }: Props): React.ReactElement { 18 | const { t } = useTranslation(); 19 | 20 | return ( 21 |