├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── apps
├── app
│ ├── .gitignore
│ ├── README.md
│ ├── assets
│ │ ├── asset.svg
│ │ ├── b1.png
│ │ ├── khala_claimer_abi.ts
│ │ ├── nfts.svg
│ │ ├── nova_logo.png
│ │ ├── pha_vault_abi.ts
│ │ ├── phala_logo.svg
│ │ ├── powered_by_slpx.png
│ │ ├── share_equation.png
│ │ ├── stake_pool.svg
│ │ ├── stake_pool_detailed.svg
│ │ ├── subwallet_logo.png
│ │ ├── vault.svg
│ │ ├── vault_detailed.svg
│ │ ├── whitelists.svg
│ │ ├── wikiData.ts
│ │ ├── withdraw_queue.svg
│ │ └── worker.svg
│ ├── components
│ │ ├── AssetTransfer.tsx
│ │ ├── BasePool
│ │ │ ├── Chart.tsx
│ │ │ ├── ClaimDelegation.tsx
│ │ │ ├── ClaimReward.tsx
│ │ │ ├── CreateButton.tsx
│ │ │ ├── DelegateCard.tsx
│ │ │ ├── DelegateInput.tsx
│ │ │ ├── DetailPage.tsx
│ │ │ ├── ExtraProperties.tsx
│ │ │ ├── FarmCard.tsx
│ │ │ ├── Identity.tsx
│ │ │ ├── Intro.tsx
│ │ │ ├── List.tsx
│ │ │ ├── OwnerSettings.tsx
│ │ │ ├── Whitelist
│ │ │ │ ├── AddWhitelist.tsx
│ │ │ │ └── List.tsx
│ │ │ ├── WithdrawQueue.tsx
│ │ │ └── Worker
│ │ │ │ ├── AddWorker.tsx
│ │ │ │ ├── Card.tsx
│ │ │ │ ├── ChangeStake.tsx
│ │ │ │ └── List.tsx
│ │ ├── ClaimKhalaAssets.tsx
│ │ ├── ClientOnly.tsx
│ │ ├── DashboardAccount.tsx
│ │ ├── DashboardAssetList.tsx
│ │ ├── DashboardCarousel.tsx
│ │ ├── DashboardNftList.tsx
│ │ ├── Delegation
│ │ │ ├── Chart.tsx
│ │ │ ├── HorizonCard.tsx
│ │ │ ├── List.tsx
│ │ │ ├── Menu.tsx
│ │ │ ├── NftCard.tsx
│ │ │ └── Withdraw.tsx
│ │ ├── DelegationDetailCard.tsx
│ │ ├── DelegationNftCover.tsx
│ │ ├── DelegationScatterChart.tsx
│ │ ├── DelegationValueChart.tsx
│ │ ├── DelegatorSelect.tsx
│ │ ├── DownloadHistory.tsx
│ │ ├── Empty.tsx
│ │ ├── FarmChart.tsx
│ │ ├── HydrateAtoms.tsx
│ │ ├── Layout.tsx
│ │ ├── ListSkeleton.tsx
│ │ ├── PageHeader.tsx
│ │ ├── PromiseButton.tsx
│ │ ├── Property.tsx
│ │ ├── RechartsTooltip.tsx
│ │ ├── ScrollTop.tsx
│ │ ├── SectionHeader.tsx
│ │ ├── SnackbarProvider.tsx
│ │ ├── Staking
│ │ │ ├── Stake.tsx
│ │ │ ├── Unstake.tsx
│ │ │ └── index.tsx
│ │ ├── SwitchChainButton.tsx
│ │ ├── TextSkeleton.tsx
│ │ ├── Title.tsx
│ │ ├── TopBar
│ │ │ ├── Account.tsx
│ │ │ ├── ChainStatus.tsx
│ │ │ ├── NetworkStats.tsx
│ │ │ └── index.tsx
│ │ ├── Vest.tsx
│ │ ├── WalletDialog.tsx
│ │ ├── Web3Provider.tsx
│ │ ├── Wiki
│ │ │ ├── Button.tsx
│ │ │ ├── Dialog.tsx
│ │ │ └── index.tsx
│ │ ├── WrapDecimal.tsx
│ │ └── ZendeskWidget.tsx
│ ├── config.ts
│ ├── hooks
│ │ ├── khalaAssets.ts
│ │ ├── staking.ts
│ │ ├── useAccountQuery.ts
│ │ ├── useAssetBalance.ts
│ │ ├── useAssetsMetadata.ts
│ │ ├── useAutoSwitchChain.ts
│ │ ├── useDebounced.ts
│ │ ├── useDelegationOneDayProfit.ts
│ │ ├── useGetApr.ts
│ │ ├── useGetAprMultiplier.ts
│ │ ├── useListenBlockHeight.ts
│ │ ├── useLockedWrappedBalance.ts
│ │ ├── useNotice.tsx
│ │ ├── usePolkadotApi.ts
│ │ ├── usePoolFavorite.ts
│ │ ├── usePoolIntro.ts
│ │ ├── useResetVault.ts
│ │ ├── useSelectedVaultState.ts
│ │ ├── useShowWalletDialog.ts
│ │ ├── useSignAndSend.tsx
│ │ ├── useToday.ts
│ │ └── useWrapAsset.ts
│ ├── lib
│ │ ├── apr.ts
│ │ ├── getBasePoolServerSideProps.ts
│ │ ├── getDelegationProfit.ts
│ │ ├── getPoolPath.ts
│ │ ├── getVaultOwnerCut.ts
│ │ ├── styles.ts
│ │ ├── subsquidQuery.ts
│ │ ├── subsquidSdk.ts
│ │ └── theme.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ ├── api
│ │ │ ├── [chain]
│ │ │ │ └── snapshots
│ │ │ │ │ ├── account
│ │ │ │ │ └── [account].ts
│ │ │ │ │ ├── delegation
│ │ │ │ │ └── [id].ts
│ │ │ │ │ └── pool
│ │ │ │ │ └── [pid].ts
│ │ │ └── circulation.ts
│ │ ├── delegate
│ │ │ ├── [kind].tsx
│ │ │ └── my-delegation.tsx
│ │ ├── farm
│ │ │ ├── stake-pool.tsx
│ │ │ └── vault.tsx
│ │ ├── index.tsx
│ │ ├── khala-assets.tsx
│ │ ├── stake-pool
│ │ │ └── [pid].tsx
│ │ ├── staking.tsx
│ │ ├── vault
│ │ │ └── [pid].tsx
│ │ └── wiki.tsx
│ ├── public
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── site.webmanifest
│ │ └── vpha.svg
│ ├── store
│ │ ├── common.ts
│ │ └── ui.ts
│ ├── tsconfig.json
│ └── types
│ │ ├── global.d.ts
│ │ ├── image.d.ts
│ │ ├── mui.d.ts
│ │ └── reset.d.ts
└── subbridge
│ ├── README.md
│ ├── assets
│ ├── evm_chains_data.json
│ ├── moonriver_xtokens_abi.json
│ ├── phala_brand_logo.svg
│ ├── subbridge_logo_icon.svg
│ ├── subbridge_logo_text.svg
│ ├── sygma_logo.svg
│ └── token_standard_abi.json
│ ├── components
│ ├── BridgeBody
│ │ ├── Action
│ │ │ ├── EvmAction.tsx
│ │ │ ├── PolkadotAction.tsx
│ │ │ └── index.tsx
│ │ ├── AmountInput.tsx
│ │ ├── AssetSelect.tsx
│ │ ├── ChainSelect.tsx
│ │ ├── DestinationAccountInput.tsx
│ │ ├── DestinationAccountWarning.tsx
│ │ ├── Extra.tsx
│ │ ├── PoweredBySygma.tsx
│ │ └── index.tsx
│ ├── BridgeConfirmationDialog.tsx
│ ├── BridgeHistory.tsx
│ ├── ClientOnly.tsx
│ ├── ConnectWalletButton.tsx
│ ├── ExplorerLink.tsx
│ ├── Footer.tsx
│ ├── GlobalStyles.tsx
│ ├── KhalaSunsetAlert.tsx
│ ├── Layout.tsx
│ ├── MuiThemeProvider.tsx
│ ├── PolkadotWalletDialog.tsx
│ └── TopBar
│ │ ├── Account
│ │ ├── AccountTemplate.tsx
│ │ ├── EvmAccount.tsx
│ │ ├── PolkadotAccount.tsx
│ │ └── index.tsx
│ │ ├── MoreButton.tsx
│ │ ├── SubbridgeLogo.tsx
│ │ └── index.tsx
│ ├── config
│ ├── asset.ts
│ ├── bridge.ts
│ ├── chain.ts
│ └── error.ts
│ ├── hooks
│ ├── useBalance.ts
│ ├── useBridgeFee.ts
│ ├── useEstimatedGasFee.ts
│ ├── useEthereumProviderInitialization.ts
│ ├── useEthersContract.ts
│ ├── useEthersProvider.ts
│ ├── usePolkadotApi.ts
│ ├── useSwitchNetwork.ts
│ ├── useTransfer.ts
│ └── useValidation.ts
│ ├── lib
│ ├── createEmotionCache.ts
│ ├── createPhalaMultilocation.ts
│ ├── ethersFetcher.ts
│ ├── evmSygma.ts
│ ├── getFullUrl.ts
│ ├── getGeneralKey.ts
│ ├── polkadotFetcher.ts
│ ├── transferByEvmSygma.ts
│ ├── transferByEvmXTokens.ts
│ ├── transferByPhalaXTransfer.ts
│ └── transferByPolkadotXTokens.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ ├── _app.tsx
│ ├── _document.tsx
│ └── index.tsx
│ ├── public
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── background_waves.svg
│ ├── favicon.ico
│ ├── favicon.svg
│ └── site.webmanifest
│ ├── store
│ ├── bridge.ts
│ ├── common.ts
│ ├── ethers.ts
│ └── polkadotWalletModal.ts
│ ├── tsconfig.json
│ └── types
│ ├── global.d.ts
│ └── svgr.d.ts
├── biome.json
├── bun.lock
├── bunfig.toml
├── lefthook.yml
├── package.json
├── packages
├── lib
│ ├── package.json
│ ├── src
│ │ ├── JotaiDevTools.tsx
│ │ ├── compactFormat.ts
│ │ ├── createPolkadotApi.ts
│ │ ├── getDecimalPattern.ts
│ │ ├── index.ts
│ │ ├── signAndSend.ts
│ │ ├── sleep.ts
│ │ ├── toCurrency.ts
│ │ ├── toFixed.ts
│ │ ├── toPercentage.ts
│ │ ├── transformSs58Format.ts
│ │ ├── trimAddress.ts
│ │ ├── useConnectPolkadotWallet.ts
│ │ ├── useInterval.ts
│ │ ├── useTimeout.ts
│ │ ├── validateAddress.ts
│ │ └── weightedAverage.ts
│ └── tsconfig.json
├── polkadot-types
│ ├── interfaces
│ │ ├── augment-api-consts.ts
│ │ ├── augment-api-errors.ts
│ │ ├── augment-api-events.ts
│ │ ├── augment-api-query.ts
│ │ ├── augment-api-rpc.ts
│ │ ├── augment-api-runtime.ts
│ │ ├── augment-api-tx.ts
│ │ ├── augment-api.ts
│ │ ├── augment-types.ts
│ │ ├── definitions.ts
│ │ ├── index.ts
│ │ ├── lookup.ts
│ │ ├── registry.ts
│ │ ├── types-lookup.ts
│ │ └── types.ts
│ ├── metadata.json
│ ├── package.json
│ ├── tsconfig.json
│ └── updateMetadata.ts
├── store
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── polkadotAccountAtoms.ts
│ │ └── walletAtoms.ts
│ └── tsconfig.json
└── ui
│ ├── icons
│ ├── asset
│ │ ├── aca.png
│ │ ├── astr.png
│ │ ├── ausd.png
│ │ ├── bnc.png
│ │ ├── bsx.png
│ │ ├── crab.png
│ │ ├── dot.png
│ │ ├── eth.png
│ │ ├── glmr.png
│ │ ├── hko.png
│ │ ├── kar.png
│ │ ├── kma.png
│ │ ├── movr.png
│ │ ├── para.png
│ │ ├── pha.png
│ │ ├── sdn.png
│ │ ├── tur.png
│ │ ├── vpha.png
│ │ ├── wglmr.png
│ │ └── zlk.png
│ └── chain
│ │ ├── acala.png
│ │ ├── astar.png
│ │ ├── astarevm.png
│ │ ├── base.png
│ │ ├── basilisk.png
│ │ ├── bifrost.png
│ │ ├── calamari.png
│ │ ├── crab.png
│ │ ├── ethereum.png
│ │ ├── karura.png
│ │ ├── khala.png
│ │ ├── moonbase_alpha.png
│ │ ├── moonbeam.png
│ │ ├── moonriver.png
│ │ ├── parallel.png
│ │ ├── parallel_heiko.png
│ │ ├── phala.png
│ │ ├── polkadot.png
│ │ ├── shiden.png
│ │ └── turing.png
│ ├── index.ts
│ ├── package.json
│ └── tsconfig.json
├── patches
└── @buildwithsygma%2Fsygma-sdk-core@2.11.2.patch
├── tsconfig.base.json
└── turbo.json
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 | workflow_dispatch:
11 |
12 | jobs:
13 | build:
14 | name: Test
15 | runs-on: ubuntu-latest
16 | env:
17 | TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
18 | TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | - uses: oven-sh/setup-bun@v2
23 | - run: bun install --frozen-lockfile
24 | - run: bunx biome ci .
25 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[javascript]": {
3 | "editor.defaultFormatter": "biomejs.biome"
4 | },
5 | "[javascriptreact]": {
6 | "editor.defaultFormatter": "biomejs.biome"
7 | },
8 | "[json]": {
9 | "editor.defaultFormatter": "biomejs.biome"
10 | },
11 | "[jsonc]": {
12 | "editor.defaultFormatter": "biomejs.biome"
13 | },
14 | "[typescript]": {
15 | "editor.defaultFormatter": "biomejs.biome"
16 | },
17 | "[typescriptreact]": {
18 | "editor.defaultFormatter": "biomejs.biome"
19 | },
20 | "biome.enabled": true,
21 | "cSpell.words": [
22 | "biomejs",
23 | "bunx",
24 | "crowdloan",
25 | "fontawesome",
26 | "fortawesome",
27 | "karura",
28 | "khala",
29 | "lefthook",
30 | "moonbase",
31 | "moonriver",
32 | "movr",
33 | "orml",
34 | "parachain",
35 | "phala",
36 | "polkadot",
37 | "preinstall",
38 | "readystate",
39 | "roboto",
40 | "staker",
41 | "stakers",
42 | "subbridge",
43 | "Subscan",
44 | "subsquid",
45 | "svgs",
46 | "sygma",
47 | "talismn",
48 | "tanstack",
49 | "tokenomic",
50 | "unsub",
51 | "xtokens",
52 | "connectkit",
53 | "wagmi",
54 | "walletconnect"
55 | ],
56 | "editor.codeActionsOnSave": {
57 | "quickfix.biome": "explicit",
58 | "source.addMissingImports": "explicit",
59 | "source.organizeImports.biome": "explicit"
60 | },
61 | "editor.formatOnSave": true,
62 | "files.associations": {
63 | "*.css": "tailwindcss"
64 | },
65 | "javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
66 | "javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
67 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
68 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
69 | "typescript.tsdk": "./node_modules/typescript/lib"
70 | }
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Phala Apps
2 |
3 | [](https://discord.gg/phala-network)
4 |
5 | Monorepo for apps in phala.
6 |
7 | ## Apps
8 |
9 | ### [Phala App](/apps/app)
10 |
11 | ### [SubBridge](/apps/subbridge)
12 |
--------------------------------------------------------------------------------
/apps/app/.gitignore:
--------------------------------------------------------------------------------
1 | # Sentry Config File
2 | .env.sentry-build-plugin
3 |
--------------------------------------------------------------------------------
/apps/app/README.md:
--------------------------------------------------------------------------------
1 | # [Phala App](https://app.phala.network)
2 |
--------------------------------------------------------------------------------
/apps/app/assets/b1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/assets/b1.png
--------------------------------------------------------------------------------
/apps/app/assets/nova_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/assets/nova_logo.png
--------------------------------------------------------------------------------
/apps/app/assets/phala_logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/app/assets/powered_by_slpx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/assets/powered_by_slpx.png
--------------------------------------------------------------------------------
/apps/app/assets/share_equation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/assets/share_equation.png
--------------------------------------------------------------------------------
/apps/app/assets/stake_pool.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/apps/app/assets/subwallet_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/assets/subwallet_logo.png
--------------------------------------------------------------------------------
/apps/app/assets/vault.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/apps/app/assets/vault_detailed.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/apps/app/assets/whitelists.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/app/components/BasePool/CreateButton.tsx:
--------------------------------------------------------------------------------
1 | import usePolkadotApi from '@/hooks/usePolkadotApi'
2 | import useSignAndSend from '@/hooks/useSignAndSend'
3 | import type {BasePoolKind} from '@/lib/subsquidQuery'
4 | import type {ButtonProps} from '@mui/material'
5 | import type {FC} from 'react'
6 | import PromiseButton from '../PromiseButton'
7 |
8 | const CreateButton: FC<{kind: BasePoolKind} & Omit> = ({
9 | kind,
10 | ...props
11 | }) => {
12 | const api = usePolkadotApi()
13 | const signAndSend = useSignAndSend()
14 | const onClick = async (): Promise => {
15 | if (api == null) return
16 | const extrinsic =
17 | kind === 'StakePool'
18 | ? api.tx.phalaStakePoolv2.create()
19 | : api.tx.phalaVault.create()
20 |
21 | await signAndSend(extrinsic)
22 | }
23 |
24 | return (
25 | {`Create ${kind}`}
31 | )
32 | }
33 |
34 | export default CreateButton
35 |
--------------------------------------------------------------------------------
/apps/app/components/BasePool/Whitelist/AddWhitelist.tsx:
--------------------------------------------------------------------------------
1 | import usePolkadotApi from '@/hooks/usePolkadotApi'
2 | import useSignAndSend from '@/hooks/useSignAndSend'
3 | import {
4 | Button,
5 | DialogActions,
6 | DialogContent,
7 | DialogTitle,
8 | TextField,
9 | } from '@mui/material'
10 | import {validateAddress} from '@phala/lib'
11 | import {type FC, useMemo, useState} from 'react'
12 |
13 | const AddWhitelist: FC<{
14 | pid: string
15 | onClose: () => void
16 | }> = ({pid, ...props}) => {
17 | const api = usePolkadotApi()
18 | const signAndSend = useSignAndSend()
19 | const [loading, setLoading] = useState(false)
20 | const [address, setAddress] = useState('')
21 | const isValid = useMemo(() => validateAddress(address), [address])
22 |
23 | const onClick = (): void => {
24 | if (api == null) return
25 | const extrinsic = api.tx.phalaBasePool.addStakerToWhitelist(pid, address)
26 |
27 | setLoading(true)
28 | signAndSend(extrinsic)
29 | .then(() => {
30 | props.onClose()
31 | })
32 | .catch(() => {
33 | setLoading(false)
34 | })
35 | }
36 |
37 | return (
38 | <>
39 | Add Whitelist
40 |
41 | {
51 | setAddress(e.target.value)
52 | }}
53 | />
54 |
55 |
56 |
64 |
65 | >
66 | )
67 | }
68 |
69 | export default AddWhitelist
70 |
--------------------------------------------------------------------------------
/apps/app/components/BasePool/Worker/AddWorker.tsx:
--------------------------------------------------------------------------------
1 | import usePolkadotApi from '@/hooks/usePolkadotApi'
2 | import useSignAndSend from '@/hooks/useSignAndSend'
3 | import {
4 | Button,
5 | DialogActions,
6 | DialogContent,
7 | DialogTitle,
8 | TextField,
9 | } from '@mui/material'
10 | import {type FC, useMemo, useState} from 'react'
11 |
12 | const AddWorker: FC<{
13 | pid: string
14 | onClose: () => void
15 | }> = ({onClose, pid}) => {
16 | const api = usePolkadotApi()
17 | const signAndSend = useSignAndSend()
18 | const [loading, setLoading] = useState(false)
19 | const [workerId, setWorkerId] = useState('')
20 | const isValid = useMemo(
21 | () => /^0x[0-9a-fA-F]{64}$/.test(workerId),
22 | [workerId],
23 | )
24 |
25 | const onClick = (): void => {
26 | if (api == null) return
27 | const extrinsic = api.tx.phalaStakePoolv2.addWorker(pid, workerId)
28 |
29 | setLoading(true)
30 | signAndSend(extrinsic)
31 | .then(() => {
32 | onClose()
33 | })
34 | .catch(() => {
35 | setLoading(false)
36 | })
37 | }
38 |
39 | return (
40 | <>
41 | Add Worker
42 |
43 | {
54 | setWorkerId(e.target.value)
55 | }}
56 | />
57 |
58 |
59 |
67 |
68 | >
69 | )
70 | }
71 |
72 | export default AddWorker
73 |
--------------------------------------------------------------------------------
/apps/app/components/ClientOnly.tsx:
--------------------------------------------------------------------------------
1 | import {type FC, type ReactNode, useEffect, useState} from 'react'
2 |
3 | // MEMO: different from MUI , fallback content will be also rendered when children is falsy
4 | const ClientOnly: FC<{children: ReactNode; fallback?: ReactNode}> = ({
5 | children,
6 | fallback,
7 | }) => {
8 | const [hasMounted, setHasMounted] = useState(false)
9 |
10 | useEffect(() => {
11 | setHasMounted(true)
12 | }, [])
13 |
14 | return (
15 | <>
16 | {hasMounted && children != null && children !== false
17 | ? children
18 | : fallback}
19 | >
20 | )
21 | }
22 |
23 | export default ClientOnly
24 |
--------------------------------------------------------------------------------
/apps/app/components/Delegation/Menu.tsx:
--------------------------------------------------------------------------------
1 | import usePolkadotApi from '@/hooks/usePolkadotApi'
2 | import useSignAndSend from '@/hooks/useSignAndSend'
3 | import type {DelegationCommonFragment} from '@/lib/subsquidQuery'
4 | import MoreVert from '@mui/icons-material/MoreVert'
5 | import {
6 | IconButton,
7 | Menu,
8 | MenuItem,
9 | type SxProps,
10 | type Theme,
11 | } from '@mui/material'
12 | import {type FC, useRef, useState} from 'react'
13 | import type {OnAction} from './List'
14 |
15 | const DelegationMenu: FC<{
16 | onAction?: OnAction
17 | delegation: DelegationCommonFragment
18 | sx?: SxProps
19 | }> = ({delegation, onAction, sx}) => {
20 | const api = usePolkadotApi()
21 | const signAndSend = useSignAndSend()
22 | const {basePool} = delegation
23 | const [menuOpen, setMenuOpen] = useState(false)
24 | const moreRef = useRef(null)
25 | const poolHasWithdrawal = delegation.basePool.withdrawingShares !== '0'
26 | const isVault = basePool.kind === 'Vault'
27 | const reclaim = async (): Promise => {
28 | if (api == null) return
29 | await signAndSend(
30 | isVault
31 | ? api.tx.phalaVault.checkAndMaybeForceWithdraw(basePool.id)
32 | : api.tx.phalaStakePoolv2.checkAndMaybeForceWithdraw(basePool.id),
33 | )
34 | }
35 |
36 | return (
37 | <>
38 | {
41 | setMenuOpen(true)
42 | }}
43 | sx={sx}
44 | >
45 |
46 |
47 |
80 | >
81 | )
82 | }
83 |
84 | export default DelegationMenu
85 |
--------------------------------------------------------------------------------
/apps/app/components/DownloadHistory.tsx:
--------------------------------------------------------------------------------
1 | import {Button, type ButtonProps} from '@mui/material'
2 | import type {FC} from 'react'
3 |
4 | const DownloadHistory: FC<
5 | {
6 | kind: 'pool' | 'account' | 'delegation'
7 | id: string
8 | } & ButtonProps
9 | > = ({kind, id, ...props}) => {
10 | return (
11 |
19 | )
20 | }
21 |
22 | export default DownloadHistory
23 |
--------------------------------------------------------------------------------
/apps/app/components/Empty.tsx:
--------------------------------------------------------------------------------
1 | import {Stack, type SxProps, Typography} from '@mui/material'
2 | import type {FC} from 'react'
3 |
4 | const Empty: FC<{message?: string; sx?: SxProps}> = ({
5 | message = 'No Results',
6 | sx,
7 | }) => {
8 | return (
9 |
10 |
11 | {message}
12 |
13 |
14 | )
15 | }
16 |
17 | export default Empty
18 |
--------------------------------------------------------------------------------
/apps/app/components/HydrateAtoms.tsx:
--------------------------------------------------------------------------------
1 | import type {WritableAtom} from 'jotai'
2 | import {useHydrateAtoms} from 'jotai/utils'
3 | import type {FC, ReactNode} from 'react'
4 |
5 | const HydrateAtoms: FC<{
6 | // biome-ignore lint/suspicious/noExplicitAny:
7 | initialValues: Map, any>
8 | children: ReactNode
9 | }> = ({initialValues, children}) => {
10 | useHydrateAtoms(initialValues)
11 | return <>{children}>
12 | }
13 |
14 | export default HydrateAtoms
15 |
--------------------------------------------------------------------------------
/apps/app/components/Layout.tsx:
--------------------------------------------------------------------------------
1 | import useListenBlockHeight from '@/hooks/useListenBlockHeight'
2 | import {useNotice} from '@/hooks/useNotice'
3 | import useResetVault from '@/hooks/useResetVault'
4 | import useShowWalletDialog from '@/hooks/useShowWalletDialog'
5 | import {Container} from '@mui/material'
6 | import {useConnectPolkadotWallet} from '@phala/lib'
7 | import {useRouter} from 'next/router'
8 | import type {FC, ReactNode} from 'react'
9 | import ScrollTop from './ScrollTop'
10 | import TopBar from './TopBar'
11 | import WalletDialog from './WalletDialog'
12 | import WikiDialog from './Wiki/Dialog'
13 | const lgWidthPathnames = ['/staking', '/wiki']
14 |
15 | const Layout: FC<{children: ReactNode}> = ({children}) => {
16 | const router = useRouter()
17 |
18 | useConnectPolkadotWallet('Phala App', 30)
19 | useListenBlockHeight()
20 | useResetVault()
21 | useShowWalletDialog()
22 | useNotice()
23 |
24 | return (
25 | <>
26 |
27 |
31 | {children}
32 |
33 |
34 |
35 |
36 | >
37 | )
38 | }
39 |
40 | export default Layout
41 |
--------------------------------------------------------------------------------
/apps/app/components/ListSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import {Skeleton, type SkeletonProps} from '@mui/material'
2 | import {forwardRef} from 'react'
3 |
4 | const ListSkeleton = forwardRef<
5 | HTMLSpanElement,
6 | SkeletonProps & {count?: number}
7 | >(({count = 5, ...props}, ref) => (
8 | <>
9 | {Array.from({length: count}).map((_, index) => (
10 |
17 | ))}
18 | >
19 | ))
20 |
21 | ListSkeleton.displayName = 'ListSkeleton'
22 |
23 | export default ListSkeleton
24 |
--------------------------------------------------------------------------------
/apps/app/components/PageHeader.tsx:
--------------------------------------------------------------------------------
1 | import {Stack, Typography} from '@mui/material'
2 | import type {FC, ReactNode} from 'react'
3 | import Title from './Title'
4 |
5 | const PageHeader: FC<{
6 | title: string
7 | pageTitle?: ReactNode
8 | children?: ReactNode
9 | }> = ({title, pageTitle, children}) => {
10 | return (
11 | <>
12 | {title}
13 |
20 |
26 | {pageTitle ?? title}
27 |
28 |
29 | {pageTitle ?? title}
30 |
31 | {children}
32 |
33 | >
34 | )
35 | }
36 |
37 | export default PageHeader
38 |
--------------------------------------------------------------------------------
/apps/app/components/PromiseButton.tsx:
--------------------------------------------------------------------------------
1 | import {Button, type ButtonProps} from '@mui/material'
2 | import {type FC, useState} from 'react'
3 |
4 | const PromiseButton: FC<
5 | Omit & {
6 | onClick?: (
7 | ...args: Parameters>
8 | ) => Promise
9 | }
10 | > = ({onClick, ...props}) => {
11 | const [loading, setLoading] = useState(false)
12 |
13 | const handleClick: ButtonProps['onClick'] = (e) => {
14 | if (onClick != null) {
15 | setLoading(true)
16 | void onClick(e).finally(() => {
17 | setLoading(false)
18 | })
19 | }
20 | }
21 |
22 | return
23 | }
24 |
25 | export default PromiseButton
26 |
--------------------------------------------------------------------------------
/apps/app/components/Property.tsx:
--------------------------------------------------------------------------------
1 | import type {WikiEntry} from '@/assets/wikiData'
2 | import {Stack, type StackProps, type SxProps, Typography} from '@mui/material'
3 | import type {FC, ReactNode} from 'react'
4 | import WikiButton from './Wiki/Button'
5 | import WrapDecimal from './WrapDecimal'
6 |
7 | const Property: FC<
8 | {
9 | size?: 'large' | 'medium' | 'small'
10 | label: ReactNode
11 | children: ReactNode
12 | fullWidth?: boolean
13 | sx?: SxProps
14 | wikiEntry?: WikiEntry
15 | wrapDecimal?: boolean
16 | center?: boolean
17 | } & StackProps
18 | > = ({
19 | size = 'medium',
20 | label,
21 | children,
22 | fullWidth = false,
23 | sx,
24 | wikiEntry,
25 | wrapDecimal = false,
26 | center = false,
27 | ...stackProps
28 | }) => {
29 | const labelNode = (
30 |
37 | {label}
38 |
39 | )
40 |
41 | const value = wrapDecimal ? {children} : children
42 |
43 | return (
44 |
53 | {wikiEntry == null ? (
54 | labelNode
55 | ) : (
56 | {labelNode}
57 | )}
58 | {size === 'large' && (
59 |
65 | {value}
66 |
67 | )}
68 | {size === 'medium' && (
69 |
76 | {value}
77 |
78 | )}
79 | {size === 'small' && (
80 |
88 | {value}
89 |
90 | )}
91 |
92 | )
93 | }
94 |
95 | export default Property
96 |
--------------------------------------------------------------------------------
/apps/app/components/RechartsTooltip.tsx:
--------------------------------------------------------------------------------
1 | import {Paper, Typography} from '@mui/material'
2 | import {toCurrency} from '@phala/lib'
3 | import type {ReactElement} from 'react'
4 | import Property from './Property'
5 |
6 | const RechartsTooltip = ({
7 | label,
8 | payload,
9 | }: {
10 | label?: string
11 | payload?: Array<{
12 | name: string
13 | value: number | string
14 | payload: {name: string}
15 | unit: string
16 | }>
17 | }): ReactElement | null => {
18 | if (payload?.[0] != null) {
19 | return (
20 |
21 |
22 | {label ?? payload[0].payload.name}
23 |
24 | {payload.map(({name, value, unit}) => (
25 | {`${
26 | unit === '%' ? value : toCurrency(value)
27 | }${unit ?? ''}`}
28 | ))}
29 |
30 | )
31 | }
32 |
33 | return null
34 | }
35 |
36 | export default RechartsTooltip
37 |
--------------------------------------------------------------------------------
/apps/app/components/ScrollTop.tsx:
--------------------------------------------------------------------------------
1 | import KeyboardArrowUp from '@mui/icons-material/KeyboardArrowUp'
2 | import {Box, Fab, Fade, useScrollTrigger} from '@mui/material'
3 | import type {FC} from 'react'
4 |
5 | const ScrollTop: FC = () => {
6 | const trigger = useScrollTrigger({
7 | disableHysteresis: true,
8 | threshold: 600,
9 | })
10 |
11 | const handleClick = (): void => {
12 | window.scrollTo({top: 0, behavior: 'smooth'})
13 | }
14 |
15 | return (
16 |
17 |
22 |
23 |
24 |
25 |
26 |
27 | )
28 | }
29 | export default ScrollTop
30 |
--------------------------------------------------------------------------------
/apps/app/components/SectionHeader.tsx:
--------------------------------------------------------------------------------
1 | import {Stack, type SxProps, Typography} from '@mui/material'
2 | import type {FC, ReactNode} from 'react'
3 |
4 | const SectionHeader: FC<{
5 | icon: ReactNode
6 | title: ReactNode
7 | children?: ReactNode
8 | sx?: SxProps
9 | }> = ({title, icon, children, sx}) => {
10 | return (
11 |
12 |
13 |
19 | {icon}
20 |
21 |
26 | {title}
27 |
28 |
33 | {title}
34 |
35 |
36 | {children}
37 |
38 | )
39 | }
40 |
41 | export default SectionHeader
42 |
--------------------------------------------------------------------------------
/apps/app/components/SnackbarProvider.tsx:
--------------------------------------------------------------------------------
1 | import {useMediaQuery} from '@mui/material'
2 | import {useTheme} from '@mui/material'
3 | import {SnackbarProvider as NotistackSnackbarProvider} from 'notistack'
4 |
5 | const SnackbarProvider = ({children}: {children: React.ReactNode}) => {
6 | const theme = useTheme()
7 |
8 | const matches = useMediaQuery(theme.breakpoints.up('md'))
9 |
10 | return (
11 |
22 | {children}
23 |
24 | )
25 | }
26 |
27 | export default SnackbarProvider
28 |
--------------------------------------------------------------------------------
/apps/app/components/Staking/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | useRewardRate,
3 | useShares,
4 | useSharesToAssets,
5 | useTotalAssets,
6 | } from '@/hooks/staking'
7 | import {useAutoSwitchChain} from '@/hooks/useAutoSwitchChain'
8 | import {Box, Grid2 as Grid, Paper, Stack} from '@mui/material'
9 | import {toCurrency, toPercentage} from '@phala/lib'
10 | import Decimal from 'decimal.js'
11 | import {useMemo} from 'react'
12 | import {formatUnits} from 'viem'
13 | import {useAccount} from 'wagmi'
14 | import Property from '../Property'
15 | import Stake from './Stake'
16 | import Unstake from './Unstake'
17 |
18 | const Staking = () => {
19 | const {address} = useAccount()
20 | const shares = useShares(address)
21 | const assets = useSharesToAssets(shares)
22 | const totalAssets = useTotalAssets()
23 | const rewardRate = useRewardRate()
24 | useAutoSwitchChain()
25 |
26 | const apr = useMemo(() => {
27 | if (rewardRate == null || !totalAssets) {
28 | return null
29 | }
30 | return Decimal.mul(rewardRate.toString(), 365 * 24 * 60 * 60).div(
31 | totalAssets.toString(),
32 | )
33 | }, [rewardRate, totalAssets])
34 |
35 | return (
36 |
37 |
38 |
39 |
40 |
46 | {apr != null ? toPercentage(apr) : '-'}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {assets != null ? toCurrency(formatUnits(assets, 18)) : '-'}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | {totalAssets != null
63 | ? toCurrency(formatUnits(totalAssets, 18))
64 | : '-'}
65 |
66 | {' '}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | )
80 | }
81 |
82 | export default Staking
83 |
--------------------------------------------------------------------------------
/apps/app/components/SwitchChainButton.tsx:
--------------------------------------------------------------------------------
1 | import {ethChain} from '@/config'
2 | import {Button} from '@mui/material'
3 | import {useAccount, useSwitchChain} from 'wagmi'
4 |
5 | const SwitchChainButton = ({children}: {children: React.ReactNode}) => {
6 | const {isConnected, chainId} = useAccount()
7 | const {switchChain, isPending} = useSwitchChain()
8 | if (!isConnected || chainId === ethChain.id) {
9 | return children
10 | }
11 | return (
12 |
20 | )
21 | }
22 |
23 | export default SwitchChainButton
24 |
--------------------------------------------------------------------------------
/apps/app/components/TextSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import {Skeleton, Stack, type StackProps} from '@mui/material'
2 | import type {FC} from 'react'
3 |
4 | const TextSkeleton: FC = (props) => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | export default TextSkeleton
15 |
--------------------------------------------------------------------------------
/apps/app/components/Title.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import type {FC} from 'react'
3 |
4 | const Title: FC<{children: string}> = ({children}) => {
5 | return (
6 |
7 | {`${children} | Phala App`}
8 |
9 | )
10 | }
11 |
12 | export default Title
13 |
--------------------------------------------------------------------------------
/apps/app/components/TopBar/Account.tsx:
--------------------------------------------------------------------------------
1 | import {walletDialogOpenAtom} from '@/store/ui'
2 | import {Box, Button, useTheme} from '@mui/material'
3 | import {polkadotAccountAtom, walletAtom} from '@phala/store'
4 | import {useAtom} from 'jotai'
5 | import Image from 'next/image'
6 | import type {FC} from 'react'
7 |
8 | const Account: FC = () => {
9 | const theme = useTheme()
10 | const [wallet] = useAtom(walletAtom)
11 | const [polkadotAccount] = useAtom(polkadotAccountAtom)
12 | const [, setWalletDialogOpen] = useAtom(walletDialogOpenAtom)
13 | const isConnected = wallet != null && polkadotAccount != null
14 | return (
15 |
44 | )
45 | }
46 |
47 | export default Account
48 |
--------------------------------------------------------------------------------
/apps/app/components/TopBar/ChainStatus.tsx:
--------------------------------------------------------------------------------
1 | import {subsquidClient} from '@/config'
2 | import usePolkadotApi from '@/hooks/usePolkadotApi'
3 | import {useGlobalStateQuery} from '@/lib/subsquidQuery'
4 | import CircleIcon from '@mui/icons-material/Circle'
5 | import {Tooltip} from '@mui/material'
6 | import {toCurrency} from '@phala/lib'
7 | import {useQuery} from '@tanstack/react-query'
8 | import {type FC, useMemo} from 'react'
9 | import Property from '../Property'
10 |
11 | const ChainStatus: FC = () => {
12 | const api = usePolkadotApi()
13 |
14 | const {data: globalStateData} = useGlobalStateQuery(
15 | subsquidClient,
16 | undefined,
17 | {select: (data) => data.globalStateById},
18 | )
19 | const subsquidHeight = globalStateData?.height
20 | const blockTime = globalStateData?.averageBlockTime
21 | const apiConnected = api?.isConnected
22 | const {data: chainHeight} = useQuery({
23 | queryKey: ['chainHeight'],
24 | queryFn: async () => {
25 | if (api == null) return null
26 | const lastHeader = await api.rpc.chain.getHeader()
27 | return lastHeader.number.toNumber()
28 | },
29 | refetchInterval: 3000,
30 | })
31 | const status: 'success' | 'warning' | 'error' = useMemo(() => {
32 | if (apiConnected === true) {
33 | if (
34 | subsquidHeight != null &&
35 | chainHeight != null &&
36 | chainHeight - subsquidHeight < 10
37 | ) {
38 | return 'success'
39 | }
40 | return 'warning'
41 | }
42 |
43 | return 'error'
44 | }, [apiConnected, chainHeight, subsquidHeight])
45 |
46 | return (
47 | {
52 | e.stopPropagation()
53 | },
54 | },
55 | }}
56 | title={
57 | <>
58 |
59 | {apiConnected === true ? 'Connected' : 'Disconnected'}
60 |
61 |
62 | {subsquidHeight == null ? '-' : toCurrency(subsquidHeight)}
63 |
64 |
65 | {chainHeight == null ? '-' : toCurrency(chainHeight)}
66 |
67 |
68 | {blockTime == null ? '-' : `${blockTime / 1000}s`}
69 |
70 | >
71 | }
72 | >
73 |
74 |
75 | )
76 | }
77 |
78 | export default ChainStatus
79 |
--------------------------------------------------------------------------------
/apps/app/components/Vest.tsx:
--------------------------------------------------------------------------------
1 | import usePolkadotApi from '@/hooks/usePolkadotApi'
2 | import useSignAndSend from '@/hooks/useSignAndSend'
3 | import {
4 | DialogActions,
5 | DialogContent,
6 | DialogTitle,
7 | Skeleton,
8 | Stack,
9 | } from '@mui/material'
10 | import {toCurrency} from '@phala/lib'
11 | import {polkadotAccountAtom} from '@phala/store'
12 | import type {DeriveBalancesAll} from '@polkadot/api-derive/types'
13 | import BN from 'bn.js'
14 | import Decimal from 'decimal.js'
15 | import {useAtom} from 'jotai'
16 | import {type FC, type ReactElement, useEffect, useState} from 'react'
17 | import PromiseButton from './PromiseButton'
18 | import Property from './Property'
19 |
20 | const format = (bn?: BN): ReactElement => {
21 | if (bn !== undefined) {
22 | // HACK: derived vestedClaimable may be negative
23 | const value = bn.isNeg() ? new BN(0) : bn
24 | return <>{`${toCurrency(new Decimal(value.toString()).div(1e12))} PHA`}>
25 | }
26 | return
27 | }
28 |
29 | const Vest: FC<{onClose: () => void}> = ({onClose}) => {
30 | const api = usePolkadotApi()
31 | const signAndSend = useSignAndSend()
32 | const [allBalances, setAllBalances] = useState()
33 | const [account] = useAtom(polkadotAccountAtom)
34 |
35 | useEffect(() => {
36 | let unmounted = false
37 | if (api != null && account != null) {
38 | void api.derive.balances.all(account.address).then((all) => {
39 | if (!unmounted) {
40 | setAllBalances(all)
41 | }
42 | })
43 | }
44 | return () => {
45 | unmounted = true
46 | }
47 | }, [api, account])
48 |
49 | const vest = async (): Promise => {
50 | if (api !== undefined) {
51 | await signAndSend(api.tx.vesting.vest()).then(onClose)
52 | }
53 | }
54 |
55 | return (
56 | <>
57 | Claim Crowdloan Reward
58 |
59 |
60 |
61 | {format(
62 | allBalances?.vestedBalance.sub(allBalances.vestedClaimable),
63 | )}
64 |
65 |
66 | {format(allBalances?.vestingLocked)}
67 |
68 |
69 | {format(allBalances?.vestedClaimable)}
70 |
71 |
72 |
73 |
74 |
83 | Claim
84 |
85 |
86 | >
87 | )
88 | }
89 |
90 | export default Vest
91 |
--------------------------------------------------------------------------------
/apps/app/components/Web3Provider.tsx:
--------------------------------------------------------------------------------
1 | import {ethChain} from '@/config'
2 | import {
3 | RainbowKitProvider,
4 | darkTheme,
5 | getDefaultConfig,
6 | } from '@rainbow-me/rainbowkit'
7 | import type {ReactNode} from 'react'
8 | import type {Chain} from 'viem'
9 | import {WagmiProvider} from 'wagmi'
10 | import '@rainbow-me/rainbowkit/styles.css'
11 |
12 | const chains: [Chain, ...Chain[]] = [ethChain]
13 |
14 | const config = getDefaultConfig({
15 | chains,
16 | projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID as string,
17 | appName: 'Phala App',
18 | appUrl: 'https://app.phala.network',
19 | appIcon: 'https://app.phala.network/apple-touch-icon.png',
20 | ssr: true,
21 | })
22 |
23 | export const Web3Provider = ({
24 | children,
25 | }: {
26 | children: ReactNode
27 | }) => {
28 | return (
29 |
30 |
39 | {children}
40 |
41 |
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/apps/app/components/Wiki/Button.tsx:
--------------------------------------------------------------------------------
1 | import type {WikiEntry} from '@/assets/wikiData'
2 | import {wikiExpandEntryAtom} from '@/store/common'
3 | import {wikiDialogOpenAtom} from '@/store/ui'
4 | import InfoIcon from '@mui/icons-material/Info'
5 | import {IconButton, Stack} from '@mui/material'
6 | import {useAtom} from 'jotai'
7 | import type {FC, ReactNode} from 'react'
8 |
9 | const WikiButton: FC<{entry: WikiEntry; children?: ReactNode}> = ({
10 | entry,
11 | children,
12 | }) => {
13 | const [, setOpen] = useAtom(wikiDialogOpenAtom)
14 | const [, setExpand] = useAtom(wikiExpandEntryAtom)
15 |
16 | return (
17 |
18 | {children}
19 | {
22 | e.stopPropagation()
23 | setOpen(true)
24 | setExpand(entry)
25 | }}
26 | sx={{p: 0.5, mr: -0.5, mb: -1, mt: '-9px', ml: 0}}
27 | >
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default WikiButton
35 |
--------------------------------------------------------------------------------
/apps/app/components/Wiki/Dialog.tsx:
--------------------------------------------------------------------------------
1 | import {wikiDialogOpenAtom} from '@/store/ui'
2 | import {Dialog, DialogContent, useMediaQuery, useTheme} from '@mui/material'
3 | import {useAtom} from 'jotai'
4 | import type {FC} from 'react'
5 | import Wiki from '.'
6 |
7 | const WikiDialog: FC = () => {
8 | const [open, setOpen] = useAtom(wikiDialogOpenAtom)
9 | const theme = useTheme()
10 | const fullScreen = useMediaQuery(theme.breakpoints.down('sm'))
11 |
12 | return (
13 |
25 | )
26 | }
27 |
28 | export default WikiDialog
29 |
--------------------------------------------------------------------------------
/apps/app/components/WrapDecimal.tsx:
--------------------------------------------------------------------------------
1 | import {Box} from '@mui/material'
2 | import {type FC, Fragment, type ReactNode, useMemo} from 'react'
3 |
4 | const regex = /(\d+\.\d+)/g
5 |
6 | const WrapDecimal: FC<{children: ReactNode}> = ({children}) => {
7 | const parts = useMemo(() => {
8 | if (typeof children === 'string') {
9 | return children.split(regex)
10 | }
11 | return []
12 | }, [children])
13 |
14 | if (parts.length === 0) {
15 | return children
16 | }
17 |
18 | return (
19 | <>
20 | {parts.map((part, index) => {
21 | if (regex.test(part)) {
22 | const [integer, decimal] = part.split('.')
23 | return (
24 |
28 | {integer}.
29 |
30 | {decimal}
31 |
32 |
33 | )
34 | }
35 | // biome-ignore lint/suspicious/noArrayIndexKey:
36 | return {part}
37 | })}
38 | >
39 | )
40 | }
41 |
42 | export default WrapDecimal
43 |
--------------------------------------------------------------------------------
/apps/app/components/ZendeskWidget.tsx:
--------------------------------------------------------------------------------
1 | import Script from 'next/script'
2 | import type {FC} from 'react'
3 |
4 | declare global {
5 | interface Window {
6 | zESettings: unknown
7 | }
8 | }
9 |
10 | const ZendeskWidget: FC = () => {
11 | return (
12 |
17 | )
18 | }
19 |
20 | export default ZendeskWidget
21 |
--------------------------------------------------------------------------------
/apps/app/config.ts:
--------------------------------------------------------------------------------
1 | import {GraphQLClient} from 'graphql-request'
2 | import {mainnet} from 'viem/chains'
3 |
4 | export const PHALA_ENDPOINTS = [
5 | 'wss://phala-rpc.dwellir.com',
6 | 'wss://api.phala.network/ws',
7 | 'wss://phala.api.onfinality.io/public-ws',
8 | ]
9 | export const subsquidClient = new GraphQLClient(
10 | 'https://subsquid.phala.network/phala-computation/graphql',
11 | )
12 | export const khalaSubsquidClient = new GraphQLClient(
13 | 'https://subsquid.phala.network/khala-computation/graphql',
14 | )
15 |
16 | export const WPHA_ASSET_ID = 10000
17 |
18 | export const ethChain = mainnet
19 |
20 | export const PHA_CONTRACT_ADDRESS = '0x6c5bA91642F10282b576d91922Ae6448C9d52f4E'
21 | export const VAULT_CONTRACT_ADDRESS =
22 | '0x21d6eC8fc14CaAcc55aFA23cBa66798DAB3a0ec0'
23 | export const KHALA_CLAIMER_CONTRACT_ADDRESS =
24 | '0xa139F849000C5438855032eBFeed9Ee104023a73'
25 |
--------------------------------------------------------------------------------
/apps/app/hooks/khalaAssets.ts:
--------------------------------------------------------------------------------
1 | import khalaClaimerAbi from '@/assets/khala_claimer_abi'
2 | import {KHALA_CLAIMER_CONTRACT_ADDRESS} from '@/config'
3 | import {useQuery} from '@tanstack/react-query'
4 | import type {Hex} from 'viem'
5 | import {usePublicClient, useReadContract} from 'wagmi'
6 | import wretch from 'wretch'
7 |
8 | export const khalaAssetsApi = wretch(
9 | 'https://dbcac8a0d67d837a93e8c1db0886c8f1acdc599d-8080.dstack-prod4.phala.network',
10 | )
11 |
12 | export const useKhalaAssetsQuery = (address?: string) => {
13 | return useQuery({
14 | queryKey: ['khala-assets', address],
15 | queryFn: () =>
16 | khalaAssetsApi
17 | .get(`/account/${address}`)
18 | .json<{free: string; staked: string; pwRefund: string}>(),
19 | enabled: address != null,
20 | })
21 | }
22 |
23 | export const useClaimStatus = (address?: Hex) => {
24 | const {data: claimed, refetch} = useReadContract({
25 | address: KHALA_CLAIMER_CONTRACT_ADDRESS,
26 | abi: khalaClaimerAbi,
27 | functionName: 'claimed',
28 | args: address && [address],
29 | query: {
30 | enabled: Boolean(address),
31 | refetchInterval: (query) => (query.state.data ? false : 3000),
32 | },
33 | })
34 | const publicClient = usePublicClient()
35 | const {data: log} = useQuery({
36 | queryKey: ['khala-claim-logs', address, publicClient?.chain.id],
37 | queryFn: async () => {
38 | if (publicClient == null) {
39 | return
40 | }
41 | const event = khalaClaimerAbi.find(
42 | (item) => item.type === 'event' && item.name === 'Claimed',
43 | )
44 | if (event == null) {
45 | return
46 | }
47 | const latestBlock = await publicClient.getBlock()
48 | for (let i = latestBlock.number; i > 21613791n; i -= 10000n) {
49 | const logs = await publicClient.getLogs({
50 | address: KHALA_CLAIMER_CONTRACT_ADDRESS,
51 | event,
52 | args: {user: address},
53 | fromBlock: i - 10000n,
54 | toBlock: i,
55 | })
56 | if (logs.length > 0) {
57 | return logs[0]
58 | }
59 | }
60 | },
61 | enabled: claimed === true && publicClient != null && address != null,
62 | refetchInterval: (query) => (query.state.data ? false : 3000),
63 | })
64 | return {
65 | claimed,
66 | log,
67 | refetch,
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/apps/app/hooks/useAccountQuery.ts:
--------------------------------------------------------------------------------
1 | import {subsquidClient} from '@/config'
2 | import {type AccountByIdQuery, useAccountByIdQuery} from '@/lib/subsquidQuery'
3 | import {polkadotAccountAtom} from '@phala/store'
4 | import type {UseQueryResult} from '@tanstack/react-query'
5 | import {useAtom} from 'jotai'
6 |
7 | const useAccountQuery = (): UseQueryResult<
8 | AccountByIdQuery['accountById'],
9 | unknown
10 | > => {
11 | const [account] = useAtom(polkadotAccountAtom)
12 | const query = useAccountByIdQuery(
13 | subsquidClient,
14 | {accountId: account?.address as unknown as string},
15 | {enabled: account != null, select: (data) => data.accountById},
16 | )
17 | return query
18 | }
19 |
20 | export default useAccountQuery
21 |
--------------------------------------------------------------------------------
/apps/app/hooks/useAssetBalance.ts:
--------------------------------------------------------------------------------
1 | import {useQuery} from '@tanstack/react-query'
2 | import Decimal from 'decimal.js'
3 | import useAssetsMetadata from './useAssetsMetadata'
4 | import usePolkadotApi from './usePolkadotApi'
5 |
6 | const useAssetBalance = (
7 | account: string | undefined,
8 | assetId: number | 'free' | 'available',
9 | ): Decimal | null | undefined => {
10 | const api = usePolkadotApi()
11 | const assetsMetadata = useAssetsMetadata()
12 | const {data} = useQuery({
13 | queryKey: ['assetBalance', api?.runtimeChain, account, assetId],
14 | enabled: api != null && account != null && assetsMetadata != null,
15 | queryFn: async () => {
16 | if (api == null || account == null) {
17 | return null
18 | }
19 | if (typeof assetId === 'number') {
20 | const assetMetadata = assetsMetadata?.[assetId]
21 | if (assetMetadata == null) return null
22 | const res = await api.query.assets.account(assetId, account)
23 | const unwrapped = res.unwrapOr({balance: 0})
24 | return new Decimal(unwrapped.balance.toString()).div(
25 | Decimal.pow(10, assetMetadata.decimals),
26 | )
27 | }
28 | const {availableBalance, freeBalance} =
29 | await api.derive.balances.all(account)
30 | return new Decimal(
31 | (assetId === 'available' ? availableBalance : freeBalance).toHex(),
32 | ).div(1e12)
33 | },
34 | })
35 |
36 | return data
37 | }
38 |
39 | export default useAssetBalance
40 |
--------------------------------------------------------------------------------
/apps/app/hooks/useAutoSwitchChain.ts:
--------------------------------------------------------------------------------
1 | import {ethChain} from '@/config'
2 | import {useEffect} from 'react'
3 | import {useSwitchChain} from 'wagmi'
4 | import {useAccount} from 'wagmi'
5 |
6 | export const useAutoSwitchChain = () => {
7 | const {isConnected, chainId} = useAccount()
8 | const {switchChain} = useSwitchChain()
9 | useEffect(() => {
10 | if (isConnected && chainId !== ethChain.id) {
11 | switchChain({chainId: ethChain.id})
12 | }
13 | }, [chainId, switchChain, isConnected])
14 | }
15 |
--------------------------------------------------------------------------------
/apps/app/hooks/useDebounced.ts:
--------------------------------------------------------------------------------
1 | import {debounce} from 'radash'
2 | import {useEffect, useMemo, useState} from 'react'
3 |
4 | const useDebounced = (state: T, delay = 500): T => {
5 | const [debouncedState, setDebouncedState] = useState(state)
6 | const debouncedSetState = useMemo(
7 | () => debounce({delay}, setDebouncedState),
8 | [delay],
9 | )
10 |
11 | useEffect(() => {
12 | debouncedSetState(state)
13 | }, [state, debouncedSetState])
14 |
15 | return debouncedState
16 | }
17 |
18 | export default useDebounced
19 |
--------------------------------------------------------------------------------
/apps/app/hooks/useDelegationOneDayProfit.ts:
--------------------------------------------------------------------------------
1 | import {subsquidClient} from '@/config'
2 | import getDelegationProfit from '@/lib/getDelegationProfit'
3 | import {useDelegationSnapshotsConnectionQuery} from '@/lib/subsquidQuery'
4 | import {addDays} from 'date-fns'
5 | import {useMemo} from 'react'
6 | import useToday from './useToday'
7 |
8 | const useDelegationOneDayProfit = (id: string) => {
9 | const today = useToday()
10 | const pastTwoDays = useMemo(() => {
11 | const date = new Date(today)
12 | return [date.toISOString(), addDays(date, -1).toISOString()]
13 | }, [today])
14 | const {data} = useDelegationSnapshotsConnectionQuery(
15 | subsquidClient,
16 | {
17 | orderBy: 'updatedTime_ASC',
18 | where: {delegation_eq: id, updatedTime_in: pastTwoDays},
19 | },
20 | {select: (data) => data.delegationSnapshotsConnection.edges},
21 | )
22 | if (data?.length !== 2) return null
23 | return getDelegationProfit(data[1].node, data[0].node)
24 | }
25 |
26 | export default useDelegationOneDayProfit
27 |
--------------------------------------------------------------------------------
/apps/app/hooks/useGetApr.ts:
--------------------------------------------------------------------------------
1 | import {subsquidClient} from '@/config'
2 | import {useGlobalStateQuery} from '@/lib/subsquidQuery'
3 | import Decimal from 'decimal.js'
4 | import {useCallback} from 'react'
5 |
6 | const ONE_YEAR = 365 * 24 * 60 * 60 * 1000
7 |
8 | const useGetApr = (): ((
9 | aprMultiplier: string | Decimal,
10 | ) => Decimal | undefined) => {
11 | const {data: globalStateData} = useGlobalStateQuery(
12 | subsquidClient,
13 | undefined,
14 | {select: (data) => data.globalStateById},
15 | )
16 |
17 | const {averageBlockTime, idleWorkerShares, budgetPerBlock, treasuryRatio} =
18 | globalStateData ?? {}
19 |
20 | return useCallback(
21 | (aprMultiplier: string | Decimal) => {
22 | if (
23 | averageBlockTime === undefined ||
24 | idleWorkerShares === undefined ||
25 | budgetPerBlock === undefined ||
26 | treasuryRatio === undefined
27 | ) {
28 | return
29 | }
30 | return new Decimal(budgetPerBlock)
31 | .times(new Decimal(1).minus(treasuryRatio))
32 | .times(ONE_YEAR)
33 | .div(averageBlockTime)
34 | .div(idleWorkerShares)
35 | .times(aprMultiplier)
36 | },
37 | [averageBlockTime, idleWorkerShares, budgetPerBlock, treasuryRatio],
38 | )
39 | }
40 |
41 | export default useGetApr
42 |
--------------------------------------------------------------------------------
/apps/app/hooks/useGetAprMultiplier.ts:
--------------------------------------------------------------------------------
1 | import {subsquidClient} from '@/config'
2 | import {apyToApr} from '@/lib/apr'
3 | import {useGlobalStateQuery} from '@/lib/subsquidQuery'
4 | import Decimal from 'decimal.js'
5 | import {useCallback} from 'react'
6 |
7 | const ONE_YEAR = 365 * 24 * 60 * 60 * 1000
8 |
9 | const useGetAprMultiplier = (): ((
10 | aprOrApy: string | Decimal,
11 | isApy?: boolean,
12 | ) => Decimal | undefined) => {
13 | const {data: globalState} = useGlobalStateQuery(subsquidClient, undefined, {
14 | select: (data) => data.globalStateById,
15 | })
16 |
17 | const {averageBlockTime, idleWorkerShares, budgetPerBlock, treasuryRatio} =
18 | globalState ?? {}
19 |
20 | return useCallback(
21 | (aprOrApy: string | Decimal, isApy = false) => {
22 | if (
23 | averageBlockTime === undefined ||
24 | idleWorkerShares === undefined ||
25 | budgetPerBlock === undefined ||
26 | treasuryRatio === undefined
27 | ) {
28 | return
29 | }
30 | try {
31 | const apr = isApy
32 | ? apyToApr(new Decimal(aprOrApy).div(100))
33 | : new Decimal(aprOrApy).div(100)
34 | return apr.div(
35 | new Decimal(budgetPerBlock)
36 | .times(new Decimal(1).minus(treasuryRatio))
37 | .times(ONE_YEAR)
38 | .div(averageBlockTime)
39 | .div(idleWorkerShares),
40 | )
41 | } catch (err) {
42 | // noop
43 | }
44 | },
45 | [averageBlockTime, budgetPerBlock, idleWorkerShares, treasuryRatio],
46 | )
47 | }
48 |
49 | export default useGetAprMultiplier
50 |
--------------------------------------------------------------------------------
/apps/app/hooks/useListenBlockHeight.ts:
--------------------------------------------------------------------------------
1 | import {subsquidClient} from '@/config'
2 | import {useGlobalStateQuery} from '@/lib/subsquidQuery'
3 | import {useQueryClient} from '@tanstack/react-query'
4 | import {useEffect, useRef} from 'react'
5 |
6 | const useListenBlockHeight = (): void => {
7 | const enabled = useRef(false)
8 | const {data: globalStateData} = useGlobalStateQuery(
9 | subsquidClient,
10 | undefined,
11 | {
12 | refetchInterval: 3000,
13 | select: (data) => data.globalStateById,
14 | },
15 | )
16 | const queryClient = useQueryClient()
17 | const height = globalStateData?.height
18 |
19 | useEffect(() => {
20 | if (height != null) {
21 | if (enabled.current) {
22 | void queryClient.invalidateQueries({queryKey: ['AccountById']})
23 | void queryClient.invalidateQueries({queryKey: ['BasePoolById']})
24 | void queryClient.invalidateQueries({
25 | queryKey: ['BasePoolsConnection.infinite'],
26 | })
27 | void queryClient.invalidateQueries({
28 | queryKey: ['BasePoolWhitelistsConnection'],
29 | })
30 | void queryClient.invalidateQueries({queryKey: ['DelegationById']})
31 | void queryClient.invalidateQueries({
32 | queryKey: ['DelegationsConnection'],
33 | })
34 | void queryClient.invalidateQueries({
35 | queryKey: ['DelegationsConnection.infinite'],
36 | })
37 | void queryClient.invalidateQueries({queryKey: ['WorkersConnection']})
38 | void queryClient.invalidateQueries({queryKey: ['OwnedVaults']})
39 | void queryClient.invalidateQueries({queryKey: ['ClaimableStakePools']})
40 | void queryClient.invalidateQueries({queryKey: ['lockedWrappedBalance']})
41 | void queryClient.invalidateQueries({queryKey: ['assetBalance']})
42 | } else {
43 | // Skip the first time
44 | enabled.current = true
45 | }
46 | }
47 | }, [queryClient, height])
48 | }
49 |
50 | export default useListenBlockHeight
51 |
--------------------------------------------------------------------------------
/apps/app/hooks/useLockedWrappedBalance.ts:
--------------------------------------------------------------------------------
1 | import {useQuery} from '@tanstack/react-query'
2 | import Decimal from 'decimal.js'
3 | import usePolkadotApi from './usePolkadotApi'
4 |
5 | const useLockedWrappedBalance = (
6 | account?: string,
7 | ): Decimal | undefined | null => {
8 | const api = usePolkadotApi()
9 | const {data} = useQuery({
10 | queryKey: ['lockedWrappedBalance', api?.runtimeChain, account],
11 | queryFn: async () => {
12 | if (api == null || account == null) {
13 | return null
14 | }
15 | const res = await api.query.phalaWrappedBalances.stakerAccounts(account)
16 | const unwrapped = res.unwrapOr({locked: 0})
17 | return new Decimal(unwrapped.locked.toString()).div(1e12)
18 | },
19 | })
20 |
21 | return data
22 | }
23 |
24 | export default useLockedWrappedBalance
25 |
--------------------------------------------------------------------------------
/apps/app/hooks/usePolkadotApi.ts:
--------------------------------------------------------------------------------
1 | import {PHALA_ENDPOINTS} from '@/config'
2 | import {createPolkadotApi} from '@phala/lib'
3 | import type {ApiPromise} from '@polkadot/api'
4 | import useSWRImmutable from 'swr/immutable'
5 |
6 | const create = async ([endpoint, _]: [string, string]): Promise =>
7 | await createPolkadotApi(endpoint)
8 |
9 | const usePolkadotApi = (): ApiPromise | undefined => {
10 | const {data: polkadotApi} = useSWRImmutable(
11 | [PHALA_ENDPOINTS[0], 'polkadotApi'],
12 | create,
13 | )
14 |
15 | return polkadotApi
16 | }
17 |
18 | export default usePolkadotApi
19 |
--------------------------------------------------------------------------------
/apps/app/hooks/usePoolFavorite.ts:
--------------------------------------------------------------------------------
1 | import {favoritePoolsAtom} from '@/store/common'
2 | import {useAtom} from 'jotai'
3 | import {useCallback, useMemo} from 'react'
4 |
5 | const usePoolFavorite = (pid: string): [boolean, () => void] => {
6 | const [favoritePools, setFavoritePools] = useAtom(favoritePoolsAtom)
7 | const isFavorite = useMemo(
8 | () => favoritePools.includes(pid),
9 | [pid, favoritePools],
10 | )
11 | const toggleFavorite = useCallback(() => {
12 | setFavoritePools(
13 | favoritePools.includes(pid)
14 | ? favoritePools.filter((v) => v !== pid)
15 | : [...favoritePools, pid],
16 | )
17 | }, [setFavoritePools, favoritePools, pid])
18 |
19 | return [isFavorite, toggleFavorite]
20 | }
21 |
22 | export default usePoolFavorite
23 |
--------------------------------------------------------------------------------
/apps/app/hooks/usePoolIntro.ts:
--------------------------------------------------------------------------------
1 | import type {ApiPromise} from '@polkadot/api'
2 | import {hexToString} from '@polkadot/util'
3 | import Ajv, {type JSONSchemaType} from 'ajv'
4 | import useSWR from 'swr'
5 | import usePolkadotApi from './usePolkadotApi'
6 |
7 | export interface PoolIntro {
8 | telegram?: string
9 | discord?: string
10 | wechat?: string
11 | twitter?: string
12 | email?: string
13 | forum?: string
14 | basic?: string
15 | ann?: string
16 | version: 1
17 | }
18 |
19 | const schema: JSONSchemaType = {
20 | type: 'object',
21 | properties: {
22 | telegram: {type: 'string', nullable: true},
23 | discord: {type: 'string', nullable: true},
24 | wechat: {type: 'string', nullable: true},
25 | twitter: {type: 'string', nullable: true},
26 | email: {type: 'string', nullable: true},
27 | forum: {type: 'string', nullable: true},
28 | basic: {type: 'string', nullable: true},
29 | ann: {type: 'string', nullable: true},
30 | version: {type: 'integer'},
31 | },
32 | required: ['version'],
33 | }
34 |
35 | const ajv = new Ajv()
36 | const validate = ajv.compile(schema)
37 |
38 | const fetchPoolIntro = async (
39 | api: ApiPromise,
40 | pid: string,
41 | ): Promise => {
42 | return await api.query.phalaBasePool.poolDescriptions(pid).then((bytes) => {
43 | try {
44 | const hex = bytes.unwrap().toHex()
45 | const string = hexToString(hex)
46 | const json = JSON.parse(string)
47 | if (validate(json)) {
48 | return json
49 | }
50 | return {version: 1}
51 | } catch (err) {
52 | return {version: 1}
53 | }
54 | })
55 | }
56 |
57 | const usePoolIntro = (pid: string): PoolIntro | undefined => {
58 | const api = usePolkadotApi()
59 | const {data} = useSWR(
60 | api != null && [api.runtimeChain.toString(), pid, 'poolIntro'],
61 | ([_, pid]) => {
62 | if (api == null) return
63 | return fetchPoolIntro(api, pid)
64 | },
65 | )
66 |
67 | return data
68 | }
69 |
70 | export default usePoolIntro
71 |
--------------------------------------------------------------------------------
/apps/app/hooks/useResetVault.ts:
--------------------------------------------------------------------------------
1 | import {vaultIdAtom} from '@/store/common'
2 | import {polkadotAccountAtom} from '@phala/store'
3 | import {useAtom} from 'jotai'
4 | import {useEffect, useRef} from 'react'
5 |
6 | const useResetVault = (): void => {
7 | const [, setVaultId] = useAtom(vaultIdAtom)
8 | const [account] = useAtom(polkadotAccountAtom)
9 | const prevAccountAddress = useRef(undefined)
10 | useEffect(() => {
11 | if (prevAccountAddress.current != null) {
12 | setVaultId(null)
13 | }
14 | prevAccountAddress.current = account?.address
15 | }, [account?.address, setVaultId])
16 | }
17 |
18 | export default useResetVault
19 |
--------------------------------------------------------------------------------
/apps/app/hooks/useSelectedVaultState.ts:
--------------------------------------------------------------------------------
1 | import type {AccountByIdQuery} from '@/lib/subsquidQuery'
2 | import {vaultIdAtom} from '@/store/common'
3 | import {useAtom} from 'jotai'
4 | import {useMemo} from 'react'
5 | import useAccountQuery from './useAccountQuery'
6 |
7 | const useSelectedVaultState = ():
8 | | Exclude<
9 | AccountByIdQuery['accountById'],
10 | null | undefined
11 | >['ownedPools'][number]
12 | | undefined
13 | | null => {
14 | const [vaultId] = useAtom(vaultIdAtom)
15 | const {data: accountData} = useAccountQuery()
16 |
17 | const selectedVault = useMemo(() => {
18 | return vaultId != null
19 | ? accountData?.ownedPools.find((x) => x.id === vaultId)
20 | : null
21 | }, [accountData?.ownedPools, vaultId])
22 |
23 | return selectedVault
24 | }
25 |
26 | export default useSelectedVaultState
27 |
--------------------------------------------------------------------------------
/apps/app/hooks/useShowWalletDialog.ts:
--------------------------------------------------------------------------------
1 | import {walletDialogOpenAtom} from '@/store/ui'
2 | import {useTimeout} from '@phala/lib'
3 | import {polkadotAccountAtom} from '@phala/store'
4 | import {useAtom} from 'jotai'
5 | import {useRouter} from 'next/router'
6 |
7 | const whiteListPathnames = ['/wiki', '/staking', '/khala-assets', '/']
8 |
9 | const useShowWalletDialog = (): void => {
10 | const router = useRouter()
11 | const [polkadotAccount] = useAtom(polkadotAccountAtom)
12 | const [, setWalletDialogOpen] = useAtom(walletDialogOpenAtom)
13 | useTimeout(() => {
14 | if (!whiteListPathnames.includes(router.pathname) && !polkadotAccount) {
15 | setWalletDialogOpen(true)
16 | }
17 | }, 5000)
18 | }
19 |
20 | export default useShowWalletDialog
21 |
--------------------------------------------------------------------------------
/apps/app/hooks/useToday.ts:
--------------------------------------------------------------------------------
1 | import useSWR from 'swr'
2 |
3 | const getToday = () => {
4 | const today = new Date()
5 | today.setUTCHours(0, 0, 0, 0)
6 | return today.getTime()
7 | }
8 |
9 | const useToday = () => {
10 | const {data} = useSWR('today', getToday, {
11 | fallbackData: getToday(),
12 | refreshInterval: 60 * 1000,
13 | revalidateIfStale: true,
14 | refreshWhenHidden: true,
15 | refreshWhenOffline: true,
16 | })
17 | return data
18 | }
19 |
20 | export default useToday
21 |
--------------------------------------------------------------------------------
/apps/app/hooks/useWrapAsset.ts:
--------------------------------------------------------------------------------
1 | import {assetVisibleAtom} from '@/store/ui'
2 | import {useAtom} from 'jotai'
3 | import {useCallback} from 'react'
4 |
5 | const useWrapAsset = (): ((asset: T) => string | T) => {
6 | const [assetVisible] = useAtom(assetVisibleAtom)
7 | return useCallback(
8 | (asset: T): T | string => {
9 | return assetVisible ? asset : '***'
10 | },
11 | [assetVisible],
12 | )
13 | }
14 |
15 | export default useWrapAsset
16 |
--------------------------------------------------------------------------------
/apps/app/lib/apr.ts:
--------------------------------------------------------------------------------
1 | import Decimal from 'decimal.js'
2 |
3 | const n = 365
4 |
5 | export const aprToApy = (apr: Decimal): Decimal =>
6 | apr.div(n).plus(1).pow(n).minus(1)
7 | export const apyToApr = (apy: Decimal): Decimal =>
8 | apy.plus(1).pow(new Decimal(1).div(n)).minus(1).times(n)
9 |
--------------------------------------------------------------------------------
/apps/app/lib/getBasePoolServerSideProps.ts:
--------------------------------------------------------------------------------
1 | import {subsquidClient} from '@/config'
2 | import {sleep} from '@phala/lib'
3 | import type {GetServerSideProps} from 'next'
4 | import type {BasePoolByIdQuery, BasePoolKind} from './subsquidQuery'
5 | import {getSdk} from './subsquidSdk'
6 |
7 | export interface BasePoolServerSideProps {
8 | pid: string
9 | initialData: BasePoolByIdQuery | null
10 | initialDataUpdatedAt: number
11 | }
12 |
13 | const getBasePoolServerSideProps =
14 | (kind: BasePoolKind): GetServerSideProps =>
15 | async (ctx) => {
16 | const {pid} = ctx.params ?? {}
17 | if (typeof pid !== 'string') {
18 | throw new Error('Invalid params')
19 | }
20 | const subsquidSdk = getSdk(subsquidClient)
21 | const initialData = await Promise.race([
22 | subsquidSdk.BasePoolById({id: pid}),
23 | sleep(3000),
24 | ])
25 |
26 | if (initialData != null && initialData.basePoolById?.kind !== kind) {
27 | return {notFound: true}
28 | }
29 | return {
30 | props: {
31 | pid,
32 | initialData: initialData ?? null,
33 | initialDataUpdatedAt: new Date().getTime(),
34 | },
35 | }
36 | }
37 |
38 | export default getBasePoolServerSideProps
39 |
--------------------------------------------------------------------------------
/apps/app/lib/getDelegationProfit.ts:
--------------------------------------------------------------------------------
1 | import Decimal from 'decimal.js'
2 | import type {DelegationSnapshot} from './subsquidQuery'
3 |
4 | const getDelegationProfit = (
5 | current: Pick,
6 | prev: Pick,
7 | ): Decimal => {
8 | let profit = new Decimal(current.value)
9 | .minus(prev.value)
10 | .minus(current.cost)
11 | .plus(prev.cost)
12 | profit = Decimal.max(profit, 0)
13 |
14 | return profit
15 | }
16 |
17 | export default getDelegationProfit
18 |
--------------------------------------------------------------------------------
/apps/app/lib/getPoolPath.ts:
--------------------------------------------------------------------------------
1 | import type {BasePoolKind} from './subsquidQuery'
2 |
3 | const getPoolPath = (kind: BasePoolKind, pid: string): string =>
4 | `/${kind === 'Vault' ? 'vault' : 'stake-pool'}/${pid}`
5 |
6 | export default getPoolPath
7 |
--------------------------------------------------------------------------------
/apps/app/lib/getVaultOwnerCut.ts:
--------------------------------------------------------------------------------
1 | import Decimal from 'decimal.js'
2 | import type {BasePool, Vault} from './subsquidQuery'
3 |
4 | const getVaultOwnerCut = (
5 | basePool: Pick & {
6 | vault?: Pick | null
7 | },
8 | ): Decimal => {
9 | const {vault, sharePrice, commission, totalShares} = basePool
10 | if (vault == null) return new Decimal(0)
11 | const {lastSharePriceCheckpoint} = vault
12 | return Decimal.max(
13 | new Decimal(sharePrice)
14 | .minus(lastSharePriceCheckpoint)
15 | .times(commission)
16 | .times(totalShares),
17 | 0,
18 | )
19 | }
20 |
21 | export default getVaultOwnerCut
22 |
--------------------------------------------------------------------------------
/apps/app/lib/styles.ts:
--------------------------------------------------------------------------------
1 | import type {SerializedStyles} from '@emotion/react'
2 | import {type Theme, css} from '@mui/material'
3 | import {barlow} from './theme'
4 |
5 | export const globalStyles = (theme: Theme): SerializedStyles => css`
6 | * {
7 | min-width: 0;
8 | }
9 |
10 | body {
11 | min-height: 100vh;
12 | background-image: linear-gradient(
13 | to bottom,
14 | #1f222e 0%,
15 | #1f222e 200px,
16 | ${theme.palette.background.default} 580px
17 | );
18 | /* background-attachment: fixed; */
19 |
20 | ${theme.breakpoints.down('sm')} {
21 | background-image: linear-gradient(
22 | to bottom,
23 | #1f222e 0%,
24 | #1f222e 100px,
25 | ${theme.palette.background.default} 300px
26 | );
27 | }
28 | }
29 |
30 | .notistack-containerAnchorOriginTopRight {
31 | top: 112px !important;
32 | }
33 |
34 | sub {
35 | font-size: 62.5%;
36 | vertical-align: baseline;
37 | margin-left: 0.25em;
38 | }
39 |
40 | .recharts-cartesian-axis-tick-value {
41 | font-size: 0.75rem;
42 | font-family: ${barlow.style.fontFamily};
43 | }
44 |
45 | .recharts-tooltip-wrapper:focus-visible {
46 | outline: none;
47 | }
48 | `
49 |
--------------------------------------------------------------------------------
/apps/app/next.config.ts:
--------------------------------------------------------------------------------
1 | import withBundleAnalyzer from '@next/bundle-analyzer'
2 | import type {NextConfig} from 'next'
3 |
4 | const bundleAnalyzer = withBundleAnalyzer({
5 | enabled: process.env.ANALYZE === 'true',
6 | })
7 |
8 | const nextConfig: NextConfig = {
9 | transpilePackages: ['jotai-devtools'],
10 | eslint: {
11 | ignoreDuringBuilds: true,
12 | dirs: ['pages', 'components', 'lib', 'hooks', 'store', 'types'],
13 | },
14 | modularizeImports: {
15 | '@mui/icons-material': {
16 | transform: '@mui/icons-material/{{member}}',
17 | },
18 | },
19 | reactStrictMode: true,
20 | images: {
21 | remotePatterns: [
22 | {
23 | protocol: 'https',
24 | hostname: '*.coinmarketcap.com',
25 | pathname: '/static/img/coins/**',
26 | },
27 | ],
28 | },
29 | webpack(config) {
30 | config.module.rules.push({
31 | test: /\.svg$/i,
32 | issuer: /\.[jt]sx?$/,
33 | use: [{loader: '@svgr/webpack', options: {dimensions: false}}],
34 | })
35 |
36 | return config
37 | },
38 | async redirects() {
39 | return [
40 | {
41 | source: '/khala/:path*',
42 | destination: '/khala-assets',
43 | permanent: false,
44 | },
45 | {
46 | source: '/phala/:path*',
47 | destination: '/:path*',
48 | permanent: false,
49 | },
50 | ]
51 | },
52 | experimental: {
53 | swcPlugins: [
54 | // ['@swc-jotai/debug-label', {}],
55 | // ['@swc-jotai/react-refresh', {}],
56 | ],
57 | turbo: {
58 | rules: {
59 | loaders: {
60 | '*.svg': {
61 | loaders: [{loader: '@svgr/webpack', options: {dimensions: false}}],
62 | as: '*.js',
63 | },
64 | },
65 | },
66 | },
67 | },
68 | }
69 |
70 | export default bundleAnalyzer(nextConfig)
71 |
--------------------------------------------------------------------------------
/apps/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@phala/app",
3 | "version": "2.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "next build",
7 | "dev": "next dev -p 3001",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "@emotion/cache": "^11.14.0",
12 | "@emotion/react": "^11.14.0",
13 | "@emotion/server": "^11.11.0",
14 | "@emotion/styled": "^11.14.0",
15 | "@fortawesome/fontawesome-svg-core": "^6.7.2",
16 | "@fortawesome/free-brands-svg-icons": "^6.7.2",
17 | "@fortawesome/free-regular-svg-icons": "^6.7.2",
18 | "@fortawesome/free-solid-svg-icons": "^6.7.2",
19 | "@fortawesome/react-fontawesome": "^0.2.2",
20 | "@json2csv/plainjs": "^7.0.6",
21 | "@mui/icons-material": "^6.4.11",
22 | "@mui/lab": "latest-v6",
23 | "@mui/material": "^6.4.11",
24 | "@mui/material-nextjs": "^6.4.3",
25 | "@mui/x-data-grid": "^7.29.0",
26 | "@next/third-parties": "^15.3.1",
27 | "@phala/lib": "workspace:*",
28 | "@phala/store": "workspace:*",
29 | "@polkadot/api": "^15.9.2",
30 | "@polkadot/extension-inject": "^0.58.8",
31 | "@polkadot/keyring": "^13.4.4",
32 | "@polkadot/react-identicon": "^3.13.1",
33 | "@polkadot/types": "^15.9.2",
34 | "@polkadot/util": "^13.4.4",
35 | "@rainbow-me/rainbowkit": "^2.2.4",
36 | "@talismn/connect-wallets": "^1.2.8",
37 | "@tanstack/react-query": "^5.74.4",
38 | "@tanstack/react-query-devtools": "^5.74.4",
39 | "ajv": "^8.17.1",
40 | "date-fns": "^4.1.0",
41 | "decimal.js": "^10.5.0",
42 | "graphql": "^16.10.0",
43 | "graphql-request": "^7.1.2",
44 | "graphql-tag": "^2.12.6",
45 | "jotai": "^2.12.3",
46 | "jotai-devtools": "^0.10.1",
47 | "mutative": "^1.1.0",
48 | "next": "^15.3.1",
49 | "notistack": "^3.0.2",
50 | "radash": "^12.1.0",
51 | "react": "^18.3.1",
52 | "react-dom": "^18.3.1",
53 | "react-intersection-observer": "^9.16.0",
54 | "react-markdown": "^9.1.0",
55 | "react-snap-carousel": "^0.5.1",
56 | "recharts": "2.15.0",
57 | "sharp": "^0.33.5",
58 | "swr": "^2.3.3",
59 | "viem": "^2.27.2",
60 | "wagmi": "^2.14.16",
61 | "wretch": "^2.11.0"
62 | },
63 | "devDependencies": {
64 | "@next/bundle-analyzer": "^15.3.1",
65 | "@svgr/webpack": "^8.1.0",
66 | "@swc-jotai/debug-label": "^0.2.0",
67 | "@swc-jotai/react-refresh": "^0.3.0",
68 | "@types/react": "^18.3.20",
69 | "@types/react-dom": "^18.3.6",
70 | "@types/sharp": "^0.32.0"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/apps/app/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import Layout from '@/components/Layout'
2 | import SnackbarProvider from '@/components/SnackbarProvider'
3 | import {Web3Provider} from '@/components/Web3Provider'
4 | import ZendeskWidget from '@/components/ZendeskWidget'
5 | import {globalStyles} from '@/lib/styles'
6 | import {theme} from '@/lib/theme'
7 | import {
8 | CssBaseline,
9 | GlobalStyles,
10 | ThemeProvider as MuiThemeProvider,
11 | } from '@mui/material'
12 | import {AppCacheProvider} from '@mui/material-nextjs/v15-pagesRouter'
13 | import {GoogleAnalytics} from '@next/third-parties/google'
14 | // import {JotaiDevTools} from '@phala/lib'
15 | import {
16 | QueryCache,
17 | QueryClient,
18 | QueryClientProvider,
19 | } from '@tanstack/react-query'
20 | import {ReactQueryDevtools} from '@tanstack/react-query-devtools'
21 | import Decimal from 'decimal.js'
22 | import {Provider as JotaiProvider} from 'jotai'
23 | import type {AppProps} from 'next/app'
24 | import {SWRConfig} from 'swr'
25 |
26 | Decimal.set({toExpNeg: -9e15, toExpPos: 9e15, precision: 50})
27 |
28 | // @ts-ignore
29 | BigInt.prototype.toJSON = function () {
30 | return this.toString()
31 | }
32 |
33 | const queryClient = new QueryClient({
34 | queryCache: new QueryCache({
35 | onError: (error) => {
36 | console.error(error)
37 | },
38 | }),
39 | })
40 |
41 | const MyApp = (props: AppProps) => {
42 | const {Component, pageProps} = props
43 |
44 | return (
45 | {
48 | if (process.env.NODE_ENV === 'development') {
49 | console.error(key, error)
50 | }
51 | },
52 | }}
53 | >
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | {/* */}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | )
78 | }
79 |
80 | export default MyApp
81 |
--------------------------------------------------------------------------------
/apps/app/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import {theme} from '@/lib/theme'
2 | import {
3 | DocumentHeadTags,
4 | type DocumentHeadTagsProps,
5 | documentGetInitialProps,
6 | } from '@mui/material-nextjs/v15-pagesRouter'
7 | import {
8 | type DocumentContext,
9 | type DocumentProps,
10 | Head,
11 | Html,
12 | Main,
13 | NextScript,
14 | } from 'next/document'
15 |
16 | export default function MyDocument(
17 | props: DocumentProps & DocumentHeadTagsProps,
18 | ) {
19 | return (
20 |
21 |
22 | {/* PWA primary color */}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 |
36 | MyDocument.getInitialProps = async (ctx: DocumentContext) => {
37 | const finalProps = await documentGetInitialProps(ctx)
38 | return finalProps
39 | }
40 |
--------------------------------------------------------------------------------
/apps/app/pages/api/[chain]/snapshots/account/[account].ts:
--------------------------------------------------------------------------------
1 | import {khalaSubsquidClient, subsquidClient} from '@/config'
2 | import {getSdk} from '@/lib/subsquidSdk'
3 | import {Parser} from '@json2csv/plainjs'
4 | import type {NextApiRequest, NextApiResponse} from 'next'
5 |
6 | export default async function handler(
7 | req: NextApiRequest,
8 | res: NextApiResponse,
9 | ) {
10 | const {chain, account} = req.query
11 | if ((chain !== 'phala' && chain !== 'khala') || typeof account !== 'string') {
12 | res.status(500).send('Invalid params')
13 | } else {
14 | const client = chain === 'phala' ? subsquidClient : khalaSubsquidClient
15 | const sdk = getSdk(client)
16 | const data = await sdk.AccountSnapshotsConnection({
17 | orderBy: 'updatedTime_DESC',
18 | first: 2000,
19 | where: {account_eq: account},
20 | withDelegationValue: true,
21 | withCumulativeStakePoolOwnerRewards: true,
22 | withCumulativeVaultOwnerRewards: true,
23 | })
24 |
25 | const result = data.accountSnapshotsConnection.edges.map(({node}) => node)
26 |
27 | console.log(111, result)
28 | const csv = new Parser().parse(result)
29 | res.setHeader('Content-Type', 'text/csv')
30 | res.setHeader(
31 | 'Content-Disposition',
32 | `attachment;filename=snapshots_account_${account}_${new Date()
33 | .toISOString()
34 | .slice(0, 10)}.csv`,
35 | )
36 | res.status(200).send(csv)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/apps/app/pages/api/[chain]/snapshots/delegation/[id].ts:
--------------------------------------------------------------------------------
1 | import {khalaSubsquidClient, subsquidClient} from '@/config'
2 | import {getSdk} from '@/lib/subsquidSdk'
3 | import {Parser} from '@json2csv/plainjs'
4 | import type {NextApiRequest, NextApiResponse} from 'next'
5 |
6 | export default async function handler(
7 | req: NextApiRequest,
8 | res: NextApiResponse,
9 | ) {
10 | const {chain, id} = req.query
11 | if ((chain !== 'phala' && chain !== 'khala') || typeof id !== 'string') {
12 | res.status(500).send('Invalid params')
13 | } else {
14 | const client = chain === 'phala' ? subsquidClient : khalaSubsquidClient
15 | const sdk = getSdk(client)
16 | const data = await sdk.DelegationSnapshotsConnection({
17 | orderBy: 'updatedTime_DESC',
18 | first: 2000,
19 | where: {delegation_eq: id},
20 | })
21 |
22 | const result = data.delegationSnapshotsConnection.edges.map(
23 | ({node}) => node,
24 | )
25 | const csv = new Parser().parse(result)
26 | res.setHeader('Content-Type', 'text/csv')
27 | res.setHeader(
28 | 'Content-Disposition',
29 | `attachment;filename=snapshots_delegation_${id}_${new Date()
30 | .toISOString()
31 | .slice(0, 10)}.csv`,
32 | )
33 | res.status(200).send(csv)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/apps/app/pages/api/[chain]/snapshots/pool/[pid].ts:
--------------------------------------------------------------------------------
1 | import {khalaSubsquidClient, subsquidClient} from '@/config'
2 | import {getSdk} from '@/lib/subsquidSdk'
3 | import {Parser} from '@json2csv/plainjs'
4 | import type {NextApiRequest, NextApiResponse} from 'next'
5 |
6 | export default async function handler(
7 | req: NextApiRequest,
8 | res: NextApiResponse,
9 | ) {
10 | const {chain, pid} = req.query
11 | if (
12 | (chain !== 'phala' && chain !== 'khala') ||
13 | typeof pid !== 'string' ||
14 | Number.isNaN(Number.parseInt(pid))
15 | ) {
16 | res.status(500).send('Invalid params')
17 | } else {
18 | const client = chain === 'phala' ? subsquidClient : khalaSubsquidClient
19 | const sdk = getSdk(client)
20 | const data = await sdk.BasePoolSnapshotsConnection({
21 | orderBy: 'updatedTime_DESC',
22 | first: 2000,
23 | withCommission: true,
24 | withApr: true,
25 | withCumulativeOwnerRewards: true,
26 | withDelegatorCount: true,
27 | withStakePoolCount: true,
28 | withTotalValue: true,
29 | withWorkerCount: true,
30 | withSharePrice: true,
31 | withTotalShares: true,
32 | where: {basePool_eq: pid},
33 | })
34 |
35 | const result = data.basePoolSnapshotsConnection.edges.map(({node}) => node)
36 | const csv = new Parser().parse(result)
37 | res.setHeader('Content-Type', 'text/csv')
38 | res.setHeader(
39 | 'Content-Disposition',
40 | `attachment;filename=snapshots_pool_${pid}_${new Date()
41 | .toISOString()
42 | .slice(0, 10)}.csv`,
43 | )
44 | res.status(200).send(csv)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/apps/app/pages/api/circulation.ts:
--------------------------------------------------------------------------------
1 | import type {NextApiRequest, NextApiResponse} from 'next'
2 |
3 | const handler = async (
4 | _req: NextApiRequest,
5 | res: NextApiResponse,
6 | ): Promise => {
7 | const circulation = await fetch(
8 | 'https://pha-circulation-server.vercel.app/api/circulation',
9 | ).then(async (res) => await res.text())
10 | res.setHeader('Cache-Control', 'public, max-age=60')
11 | res.status(200).send(circulation)
12 | }
13 |
14 | export default handler
15 |
--------------------------------------------------------------------------------
/apps/app/pages/delegate/my-delegation.tsx:
--------------------------------------------------------------------------------
1 | import DelegationList from '@/components/Delegation/List'
2 | import DelegationDetailCard from '@/components/DelegationDetailCard'
3 | import DelegationScatterChart from '@/components/DelegationScatterChart'
4 | import PageHeader from '@/components/PageHeader'
5 | import useSelectedVaultState from '@/hooks/useSelectedVaultState'
6 | import {Box, Paper, Stack} from '@mui/material'
7 | import {polkadotAccountAtom} from '@phala/store'
8 | import {useAtom} from 'jotai'
9 | import type {FC} from 'react'
10 |
11 | const MyDelegation: FC = () => {
12 | const [polkadotAccount] = useAtom(polkadotAccountAtom)
13 | const selectedVaultState = useSelectedVaultState()
14 | return (
15 | <>
16 |
17 |
18 |
19 |
28 |
29 |
36 |
37 |
38 |
39 |
40 |
49 |
50 | >
51 | )
52 | }
53 |
54 | export default MyDelegation
55 |
--------------------------------------------------------------------------------
/apps/app/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import DashboardAccount from '@/components/DashboardAccount'
2 | import DashboardAssetList from '@/components/DashboardAssetList'
3 | import DashboardCarousel from '@/components/DashboardCarousel'
4 | import DashboardNftList from '@/components/DashboardNftList'
5 | import Title from '@/components/Title'
6 | import {Box, Paper, Stack} from '@mui/material'
7 | import type {FC} from 'react'
8 |
9 | const Dashboard: FC = () => {
10 | return (
11 | <>
12 | Dashboard
13 |
18 |
19 |
20 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | >
39 | )
40 | }
41 |
42 | export default Dashboard
43 |
--------------------------------------------------------------------------------
/apps/app/pages/stake-pool/[pid].tsx:
--------------------------------------------------------------------------------
1 | import DetailPage from '@/components/BasePool/DetailPage'
2 | import WorkerList from '@/components/BasePool/Worker/List'
3 | import {subsquidClient} from '@/config'
4 | import getBasePoolServerSideProps, {
5 | type BasePoolServerSideProps,
6 | } from '@/lib/getBasePoolServerSideProps'
7 | import {useBasePoolByIdQuery} from '@/lib/subsquidQuery'
8 | import {Box} from '@mui/material'
9 | import {polkadotAccountAtom} from '@phala/store'
10 | import {useAtom} from 'jotai'
11 | import type {NextPage} from 'next'
12 |
13 | export const getServerSideProps = getBasePoolServerSideProps('StakePool')
14 |
15 | const StakePool: NextPage = ({
16 | pid,
17 | initialData,
18 | initialDataUpdatedAt,
19 | }) => {
20 | const [account] = useAtom(polkadotAccountAtom)
21 | const {data: basePool} = useBasePoolByIdQuery(
22 | subsquidClient,
23 | {id: pid, accountId: account?.address},
24 | {
25 | initialData: initialData ?? undefined,
26 | initialDataUpdatedAt,
27 | select: (data) => data.basePoolById,
28 | },
29 | )
30 |
31 | if (basePool == null) return null
32 |
33 | return (
34 | <>
35 |
36 |
37 |
38 |
39 | >
40 | )
41 | }
42 |
43 | export default StakePool
44 |
--------------------------------------------------------------------------------
/apps/app/pages/staking.tsx:
--------------------------------------------------------------------------------
1 | import poweredBySlpx from '@/assets/powered_by_slpx.png'
2 | import PageHeader from '@/components/PageHeader'
3 | import Staking from '@/components/Staking'
4 | import {Box, Chip} from '@mui/material'
5 | import {ConnectButton} from '@rainbow-me/rainbowkit'
6 | import Image from 'next/image'
7 |
8 | const Page = () => {
9 | return (
10 | <>
11 |
20 | Staking
21 |
22 |
28 |
35 |
36 |
37 | }
38 | >
39 |
40 |
41 |
42 | >
43 | )
44 | }
45 | export default Page
46 |
--------------------------------------------------------------------------------
/apps/app/pages/vault/[pid].tsx:
--------------------------------------------------------------------------------
1 | import DetailPage from '@/components/BasePool/DetailPage'
2 | import DelegationList from '@/components/Delegation/List'
3 | import {subsquidClient} from '@/config'
4 | import getBasePoolServerSideProps, {
5 | type BasePoolServerSideProps,
6 | } from '@/lib/getBasePoolServerSideProps'
7 | import {useBasePoolByIdQuery} from '@/lib/subsquidQuery'
8 | import {Box} from '@mui/material'
9 | import {polkadotAccountAtom} from '@phala/store'
10 | import {useAtom} from 'jotai'
11 | import type {NextPage} from 'next'
12 |
13 | export const getServerSideProps = getBasePoolServerSideProps('Vault')
14 |
15 | const Vault: NextPage = ({
16 | pid,
17 | initialData,
18 | initialDataUpdatedAt,
19 | }) => {
20 | const [account] = useAtom(polkadotAccountAtom)
21 | const {data: basePool} = useBasePoolByIdQuery(
22 | subsquidClient,
23 | {id: pid},
24 | {
25 | initialData: initialData ?? undefined,
26 | initialDataUpdatedAt,
27 | select: (data) => data.basePoolById,
28 | },
29 | )
30 |
31 | if (basePool == null) return null
32 |
33 | return (
34 | <>
35 |
36 |
37 |
38 |
44 |
45 | >
46 | )
47 | }
48 |
49 | export default Vault
50 |
--------------------------------------------------------------------------------
/apps/app/pages/wiki.tsx:
--------------------------------------------------------------------------------
1 | import PageHeader from '@/components/PageHeader'
2 | import Wiki from '@/components/Wiki'
3 | import type {NextPage} from 'next'
4 |
5 | const WikiPage: NextPage = () => {
6 | return (
7 | <>
8 |
9 |
10 | >
11 | )
12 | }
13 |
14 | export default WikiPage
15 |
--------------------------------------------------------------------------------
/apps/app/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/apps/app/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/apps/app/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/apps/app/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/public/favicon-16x16.png
--------------------------------------------------------------------------------
/apps/app/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/public/favicon-32x32.png
--------------------------------------------------------------------------------
/apps/app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/app/public/favicon.ico
--------------------------------------------------------------------------------
/apps/app/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Phala App",
3 | "short_name": "Phala App",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#000000",
17 | "background_color": "#000000",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/apps/app/public/vpha.svg:
--------------------------------------------------------------------------------
1 |
41 |
--------------------------------------------------------------------------------
/apps/app/store/common.ts:
--------------------------------------------------------------------------------
1 | import type {WikiEntry} from '@/assets/wikiData'
2 | import {atom} from 'jotai'
3 | import {atomWithStorage} from 'jotai/utils'
4 |
5 | export const vaultIdAtom = atomWithStorage(
6 | 'jotai:phala_vault',
7 | null,
8 | )
9 |
10 | export const favoritePoolsAtom = atomWithStorage(
11 | 'jotai:phala_favorite_pools',
12 | [],
13 | )
14 | export const basePoolMinDelegableAtom = atomWithStorage(
15 | 'jotai:base_pool_min_delegable',
16 | '100',
17 | )
18 |
19 | export const basePoolMinTvlAtom = atomWithStorage(
20 | 'jotai:base_pool_min_tvl',
21 | '100',
22 | )
23 |
24 | export const basePoolMinAprAtom = atomWithStorage(
25 | 'jotai:base_pool_min_apr',
26 | '',
27 | )
28 |
29 | export const basePoolMinWorkersAtom = atomWithStorage(
30 | 'jotai:base_pool_min_workers',
31 | '',
32 | )
33 |
34 | export const wikiExpandEntryAtom = atom(null)
35 |
--------------------------------------------------------------------------------
/apps/app/store/ui.ts:
--------------------------------------------------------------------------------
1 | import {atom} from 'jotai'
2 | import {atomWithStorage} from 'jotai/utils'
3 |
4 | export const walletDialogOpenAtom = atom(false)
5 | export const wikiDialogOpenAtom = atom(false)
6 | export const assetVisibleAtom = atomWithStorage('jotai:asset_visible', true)
7 | export const hideSmallBalanceAtom = atomWithStorage(
8 | 'jotai:hide_small_balance',
9 | true,
10 | )
11 |
--------------------------------------------------------------------------------
/apps/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "jsx": "preserve",
6 | "jsxImportSource": "@emotion/react",
7 | "baseUrl": ".",
8 | "paths": {
9 | "@/assets/*": ["assets/*"],
10 | "@/components/*": ["components/*"],
11 | "@/hooks/*": ["hooks/*"],
12 | "@/lib/*": ["lib/*"],
13 | "@/store/*": ["store/*"],
14 | "@/config": ["config"]
15 | },
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "strictNullChecks": true
22 | },
23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
24 | "exclude": ["node_modules", "polkadot-types/**/*.ts"]
25 | }
26 |
--------------------------------------------------------------------------------
/apps/app/types/global.d.ts:
--------------------------------------------------------------------------------
1 | import '@phala/polkadot-types/interfaces/augment-api'
2 | import '@phala/polkadot-types/interfaces/augment-types'
3 | import '@phala/polkadot-types/interfaces/types-lookup'
4 |
--------------------------------------------------------------------------------
/apps/app/types/image.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | const ReactComponent: React.FC>
3 |
4 | export default ReactComponent
5 | }
6 |
--------------------------------------------------------------------------------
/apps/app/types/mui.d.ts:
--------------------------------------------------------------------------------
1 | import type {} from '@mui/lab/themeAugmentation'
2 |
3 | declare module '@mui/material/styles' {
4 | interface TypographyVariants {
5 | num1: React.CSSProperties
6 | num2: React.CSSProperties
7 | num3: React.CSSProperties
8 | num4: React.CSSProperties
9 | num5: React.CSSProperties
10 | num6: React.CSSProperties
11 | }
12 |
13 | interface TypographyVariantsOptions {
14 | num1?: React.CSSProperties
15 | num2?: React.CSSProperties
16 | num3?: React.CSSProperties
17 | num4?: React.CSSProperties
18 | num5?: React.CSSProperties
19 | num6?: React.CSSProperties
20 | }
21 | }
22 |
23 | // Update the Typography's variant prop options
24 | declare module '@mui/material/Typography' {
25 | interface TypographyPropsVariantOverrides {
26 | num1: true
27 | num2: true
28 | num3: true
29 | num4: true
30 | num5: true
31 | num6: true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/apps/app/types/reset.d.ts:
--------------------------------------------------------------------------------
1 | import '@total-typescript/ts-reset'
2 |
--------------------------------------------------------------------------------
/apps/subbridge/README.md:
--------------------------------------------------------------------------------
1 | # [SubBridge](https://subbridge.io)
2 |
--------------------------------------------------------------------------------
/apps/subbridge/assets/evm_chains_data.json:
--------------------------------------------------------------------------------
1 | {
2 | "1284": {
3 | "chainName": "Moonbeam",
4 | "rpcUrls": [
5 | "https://rpc.api.moonbeam.network",
6 | "https://moonbeam.public.blastapi.io",
7 | "https://rpc.ankr.com/moonbeam"
8 | ],
9 | "nativeCurrency": {
10 | "name": "Moonbeam",
11 | "symbol": "GLMR",
12 | "decimals": 18
13 | },
14 | "chainId": "0x504",
15 | "blockExplorerUrls": ["https://moonbeam.moonscan.io/"]
16 | },
17 | "1285": {
18 | "chainName": "Moonriver",
19 | "rpcUrls": [
20 | "https://rpc.api.moonriver.moonbeam.network",
21 | "wss://wss.api.moonriver.moonbeam.network"
22 | ],
23 | "nativeCurrency": {
24 | "name": "Moonriver",
25 | "symbol": "MOVR",
26 | "decimals": 18
27 | },
28 | "chainId": "0x505",
29 | "blockExplorerUrls": ["https://moonriver.moonscan.io"]
30 | },
31 | "1287": {
32 | "chainName": "Moonbase Alpha",
33 | "rpcUrls": [
34 | "https://rpc.api.moonbase.moonbeam.network",
35 | "wss://wss.api.moonbase.moonbeam.network"
36 | ],
37 | "nativeCurrency": {
38 | "name": "Dev",
39 | "symbol": "DEV",
40 | "decimals": 18
41 | },
42 | "chainId": "0x507",
43 | "blockExplorerUrls": ["https://moonbase.moonscan.io"]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/apps/subbridge/assets/moonriver_xtokens_abi.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputs": [
4 | {
5 | "internalType": "address",
6 | "name": "currency_address",
7 | "type": "address"
8 | },
9 | {"internalType": "uint256", "name": "amount", "type": "uint256"},
10 | {
11 | "components": [
12 | {"internalType": "uint8", "name": "parents", "type": "uint8"},
13 | {"internalType": "bytes[]", "name": "interior", "type": "bytes[]"}
14 | ],
15 | "internalType": "struct Xtokens.Multilocation",
16 | "name": "destination",
17 | "type": "tuple"
18 | },
19 | {"internalType": "uint64", "name": "weight", "type": "uint64"}
20 | ],
21 | "name": "transfer",
22 | "outputs": [],
23 | "stateMutability": "nonpayable",
24 | "type": "function"
25 | },
26 | {
27 | "inputs": [
28 | {
29 | "components": [
30 | {"internalType": "uint8", "name": "parents", "type": "uint8"},
31 | {"internalType": "bytes[]", "name": "interior", "type": "bytes[]"}
32 | ],
33 | "internalType": "struct Xtokens.Multilocation",
34 | "name": "asset",
35 | "type": "tuple"
36 | },
37 | {"internalType": "uint256", "name": "amount", "type": "uint256"},
38 | {
39 | "components": [
40 | {"internalType": "uint8", "name": "parents", "type": "uint8"},
41 | {"internalType": "bytes[]", "name": "interior", "type": "bytes[]"}
42 | ],
43 | "internalType": "struct Xtokens.Multilocation",
44 | "name": "destination",
45 | "type": "tuple"
46 | },
47 | {"internalType": "uint64", "name": "weight", "type": "uint64"}
48 | ],
49 | "name": "transfer_multiasset",
50 | "outputs": [],
51 | "stateMutability": "nonpayable",
52 | "type": "function"
53 | }
54 | ]
55 |
--------------------------------------------------------------------------------
/apps/subbridge/assets/phala_brand_logo.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/apps/subbridge/assets/subbridge_logo_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/subbridge/assets/subbridge_logo_text.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/subbridge/assets/token_standard_abi.json:
--------------------------------------------------------------------------------
1 | [
2 | "function allowance(address _owner, address _spender) public view returns (uint256 remaining)",
3 | "function approve(address _spender, uint256 _value) public returns (bool success)",
4 | "function balanceOf(address _owner) public view returns (uint256 balance)"
5 | ]
6 |
--------------------------------------------------------------------------------
/apps/subbridge/components/BridgeBody/Action/PolkadotAction.tsx:
--------------------------------------------------------------------------------
1 | import {bridgeErrorMessageAtom} from '@/store/bridge'
2 | import {Button} from '@mui/material'
3 | import {useAtom} from 'jotai'
4 | import type {FC} from 'react'
5 |
6 | const PolkadotAction: FC<{onConfirm: () => void}> = ({onConfirm}) => {
7 | const [bridgeErrorMessage] = useAtom(bridgeErrorMessageAtom)
8 |
9 | return (
10 |
19 | )
20 | }
21 |
22 | export default PolkadotAction
23 |
--------------------------------------------------------------------------------
/apps/subbridge/components/BridgeBody/Action/index.tsx:
--------------------------------------------------------------------------------
1 | import {bridgeInfoAtom, fromChainAtom} from '@/store/bridge'
2 | import {isWalletConnectAtom} from '@/store/common'
3 | import {Button} from '@mui/material'
4 | import {useAtom} from 'jotai'
5 | import dynamic from 'next/dynamic'
6 | import {type FC, useState} from 'react'
7 | import ConnectWalletButton from '../../ConnectWalletButton'
8 | import EvmAction from './EvmAction'
9 | import PolkadotAction from './PolkadotAction'
10 |
11 | const BridgeConfirmationDialog = dynamic(
12 | async () => await import('../../BridgeConfirmationDialog'),
13 | {ssr: false},
14 | )
15 |
16 | const Action: FC = () => {
17 | const [open, setOpen] = useState(false)
18 | const [fromChain] = useAtom(fromChainAtom)
19 | const [bridgeInfo] = useAtom(bridgeInfoAtom)
20 | const [isWalletConnected] = useAtom(isWalletConnectAtom)
21 | const onConfirm = (): void => {
22 | setOpen(true)
23 | }
24 | if (bridgeInfo.kind === 'placeholder') {
25 | // There is only one placeholder bridge
26 | return (
27 |
37 | )
38 | }
39 |
40 | return (
41 | <>
42 | {!isWalletConnected && (
43 |
44 | )}
45 | {isWalletConnected && fromChain.kind === 'evm' && (
46 |
47 | )}
48 | {isWalletConnected && fromChain.kind === 'substrate' && (
49 |
50 | )}
51 |
52 | {
55 | setOpen(false)
56 | }}
57 | />
58 | >
59 | )
60 | }
61 |
62 | export default Action
63 |
--------------------------------------------------------------------------------
/apps/subbridge/components/BridgeBody/AssetSelect.tsx:
--------------------------------------------------------------------------------
1 | import {ASSETS, type AssetId} from '@/config/asset'
2 | import {assetAtom} from '@/store/bridge'
3 | import {
4 | Box,
5 | MenuItem,
6 | TextField,
7 | type TextFieldProps,
8 | Typography,
9 | } from '@mui/material'
10 | import {useAtom} from 'jotai'
11 | import {type FC, useEffect} from 'react'
12 |
13 | const AssetSelect: FC = ({
14 | assetIds,
15 | ...props
16 | }) => {
17 | const [asset, setAsset] = useAtom(assetAtom)
18 | useEffect(() => {
19 | // Preload asset icons
20 | for (const assetId of assetIds) {
21 | const {icon} = ASSETS[assetId]
22 | const image = new Image()
23 | image.src = icon
24 | }
25 | }, [assetIds])
26 |
27 | return (
28 | {
31 | setAsset(e.target.value as AssetId)
32 | }}
33 | select
34 | variant="standard"
35 | inputProps={{sx: {pl: 1, py: 1}}}
36 | {...props}
37 | >
38 | {assetIds.map((assetId) => {
39 | const {icon, symbol} = ASSETS[assetId]
40 | return (
41 |
53 | )
54 | })}
55 |
56 | )
57 | }
58 |
59 | export default AssetSelect
60 |
--------------------------------------------------------------------------------
/apps/subbridge/components/BridgeBody/ChainSelect.tsx:
--------------------------------------------------------------------------------
1 | import {CHAINS, type ChainId} from '@/config/chain'
2 | import {
3 | Box,
4 | MenuItem,
5 | TextField,
6 | type TextFieldProps,
7 | Typography,
8 | useTheme,
9 | } from '@mui/material'
10 | import {type FC, useEffect} from 'react'
11 |
12 | const shouldShowTest =
13 | process.env.NODE_ENV === 'development' ||
14 | process.env.VERCEL_ENV === 'preview' ||
15 | process.env.VERCEL_ENV === 'development'
16 |
17 | const ChainSelect: FC<
18 | {
19 | chainIds: ChainId[]
20 | } & TextFieldProps
21 | > = ({chainIds, ...props}) => {
22 | const theme = useTheme()
23 |
24 | useEffect(() => {
25 | // Preload chain icons
26 | for (const chainId of chainIds) {
27 | const {icon, isTest} = CHAINS[chainId]
28 | if (!shouldShowTest && isTest === true) return
29 | const image = new Image()
30 | image.src = icon
31 | }
32 | }, [chainIds])
33 |
34 | return (
35 |
47 | {chainIds.map((chainId) => {
48 | const {icon, name, isTest} = CHAINS[chainId]
49 |
50 | if (!shouldShowTest && isTest === true) {
51 | return null
52 | }
53 |
54 | return (
55 |
75 | )
76 | })}
77 |
78 | )
79 | }
80 |
81 | export default ChainSelect
82 |
--------------------------------------------------------------------------------
/apps/subbridge/components/BridgeBody/DestinationAccountWarning.tsx:
--------------------------------------------------------------------------------
1 | import {Alert, type AlertProps, Typography} from '@mui/material'
2 | import type {FC} from 'react'
3 |
4 | const DestinationAccountWarning: FC = ({sx, ...props}) => {
5 | return (
6 |
10 | sx={[{border: 'none'}, ...(Array.isArray(sx) ? (sx as any) : [sx])]}
11 | {...props}
12 | >
13 |
14 | Please check the target address/network again before transferring. Do
15 | not transfer to hardware wallets.
16 |
17 |
18 | )
19 | }
20 |
21 | export default DestinationAccountWarning
22 |
--------------------------------------------------------------------------------
/apps/subbridge/components/BridgeBody/PoweredBySygma.tsx:
--------------------------------------------------------------------------------
1 | import SygmaLogo from '@/assets/sygma_logo.svg?react'
2 | import {Stack, type StackProps, Typography, useTheme} from '@mui/material'
3 | import type {FC} from 'react'
4 |
5 | const PoweredBySygma: FC = (props) => {
6 | const theme = useTheme()
7 | return (
8 |
14 |
19 | Powered by
20 |
21 |
27 |
32 |
33 |
34 | )
35 | }
36 |
37 | export default PoweredBySygma
38 |
--------------------------------------------------------------------------------
/apps/subbridge/components/ClientOnly.tsx:
--------------------------------------------------------------------------------
1 | import {type FC, type ReactNode, useEffect, useState} from 'react'
2 |
3 | export const ClientOnly: FC<{children: ReactNode}> = ({children}) => {
4 | const [hasMounted, setHasMounted] = useState(false)
5 | useEffect(() => {
6 | setHasMounted(true)
7 | }, [])
8 | if (!hasMounted) {
9 | return null
10 | }
11 | return <>{children}>
12 | }
13 |
--------------------------------------------------------------------------------
/apps/subbridge/components/ExplorerLink.tsx:
--------------------------------------------------------------------------------
1 | import {getFullUrl} from '@/lib/getFullUrl'
2 | import {Link, type LinkProps} from '@mui/material'
3 | import type {FC} from 'react'
4 |
5 | const ExplorerLink: FC<
6 | {kind: 'tx' | 'extrinsic'; url: string; hash: string} & LinkProps
7 | > = ({kind, url, hash, ...props}) => {
8 | return (
9 |
17 | )
18 | }
19 |
20 | export default ExplorerLink
21 |
--------------------------------------------------------------------------------
/apps/subbridge/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import PhalaBrandLogo from '@/assets/phala_brand_logo.svg?react'
2 | import {Box, Typography, useTheme} from '@mui/material'
3 | import type {FC} from 'react'
4 |
5 | const Footer: FC = () => {
6 | const theme = useTheme()
7 | return (
8 |
32 | )
33 | }
34 |
35 | export default Footer
36 |
--------------------------------------------------------------------------------
/apps/subbridge/components/GlobalStyles.tsx:
--------------------------------------------------------------------------------
1 | import {Global, type SerializedStyles, css} from '@emotion/react'
2 | import {type Theme, useTheme} from '@mui/material'
3 | import {type FC, useEffect, useState} from 'react'
4 |
5 | const talismanConnectStyles = (theme: Theme): SerializedStyles => css`
6 | :root {
7 | --talisman-connect-modal-z-index: ${theme.zIndex.modal};
8 | --talisman-connect-control-background: ${theme.palette.action.hover};
9 | --talisman-connect-control-foreground: ${theme.palette.text.primary};
10 | --talisman-connect-active-background: ${theme.palette.action.selected};
11 | --talisman-connect-active-foreground: ${theme.palette.text.primary};
12 | --talisman-connect-modal-background: ${theme.palette.background.default};
13 | --talisman-connect-modal-foreground: ${theme.palette.text.primary};
14 | --talisman-connect-modal-gutter: ${theme.spacing(3)};
15 | --talisman-connect-font-family: ${theme.typography.fontFamily};
16 | --talisman-connect-border-radius: ${theme.shape.borderRadius}px;
17 | --talisman-connect-modal-min-width: calc(100% - 2 * ${theme.spacing(2)});
18 | --talisman-connect-modal-max-height: 100vh;
19 | }
20 | `
21 |
22 | const commonStyles = css`
23 | input[type='number']::-webkit-inner-spin-button,
24 | input[type='number']::-webkit-outer-spin-button {
25 | -webkit-appearance: none;
26 | margin: 0;
27 | }
28 | `
29 |
30 | const background = (theme: Theme): SerializedStyles => css`
31 | body {
32 | background-image: url('/background_waves.svg');
33 | background-position: bottom center;
34 | background-size: 100% auto;
35 | background-attachment: fixed;
36 | background-repeat: no-repeat;
37 |
38 | ${theme.breakpoints.down('md')} {
39 | background-size: 200% auto;
40 | }
41 |
42 | ${theme.breakpoints.up('xl')} {
43 | background-position: bottom -100px center;
44 | }
45 | }
46 | `
47 |
48 | const snackbarStyles = css`
49 | .SnackbarContent-root.SnackbarItem-contentRoot {
50 | box-shadow: none !important;
51 | }
52 | `
53 |
54 | const GlobalStyles: FC = () => {
55 | const theme = useTheme()
56 | const [mounted, setMounted] = useState(false)
57 |
58 | useEffect(() => {
59 | setMounted(true)
60 | }, [])
61 |
62 | return (
63 |
71 | )
72 | }
73 |
74 | export default GlobalStyles
75 |
--------------------------------------------------------------------------------
/apps/subbridge/components/KhalaSunsetAlert.tsx:
--------------------------------------------------------------------------------
1 | import {khalaSunsetAlertOpenAtom} from '@/store/common'
2 | import {Alert, type AlertProps, AlertTitle, Link} from '@mui/material'
3 | import {useAtom} from 'jotai'
4 | import type {FC} from 'react'
5 |
6 | const KhalaSunsetAlert: FC = ({sx, ...props}) => {
7 | const [open, setOpen] = useAtom(khalaSunsetAlertOpenAtom)
8 | if (!open) return null
9 |
10 | return (
11 |
15 | sx={[{border: 'none'}, ...(Array.isArray(sx) ? (sx as any) : [sx])]}
16 | onClose={() => {
17 | setOpen(false)
18 | }}
19 | {...props}
20 | >
21 | Khala Sunset Notice
22 | Due to the upcoming sunset of Khala, we have disabled all Khala-related
23 | cross-chain transfer functions.{' '}
24 |
28 | Learn more
29 |
30 |
31 | )
32 | }
33 |
34 | export default KhalaSunsetAlert
35 |
--------------------------------------------------------------------------------
/apps/subbridge/components/Layout.tsx:
--------------------------------------------------------------------------------
1 | import {useEthereumProviderInitialization} from '@/hooks/useEthereumProviderInitialization'
2 | import {useValidation} from '@/hooks/useValidation'
3 | import {useConnectPolkadotWallet} from '@phala/lib'
4 | import type {FC, ReactNode} from 'react'
5 | import TopBar from './TopBar'
6 |
7 | const Layout: FC<{children: ReactNode}> = ({children}) => {
8 | useEthereumProviderInitialization()
9 | useConnectPolkadotWallet('SubBridge')
10 | useValidation()
11 |
12 | return (
13 | <>
14 |
15 | {children}
16 | >
17 | )
18 | }
19 |
20 | export default Layout
21 |
--------------------------------------------------------------------------------
/apps/subbridge/components/PolkadotWalletDialog.tsx:
--------------------------------------------------------------------------------
1 | import {polkadotWalletModalOpenAtom} from '@/store/polkadotWalletModal'
2 | import {
3 | polkadotAccountAtom,
4 | polkadotAccountsAtom,
5 | walletAtom,
6 | } from '@phala/store'
7 | import {WalletSelect} from '@talismn/connect-components'
8 | import {useAtom} from 'jotai'
9 | import type {FC} from 'react'
10 |
11 | const PolkadotWalletDialog: FC = () => {
12 | const [open, setOpen] = useAtom(polkadotWalletModalOpenAtom)
13 | const [, setPolkadotAccount] = useAtom(polkadotAccountAtom)
14 | const [, setPolkadotAccounts] = useAtom(polkadotAccountsAtom)
15 | const [, setWallet] = useAtom(walletAtom)
16 | return (
17 | {
22 | setOpen(false)
23 | }}
24 | onWalletSelected={(wallet) => {
25 | if (wallet.installed === true) {
26 | setWallet(wallet)
27 | }
28 | }}
29 | onUpdatedAccounts={(accounts) => {
30 | if (accounts != null) {
31 | setPolkadotAccounts(accounts)
32 | }
33 | }}
34 | onAccountSelected={(account) => {
35 | setPolkadotAccount(account.address)
36 | }}
37 | />
38 | )
39 | }
40 |
41 | export default PolkadotWalletDialog
42 |
--------------------------------------------------------------------------------
/apps/subbridge/components/TopBar/Account/AccountTemplate.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | type ButtonProps as MuiButtonProps,
4 | Paper,
5 | Skeleton,
6 | Typography,
7 | } from '@mui/material'
8 | import type {FC, ReactNode} from 'react'
9 |
10 | const AccountTemplate: FC<{
11 | balance?: ReactNode
12 | account: ReactNode
13 | ButtonProps?: MuiButtonProps
14 | }> = ({balance, account, ButtonProps}) => {
15 | return (
16 |
17 | {balance == null || balance === false ? (
18 |
19 | ) : (
20 |
25 | {balance}
26 |
27 | )}
28 |
29 |
43 |
44 | )
45 | }
46 |
47 | export default AccountTemplate
48 |
--------------------------------------------------------------------------------
/apps/subbridge/components/TopBar/Account/EvmAccount.tsx:
--------------------------------------------------------------------------------
1 | import {useEthersWeb3Provider} from '@/hooks/useEthersProvider'
2 | import {useSwitchNetwork} from '@/hooks/useSwitchNetwork'
3 | import {ethersBalanceFetcher} from '@/lib/ethersFetcher'
4 | import {fromChainAtom} from '@/store/bridge'
5 | import {evmAccountAtom, isNetworkWrongAtom} from '@/store/ethers'
6 | import {Button} from '@mui/material'
7 | import {toCurrency, trimAddress} from '@phala/lib'
8 | import {useAtom} from 'jotai'
9 | import {useSnackbar} from 'notistack'
10 | import type {FC} from 'react'
11 | import useSWR from 'swr'
12 | import AccountTemplate from './AccountTemplate'
13 |
14 | const EvmAccount: FC = () => {
15 | const {enqueueSnackbar} = useSnackbar()
16 | const [fromChain] = useAtom(fromChainAtom)
17 | const [evmAccount] = useAtom(evmAccountAtom)
18 | const ethersWeb3Provider = useEthersWeb3Provider()
19 | const [isNetworkWrong] = useAtom(isNetworkWrongAtom)
20 | const switchNetwork = useSwitchNetwork()
21 | const {data} = useSWR(
22 | evmAccount != null &&
23 | ethersWeb3Provider != null && [ethersWeb3Provider, evmAccount],
24 | ethersBalanceFetcher,
25 | {refreshInterval: 12000},
26 | )
27 |
28 | if (evmAccount == null) return null
29 |
30 | return (
31 | {
41 | void switchNetwork()
42 | }}
43 | >
44 | Wrong Network
45 |
46 | ) : (
47 | data != null && `${toCurrency(data)} ${fromChain.currencySymbol}`
48 | ))
49 | }
50 | ButtonProps={{
51 | onClick: () => {
52 | enqueueSnackbar('You can switch accounts in the wallet.')
53 | },
54 | }}
55 | />
56 | )
57 | }
58 |
59 | export default EvmAccount
60 |
--------------------------------------------------------------------------------
/apps/subbridge/components/TopBar/Account/PolkadotAccount.tsx:
--------------------------------------------------------------------------------
1 | import {useCurrentPolkadotApi} from '@/hooks/usePolkadotApi'
2 | import {polkadotNativeBalanceFetcher} from '@/lib/polkadotFetcher'
3 | import {polkadotWalletModalOpenAtom} from '@/store/polkadotWalletModal'
4 | import {toCurrency} from '@phala/lib'
5 | import {polkadotAccountAtom} from '@phala/store'
6 | import {useAtom} from 'jotai'
7 | import type {FC} from 'react'
8 | import useSWR from 'swr'
9 | import AccountTemplate from './AccountTemplate'
10 |
11 | const PolkadotAccount: FC = () => {
12 | const [, setPolkadotWalletModalOpen] = useAtom(polkadotWalletModalOpenAtom)
13 | const [polkadotAccount] = useAtom(polkadotAccountAtom)
14 | const polkadotApi = useCurrentPolkadotApi()
15 | const {data} = useSWR(
16 | polkadotApi != null &&
17 | polkadotAccount != null && [
18 | polkadotApi.runtimeChain.toString(),
19 | polkadotAccount.address,
20 | ],
21 | polkadotNativeBalanceFetcher(polkadotApi),
22 | {
23 | refreshInterval: 12000,
24 | },
25 | )
26 |
27 | const tokenSymbol = polkadotApi?.registry.chainTokens[0]
28 |
29 | const handleClick = (): void => {
30 | setPolkadotWalletModalOpen(true)
31 | }
32 |
33 | if (polkadotAccount == null) return null
34 |
35 | return (
36 |
45 | )
46 | }
47 |
48 | export default PolkadotAccount
49 |
--------------------------------------------------------------------------------
/apps/subbridge/components/TopBar/Account/index.tsx:
--------------------------------------------------------------------------------
1 | import ConnectWalletButton from '@/components/ConnectWalletButton'
2 | import {fromChainAtom} from '@/store/bridge'
3 | import {isWalletConnectAtom} from '@/store/common'
4 | import {useAtom} from 'jotai'
5 | import type {FC} from 'react'
6 | import EvmAccount from './EvmAccount'
7 | import PolkadotAccount from './PolkadotAccount'
8 |
9 | const Account: FC = () => {
10 | const [fromChain] = useAtom(fromChainAtom)
11 | const [isWalletConnected] = useAtom(isWalletConnectAtom)
12 | if (!isWalletConnected) {
13 | return
14 | }
15 |
16 | if (fromChain.kind === 'evm') {
17 | return
18 | }
19 |
20 | if (fromChain.kind === 'substrate') {
21 | return
22 | }
23 |
24 | return null
25 | }
26 |
27 | export default Account
28 |
--------------------------------------------------------------------------------
/apps/subbridge/components/TopBar/SubbridgeLogo.tsx:
--------------------------------------------------------------------------------
1 | import SubbridgeLogoIcon from '@/assets/subbridge_logo_icon.svg?react'
2 | import SubbridgeLogoText from '@/assets/subbridge_logo_text.svg?react'
3 | import {Box} from '@mui/material'
4 | import {useTheme} from '@mui/system'
5 | import type {FC} from 'react'
6 |
7 | const SubbridgeLogo: FC<{className?: string}> = ({className}) => {
8 | const theme = useTheme()
9 | return (
10 |
17 |
28 |
40 |
41 | )
42 | }
43 |
44 | export default SubbridgeLogo
45 |
--------------------------------------------------------------------------------
/apps/subbridge/components/TopBar/index.tsx:
--------------------------------------------------------------------------------
1 | import {AppBar, Stack, Toolbar, alpha, useTheme} from '@mui/material'
2 | import type {FC} from 'react'
3 | import Account from './Account'
4 | import MoreButton from './MoreButton'
5 | import SubbridgeLogo from './SubbridgeLogo'
6 |
7 | const TopBar: FC = () => {
8 | const theme = useTheme()
9 |
10 | return (
11 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default TopBar
35 |
--------------------------------------------------------------------------------
/apps/subbridge/config/error.ts:
--------------------------------------------------------------------------------
1 | export type BridgeError =
2 | | 'InvalidAmount'
3 | | 'InvalidAccount'
4 | | 'InsufficientBalance'
5 | | 'InsufficientReserve'
6 | | 'AmountTooSmall'
7 | | 'Disabled'
8 | | 'MinBalanceLimit'
9 |
10 | export const BRIDGE_ERROR_MESSAGES: Record = {
11 | InvalidAmount: 'Enter an amount',
12 | InvalidAccount: 'Enter destination account',
13 | InsufficientBalance: 'Insufficient balance',
14 | InsufficientReserve: 'Insufficient reserve',
15 | AmountTooSmall: 'Amount too small',
16 | Disabled: 'Under maintenance',
17 | MinBalanceLimit: 'Minimum balance limit not met',
18 | }
19 |
--------------------------------------------------------------------------------
/apps/subbridge/hooks/useEthereumProviderInitialization.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ethersWeb3ProviderAtom,
3 | evmAccountAtom,
4 | evmChainIdAtom,
5 | } from '@/store/ethers'
6 | import detectEthereumProvider from '@metamask/detect-provider'
7 | import {ethers} from 'ethers'
8 | import {useAtom} from 'jotai'
9 | import {useEffect, useRef} from 'react'
10 |
11 | export const useEthereumProviderInitialization = (): void => {
12 | const [, setEthersWeb3Provider] = useAtom(ethersWeb3ProviderAtom)
13 | const [, setEvmAccount] = useAtom(evmAccountAtom)
14 | const [, setEvmChainId] = useAtom(evmChainIdAtom)
15 | const runRef = useRef(false)
16 |
17 | useEffect(() => {
18 | if (runRef.current) return
19 | runRef.current = true
20 | const init = async (): Promise => {
21 | const ethereum = await detectEthereumProvider({silent: true})
22 | if (ethereum == null) return
23 | const provider = new ethers.providers.Web3Provider(ethereum)
24 | setEthersWeb3Provider(provider)
25 | const updateAccounts = (accounts: unknown): void => {
26 | const account = (accounts as string[])[0]
27 | setEvmAccount(account ?? null)
28 | }
29 | const updateChainId = (chainId: unknown): void => {
30 | setEvmChainId(Number.parseInt(chainId as string, 16))
31 | }
32 | await provider.listAccounts().then(updateAccounts)
33 | ethereum.on('accountsChanged', updateAccounts)
34 | ethereum.on('chainChanged', updateChainId)
35 | await provider.provider
36 | .request?.({method: 'eth_chainId'})
37 | .then(updateChainId)
38 | }
39 |
40 | void init()
41 | }, [setEthersWeb3Provider, setEvmAccount, setEvmChainId])
42 | }
43 |
--------------------------------------------------------------------------------
/apps/subbridge/hooks/useEthersContract.ts:
--------------------------------------------------------------------------------
1 | import moonriverXTokensAbi from '@/assets/moonriver_xtokens_abi.json'
2 | import tokenStandardAbi from '@/assets/token_standard_abi.json'
3 | import {ASSETS, type AssetId} from '@/config/asset'
4 | import type {EvmChainId} from '@/config/chain'
5 | import {assetAtom, fromChainAtom} from '@/store/bridge'
6 | import {ethers} from 'ethers'
7 | import {useAtomValue} from 'jotai'
8 | import useSWRImmutable from 'swr/immutable'
9 | import {useEthersWeb3Provider} from './useEthersProvider'
10 |
11 | type AbiKind = 'moonriverXTokens' | 'tokenStandard'
12 | const abi: Record = {
13 | moonriverXTokens: moonriverXTokensAbi,
14 | tokenStandard: tokenStandardAbi,
15 | }
16 |
17 | const fetcher = async ([provider, address, abiKind]: [
18 | ethers.providers.Web3Provider | ethers.providers.JsonRpcProvider,
19 | string,
20 | AbiKind,
21 | ]): Promise => {
22 | return new ethers.Contract(
23 | address,
24 | abi[abiKind],
25 | provider instanceof ethers.providers.Web3Provider
26 | ? provider.getSigner()
27 | : provider, // JsonRpcProvider is readonly
28 | )
29 | }
30 |
31 | export function useEthersAssetContract(
32 | provider:
33 | | ethers.providers.Web3Provider
34 | | ethers.providers.JsonRpcProvider
35 | | undefined,
36 | chainId: EvmChainId,
37 | assetId: AssetId,
38 | ): ethers.Contract | undefined {
39 | const address = ASSETS[assetId].erc20TokenContractAddress?.[chainId]
40 |
41 | const {data} = useSWRImmutable(
42 | provider != null && address != null
43 | ? [provider, address, 'tokenStandard' as AbiKind]
44 | : null,
45 | fetcher,
46 | )
47 |
48 | return data
49 | }
50 |
51 | export const useCurrentEthersAssetContract = ():
52 | | ethers.Contract
53 | | undefined => {
54 | const fromChain = useAtomValue(fromChainAtom)
55 | const asset = useAtomValue(assetAtom)
56 | const address =
57 | fromChain.kind === 'evm'
58 | ? asset.erc20TokenContractAddress?.[fromChain.id]
59 | : undefined
60 | const provider = useEthersWeb3Provider()
61 | const {data} = useSWRImmutable(
62 | provider != null && address != null
63 | ? [provider, address, 'tokenStandard' as AbiKind]
64 | : null,
65 | fetcher,
66 | )
67 |
68 | return data
69 | }
70 |
71 | export const useEthersXTokensContract = (): ethers.Contract | undefined => {
72 | const fromChain = useAtomValue(fromChainAtom)
73 | const provider = useEthersWeb3Provider()
74 | const address =
75 | fromChain.kind === 'evm' ? fromChain.xTokensContractAddress : undefined
76 |
77 | const {data} = useSWRImmutable(
78 | provider != null && address != null
79 | ? [provider, address, 'moonriverXTokens' as AbiKind]
80 | : null,
81 | fetcher,
82 | )
83 |
84 | return data
85 | }
86 |
--------------------------------------------------------------------------------
/apps/subbridge/hooks/useEthersProvider.ts:
--------------------------------------------------------------------------------
1 | import {fromChainAtom} from '@/store/bridge'
2 | import {ethersWeb3ProviderAtom} from '@/store/ethers'
3 | import type {ethers} from 'ethers'
4 | import {useAtomValue} from 'jotai'
5 |
6 | export const useEthersWeb3Provider =
7 | (): ethers.providers.Web3Provider | null => {
8 | const fromChain = useAtomValue(fromChainAtom)
9 | const ethersWeb3Provider = useAtomValue(ethersWeb3ProviderAtom)
10 |
11 | if (fromChain.kind === 'evm') {
12 | return ethersWeb3Provider
13 | }
14 |
15 | return null
16 | }
17 |
--------------------------------------------------------------------------------
/apps/subbridge/hooks/usePolkadotApi.ts:
--------------------------------------------------------------------------------
1 | import {CHAINS, type SubstrateChainId} from '@/config/chain'
2 | import {fromChainAtom} from '@/store/bridge'
3 | import {createPolkadotApi} from '@phala/lib'
4 | import type {ApiPromise} from '@polkadot/api'
5 | import {useAtomValue} from 'jotai'
6 | import useSWRImmutable from 'swr/immutable'
7 |
8 | const create = async ([chainId]: [SubstrateChainId]): Promise => {
9 | const endpoint = CHAINS[chainId].endpoint
10 | const api = await createPolkadotApi(
11 | typeof endpoint === 'string' ? endpoint : endpoint[0],
12 | )
13 | return api
14 | }
15 |
16 | export const usePolkadotApi = (
17 | chainId: SubstrateChainId | null,
18 | ): ApiPromise | undefined => {
19 | const {data: polkadotApi} = useSWRImmutable(
20 | chainId != null && [chainId],
21 | create,
22 | )
23 |
24 | return polkadotApi
25 | }
26 |
27 | export const useCurrentPolkadotApi = (): ApiPromise | undefined => {
28 | const fromChain = useAtomValue(fromChainAtom)
29 | const polkadotApi = usePolkadotApi(
30 | fromChain.kind === 'substrate' ? fromChain.id : null,
31 | )
32 |
33 | return polkadotApi
34 | }
35 |
--------------------------------------------------------------------------------
/apps/subbridge/hooks/useSwitchNetwork.ts:
--------------------------------------------------------------------------------
1 | import evmChainsData from '@/assets/evm_chains_data.json'
2 | import {fromChainAtom} from '@/store/bridge'
3 | import {useAtom} from 'jotai'
4 | import {useCallback} from 'react'
5 | import {useEthersWeb3Provider} from './useEthersProvider'
6 |
7 | export const useSwitchNetwork = (): (() => Promise) => {
8 | const [fromChain] = useAtom(fromChainAtom)
9 | const ethersWeb3Provider = useEthersWeb3Provider()
10 |
11 | const switchNetwork = useCallback(async () => {
12 | if (ethersWeb3Provider == null || fromChain.kind !== 'evm') return
13 | try {
14 | await ethersWeb3Provider.provider.request?.({
15 | method: 'wallet_switchEthereumChain',
16 | params: [{chainId: `0x${fromChain.evmChainId.toString(16)}`}],
17 | })
18 | } catch (switchError) {
19 | if ((switchError as {code: number}).code === 4902) {
20 | await ethersWeb3Provider.provider.request?.({
21 | method: 'wallet_addEthereumChain',
22 | params: [
23 | (evmChainsData as Readonly>)[
24 | fromChain.evmChainId
25 | ],
26 | ],
27 | })
28 | }
29 | }
30 | }, [ethersWeb3Provider, fromChain])
31 |
32 | return switchNetwork
33 | }
34 |
--------------------------------------------------------------------------------
/apps/subbridge/lib/createEmotionCache.ts:
--------------------------------------------------------------------------------
1 | import createCache from '@emotion/cache'
2 | import type {EmotionCache} from '@emotion/react'
3 |
4 | export const createEmotionCache = (): EmotionCache => {
5 | return createCache({key: 'css', prepend: true})
6 | }
7 |
--------------------------------------------------------------------------------
/apps/subbridge/lib/createPhalaMultilocation.ts:
--------------------------------------------------------------------------------
1 | import getGeneralKey, {type GeneralKey, type Hex} from './getGeneralKey'
2 |
3 | type Multilocation = [
4 | {GeneralKey: GeneralKey},
5 | {GeneralIndex: number},
6 | {GeneralKey: GeneralKey},
7 | ]
8 |
9 | export const createPhalaMultilocation = (
10 | kind: 'cb' | 'sygma',
11 | generalIndex: number,
12 | destinationAccount: Hex,
13 | ): Multilocation => {
14 | return [
15 | {
16 | GeneralKey:
17 | kind === 'sygma'
18 | ? getGeneralKey('0x7379676d61') // string "sygma"
19 | : getGeneralKey('0x6362'), // string "cb"
20 | },
21 | {GeneralIndex: generalIndex},
22 | {GeneralKey: getGeneralKey(destinationAccount)},
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/apps/subbridge/lib/ethersFetcher.ts:
--------------------------------------------------------------------------------
1 | import {hexStripPrefix, u8aToHex} from '@polkadot/util'
2 | import {decodeAddress} from '@polkadot/util-crypto'
3 | import Decimal from 'decimal.js'
4 | import {type BigNumber, ethers} from 'ethers'
5 |
6 | export const ethersBalanceFetcher = async ([provider, address]: [
7 | ethers.providers.Web3Provider,
8 | `0x${string}`,
9 | ]): Promise => {
10 | const balance = await provider.getBalance(address)
11 | return new Decimal(ethers.utils.formatEther(balance))
12 | }
13 |
14 | export const ethersContractBalanceFetcher = async ([
15 | contract,
16 | account,
17 | decimals,
18 | ]: [ethers.Contract, `0x${string}`, number]): Promise => {
19 | const balance = (await contract.balanceOf(account)) as BigNumber
20 |
21 | return new Decimal(ethers.utils.formatUnits(balance, decimals))
22 | }
23 |
24 | export const ethersContractAllowanceFetcher = async ([
25 | contract,
26 | owner,
27 | spender,
28 | ]: [ethers.Contract, `0x${string}`, `0x${string}`]): Promise => {
29 | const balance = (await contract.allowance(owner, spender)) as BigNumber
30 |
31 | return new Decimal(balance.toHexString())
32 | }
33 |
34 | export const ethersGasPriceFetcher = async (
35 | provider: ethers.providers.Web3Provider,
36 | ): Promise => {
37 | return new Decimal(ethers.utils.formatEther(await provider.getGasPrice()))
38 | }
39 |
40 | const ALICE = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'
41 |
42 | export const evmXTokensEstimatedGasFetcher = async ([
43 | contract,
44 | xc20Address,
45 | paraId,
46 | decimals,
47 | ]: [ethers.Contract, `0x${string}`, number, number]): Promise => {
48 | const estimateGas = await contract.estimateGas.transfer(
49 | xc20Address,
50 | '0',
51 | {
52 | parents: 1,
53 | interior: [
54 | ethers.utils.hexZeroPad(ethers.utils.hexlify(paraId), 5),
55 | `0x01${hexStripPrefix(u8aToHex(decodeAddress(ALICE)))}00`, // AccountKey32 Selector + Address in hex + Network = Any
56 | ],
57 | },
58 | Decimal.pow(10, decimals - 3)
59 | .times(6)
60 | .toString(),
61 | )
62 |
63 | return new Decimal(estimateGas.toString())
64 | }
65 |
--------------------------------------------------------------------------------
/apps/subbridge/lib/evmSygma.ts:
--------------------------------------------------------------------------------
1 | import type {Asset} from '@/config/asset'
2 | import {CHAINS, type Chain} from '@/config/chain'
3 | import type {
4 | EVMAssetTransfer,
5 | EvmFee,
6 | Fungible,
7 | Transfer,
8 | } from '@buildwithsygma/sygma-sdk-core'
9 | import type {PopulatedTransaction, ethers} from 'ethers'
10 |
11 | export const getEvmSygmaTransfer = async (
12 | provider: ethers.providers.Web3Provider,
13 | fromChain: Chain,
14 | toChain: Chain,
15 | sender: string,
16 | asset: Asset,
17 | destinationAccount: string,
18 | amount: string,
19 | ): Promise<{
20 | assetTransfer: EVMAssetTransfer
21 | transfer: Transfer
22 | fee: EvmFee
23 | tx: PopulatedTransaction
24 | }> => {
25 | const {EVMAssetTransfer, Environment} = await import(
26 | '@buildwithsygma/sygma-sdk-core'
27 | )
28 | const assetTransfer = new EVMAssetTransfer()
29 | await assetTransfer.init(
30 | provider,
31 | fromChain.isTest === true ? Environment.TESTNET : Environment.MAINNET,
32 | )
33 |
34 | let destinationChainId: number | undefined
35 | let parachainId: number | undefined
36 |
37 | if (toChain.id === 'phala' || toChain.id === 'rhala') {
38 | destinationChainId = toChain.sygmaChainId
39 | } else {
40 | parachainId = toChain.paraId
41 | if (toChain.id === 'astar') {
42 | destinationChainId = CHAINS.phala.sygmaChainId
43 | }
44 | }
45 |
46 | if (destinationChainId == null || asset.sygmaResourceId == null) {
47 | throw new Error('Chain or asset not supported')
48 | }
49 |
50 | const transfer = await assetTransfer.createFungibleTransfer(
51 | sender,
52 | destinationChainId,
53 | destinationAccount,
54 | asset.sygmaResourceId,
55 | amount,
56 | parachainId,
57 | )
58 |
59 | const fee = await assetTransfer.getFee(transfer)
60 | const tx = await assetTransfer.buildTransferTransaction(transfer, fee)
61 |
62 | return {assetTransfer, transfer, fee, tx}
63 | }
64 |
--------------------------------------------------------------------------------
/apps/subbridge/lib/getFullUrl.ts:
--------------------------------------------------------------------------------
1 | export const getFullUrl = (url: string, path: string): string => {
2 | const urlObject = new URL(url)
3 | urlObject.pathname = path
4 | return urlObject.toString()
5 | }
6 |
--------------------------------------------------------------------------------
/apps/subbridge/lib/getGeneralKey.ts:
--------------------------------------------------------------------------------
1 | export type Hex = `0x${string}`
2 | export interface GeneralKey {
3 | length: number
4 | data: Hex
5 | }
6 |
7 | const getGeneralKey = (hex: Hex): {length: number; data: Hex} => {
8 | return {
9 | length: (hex.length - 2) / 2,
10 | data: `${hex}${'0'.repeat(66 - hex.length)}`,
11 | }
12 | }
13 |
14 | export default getGeneralKey
15 |
--------------------------------------------------------------------------------
/apps/subbridge/lib/transferByEvmSygma.ts:
--------------------------------------------------------------------------------
1 | import {ASSETS, type AssetId} from '@/config/asset'
2 | import {CHAINS, type ChainId} from '@/config/chain'
3 | import type {TransactionResponse} from '@ethersproject/abstract-provider'
4 | import type {ethers} from 'ethers'
5 | import {getEvmSygmaTransfer} from './evmSygma'
6 |
7 | export const transferEvmSygma = async ({
8 | provider,
9 | sender,
10 | amount,
11 | fromChainId,
12 | toChainId,
13 | destinationAccount,
14 | assetId,
15 | onReady,
16 | }: {
17 | provider: ethers.providers.Web3Provider
18 | sender: string
19 | amount: string
20 | fromChainId: ChainId
21 | toChainId: ChainId
22 | destinationAccount: string
23 | assetId: AssetId
24 | onReady: () => void
25 | }): Promise => {
26 | const fromChain = CHAINS[fromChainId]
27 | const toChain = CHAINS[toChainId]
28 | const asset = ASSETS[assetId]
29 | const {tx} = await getEvmSygmaTransfer(
30 | provider,
31 | fromChain,
32 | toChain,
33 | sender,
34 | asset,
35 | destinationAccount,
36 | amount,
37 | )
38 | const res = await provider.getSigner().sendTransaction(tx)
39 | onReady()
40 | return res
41 | }
42 |
--------------------------------------------------------------------------------
/apps/subbridge/lib/transferByEvmXTokens.ts:
--------------------------------------------------------------------------------
1 | import {ASSETS, type AssetId} from '@/config/asset'
2 | import {CHAINS, type ChainId} from '@/config/chain'
3 | import {hexStripPrefix, u8aToHex} from '@polkadot/util'
4 | import {decodeAddress} from '@polkadot/util-crypto'
5 | import Decimal from 'decimal.js'
6 | import type {ContractTransaction} from 'ethers'
7 | import {ethers} from 'ethers'
8 |
9 | export const transferByEvmXTokens = async ({
10 | contract,
11 | assetId,
12 | destinationAccount,
13 | amount,
14 | fromChainId,
15 | toChainId,
16 | decimals,
17 | }: {
18 | contract: ethers.Contract
19 | assetId: AssetId
20 | destinationAccount: string
21 | amount: string
22 | fromChainId: ChainId
23 | toChainId: ChainId
24 | decimals: number
25 | }): Promise => {
26 | const asset = ASSETS[assetId]
27 | const toChain = CHAINS[toChainId]
28 |
29 | if (asset.xc20Address?.[fromChainId] == null || toChain.paraId == null) {
30 | throw new Error('Transfer missing required parameters')
31 | }
32 |
33 | return contract.transfer(
34 | asset.xc20Address[fromChainId],
35 | amount,
36 | {
37 | parents: 1,
38 | interior: [
39 | ethers.utils.hexZeroPad(ethers.utils.hexlify(toChain.paraId), 5),
40 | `0x01${hexStripPrefix(u8aToHex(decodeAddress(destinationAccount)))}00`, // AccountKey32 Selector + Address in hex + Network = Any
41 | ],
42 | },
43 | Decimal.pow(10, decimals - 3)
44 | .times(6)
45 | .toString(),
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/apps/subbridge/lib/transferByPhalaXTransfer.ts:
--------------------------------------------------------------------------------
1 | import {ASSETS, type AssetId, nativeLocation} from '@/config/asset'
2 | import type {BridgeKind} from '@/config/bridge'
3 | import {CHAINS, type ChainId, type SubstrateChain} from '@/config/chain'
4 | import type {ApiPromise} from '@polkadot/api'
5 | import type {SubmittableExtrinsic} from '@polkadot/api/types'
6 | import {u8aToHex} from '@polkadot/util'
7 | import {decodeAddress} from '@polkadot/util-crypto'
8 | import getGeneralKey, {type Hex} from './getGeneralKey'
9 |
10 | export const transferByPhalaXTransfer = ({
11 | api,
12 | amount,
13 | fromChainId,
14 | toChainId,
15 | destinationAccount,
16 | assetId,
17 | kind,
18 | }: {
19 | api: ApiPromise
20 | amount: string
21 | fromChainId: ChainId
22 | toChainId: ChainId
23 | destinationAccount: string
24 | assetId: AssetId
25 | kind: BridgeKind
26 | }): SubmittableExtrinsic<'promise'> => {
27 | const asset = ASSETS[assetId]
28 | const fromChain = CHAINS[fromChainId] as SubstrateChain
29 | const toChain = CHAINS[toChainId]
30 | const isSygma = kind === 'phalaSygma'
31 | const generalIndex = toChain.kind === 'evm' ? toChain.generalIndex : null
32 |
33 | const location =
34 | fromChain.nativeAsset === assetId
35 | ? nativeLocation
36 | : asset.location?.[fromChain.relayChain]
37 |
38 | if (location == null) {
39 | throw new Error(`Unsupported asset: ${assetId}`)
40 | }
41 |
42 | if (isSygma && typeof generalIndex !== 'number') {
43 | throw new Error('Transfer missing required parameters')
44 | }
45 |
46 | return api.tx.xTransfer.transfer(
47 | {
48 | id: {Concrete: location},
49 | fun: {Fungible: amount},
50 | },
51 | {
52 | parents: isSygma ? 0 : 1,
53 | interior: isSygma
54 | ? {
55 | X3: [
56 | {
57 | GeneralKey: isSygma
58 | ? getGeneralKey('0x7379676d61') // string "sygma"
59 | : getGeneralKey('0x6362'), // string "cb"
60 | },
61 | {GeneralIndex: generalIndex},
62 | {GeneralKey: getGeneralKey(destinationAccount as Hex)},
63 | ],
64 | }
65 | : {
66 | X2: [
67 | {Parachain: toChain.paraId},
68 | toChain.kind === 'evm'
69 | ? {AccountKey20: {key: destinationAccount}}
70 | : {
71 | AccountId32: {
72 | id: u8aToHex(decodeAddress(destinationAccount)),
73 | },
74 | },
75 | ],
76 | },
77 | },
78 | isSygma
79 | ? null // No need to specify a certain weight if transfer will not through XCM
80 | : {refTime: '6000000000', proofSize: '1000000'},
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/apps/subbridge/lib/transferByPolkadotXTokens.ts:
--------------------------------------------------------------------------------
1 | import {ASSETS, type AssetId, nativeLocation} from '@/config/asset'
2 | import {CHAINS, type ChainId, type SubstrateChain} from '@/config/chain'
3 | import type {ApiPromise} from '@polkadot/api'
4 | import type {SubmittableExtrinsic} from '@polkadot/api/types'
5 | import type {ISubmittableResult} from '@polkadot/types/types'
6 | import {u8aToHex} from '@polkadot/util'
7 | import {decodeAddress} from '@polkadot/util-crypto'
8 | import {createPhalaMultilocation} from './createPhalaMultilocation'
9 | import type {Hex} from './getGeneralKey'
10 |
11 | export const transferByPolkadotXTokens = ({
12 | polkadotApi,
13 | assetId,
14 | amount,
15 | fromChainId,
16 | toChainId,
17 | destinationAccount,
18 | proxy,
19 | }: {
20 | polkadotApi: ApiPromise
21 | assetId: AssetId
22 | fromChainId: ChainId
23 | toChainId: ChainId
24 | amount: string
25 | destinationAccount: string
26 | proxy?: ChainId
27 | }): SubmittableExtrinsic<'promise', ISubmittableResult> => {
28 | const asset = ASSETS[assetId]
29 | const fromChain = CHAINS[fromChainId] as SubstrateChain
30 | const toChain = CHAINS[toChainId]
31 | const isNativeAsset = fromChain.nativeAsset === assetId
32 | const location = isNativeAsset
33 | ? nativeLocation
34 | : asset.location?.[fromChain.relayChain]
35 | const generalIndex = toChain.kind === 'evm' ? toChain.generalIndex : null
36 | const xcmVersion = 'V3'
37 | if (location == null || (proxy != null && typeof generalIndex !== 'number')) {
38 | throw new Error('Transfer missing required parameters')
39 | }
40 |
41 | return polkadotApi.tx.xTokens.transferMultiasset(
42 | {
43 | [xcmVersion]: {
44 | id: {Concrete: location},
45 | fun: {Fungible: amount},
46 | },
47 | },
48 | {
49 | [xcmVersion]: {
50 | parents: 1,
51 | interior:
52 | proxy != null
53 | ? {
54 | X4: [
55 | {Parachain: CHAINS[proxy].paraId},
56 | ...createPhalaMultilocation(
57 | 'cb',
58 | generalIndex as unknown as number,
59 | destinationAccount as Hex,
60 | ),
61 | ],
62 | }
63 | : {
64 | X2: [
65 | {Parachain: toChain.paraId},
66 | {
67 | AccountId32: {
68 | id: u8aToHex(decodeAddress(destinationAccount)),
69 | },
70 | },
71 | ],
72 | },
73 | },
74 | },
75 | 'Unlimited',
76 | )
77 | }
78 |
--------------------------------------------------------------------------------
/apps/subbridge/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/apps/subbridge/next.config.js:
--------------------------------------------------------------------------------
1 | const withBundleAnalyzer = require('@next/bundle-analyzer')({
2 | enabled: process.env.ANALYZE === 'true',
3 | })
4 |
5 | /** @type {import('next').NextConfig} */
6 | const nextConfig = {
7 | output: 'export',
8 | transpilePackages: ['jotai-devtools'],
9 | eslint: {
10 | ignoreDuringBuilds: true,
11 | dirs: ['pages', 'components', 'lib', 'hooks', 'store', 'types'],
12 | },
13 | modularizeImports: {
14 | '@mui/icons-material': {
15 | transform: '@mui/icons-material/{{member}}',
16 | },
17 | },
18 | reactStrictMode: true,
19 | webpack: (config) => {
20 | config.module.rules.push({
21 | test: /\.svg$/i,
22 | resourceQuery: /react/,
23 | issuer: /\.[jt]sx?$/,
24 | use: [
25 | {
26 | loader: '@svgr/webpack',
27 | options: {
28 | svgoConfig: {
29 | plugins: [
30 | {
31 | name: 'preset-default',
32 | params: {overrides: {removeViewBox: false}},
33 | },
34 | ],
35 | },
36 | },
37 | },
38 | ],
39 | })
40 | return config
41 | },
42 | experimental: {
43 | swcPlugins: [
44 | // ['@swc-jotai/debug-label', {}],
45 | // ['@swc-jotai/react-refresh', {}],
46 | ],
47 | },
48 | }
49 |
50 | module.exports = withBundleAnalyzer(nextConfig)
51 |
--------------------------------------------------------------------------------
/apps/subbridge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@phala/subbridge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "next build",
7 | "dev": "next dev -p 3002",
8 | "lint": "next lint",
9 | "lint:fix": "next lint --fix",
10 | "start": "next start"
11 | },
12 | "dependencies": {
13 | "@buildwithsygma/sygma-sdk-core": "2.11.2",
14 | "@emotion/cache": "^11.14.0",
15 | "@emotion/react": "^11.14.0",
16 | "@emotion/server": "^11.11.0",
17 | "@emotion/styled": "^11.14.0",
18 | "@metamask/detect-provider": "^2.0.0",
19 | "@mui/icons-material": "^6.4.11",
20 | "@mui/material": "^6.4.11",
21 | "@mui/material-nextjs": "^6.4.3",
22 | "@phala/lib": "workspace:*",
23 | "@phala/store": "workspace:*",
24 | "@phala/ui": "workspace:*",
25 | "@polkadot/api": "^15.9.2",
26 | "@polkadot/util": "^13.4.4",
27 | "@polkadot/util-crypto": "^13.4.4",
28 | "@talismn/connect-components": "1.1.9",
29 | "@talismn/connect-ui": "^1.1.4",
30 | "@talismn/connect-wallets": "^1.2.8",
31 | "date-fns": "^4.1.0",
32 | "decimal.js": "^10.5.0",
33 | "ethers": "^5.8.0",
34 | "jotai": "^2.12.3",
35 | "jotai-devtools": "^0.10.1",
36 | "next": "^15.3.1",
37 | "notistack": "^3.0.2",
38 | "react": "^18.3.1",
39 | "react-dom": "^18.3.1",
40 | "swr": "^2.3.3"
41 | },
42 | "devDependencies": {
43 | "@next/bundle-analyzer": "^15.3.1",
44 | "@svgr/webpack": "^8.1.0",
45 | "@swc-jotai/debug-label": "^0.2.0",
46 | "@swc-jotai/react-refresh": "^0.3.0",
47 | "@types/react": "^18.3.20",
48 | "@types/react-dom": "^18.3.6"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/apps/subbridge/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import GlobalStyles from '@/components/GlobalStyles'
2 | import Layout from '@/components/Layout'
3 | import MuiThemeProvider from '@/components/MuiThemeProvider'
4 | import {CssBaseline} from '@mui/material'
5 | import {AppCacheProvider} from '@mui/material-nextjs/v15-pagesRouter'
6 | import Decimal from 'decimal.js'
7 | import {Provider as JotaiProvider} from 'jotai'
8 | import type {AppProps} from 'next/app'
9 | import dynamic from 'next/dynamic'
10 | import Head from 'next/head'
11 | import {SnackbarProvider} from 'notistack'
12 | import type {FC} from 'react'
13 | import {SWRConfig} from 'swr'
14 |
15 | Decimal.set({toExpNeg: -9e15, toExpPos: 9e15, precision: 50})
16 |
17 | const JotaiDevTools = dynamic(() =>
18 | import('@phala/lib').then((lib) => lib.JotaiDevTools),
19 | )
20 |
21 | const MyApp: FC = (props) => {
22 | const {Component, pageProps} = props
23 |
24 | return (
25 | <>
26 |
27 |
28 | SubBridge
29 |
33 |
34 |
35 | {
38 | if (process.env.NODE_ENV === 'development') {
39 | console.error(key, error)
40 | }
41 | },
42 | }}
43 | >
44 |
45 |
46 |
47 |
54 |
55 |
56 |
57 |
58 | {process.env.NODE_ENV === 'development' && }
59 |
60 |
61 |
62 |
63 |
64 |
65 | >
66 | )
67 | }
68 |
69 | export default MyApp
70 |
--------------------------------------------------------------------------------
/apps/subbridge/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | DocumentHeadTags,
3 | type DocumentHeadTagsProps,
4 | documentGetInitialProps,
5 | } from '@mui/material-nextjs/v15-pagesRouter'
6 | import {
7 | type DocumentContext,
8 | type DocumentProps,
9 | Head,
10 | Html,
11 | Main,
12 | NextScript,
13 | } from 'next/document'
14 |
15 | export default function MyDocument(
16 | props: DocumentProps & DocumentHeadTagsProps,
17 | ) {
18 | return (
19 |
20 |
21 | {/* favicons */}
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
41 | MyDocument.getInitialProps = async (ctx: DocumentContext) => {
42 | const finalProps = await documentGetInitialProps(ctx)
43 | return finalProps
44 | }
45 |
--------------------------------------------------------------------------------
/apps/subbridge/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import BridgeBody from '@/components/BridgeBody'
2 | import BridgeHistory from '@/components/BridgeHistory'
3 | import Footer from '@/components/Footer'
4 | import KhalaSunsetAlert from '@/components/KhalaSunsetAlert'
5 | import {Box, Container, useTheme} from '@mui/material'
6 | import type {NextPage} from 'next'
7 | import dynamic from 'next/dynamic'
8 |
9 | const PolkadotWalletDialog = dynamic(
10 | async () => await import('@/components/PolkadotWalletDialog'),
11 | {ssr: false},
12 | )
13 |
14 | const Home: NextPage = () => {
15 | const theme = useTheme()
16 | return (
17 |
25 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | )
47 | }
48 |
49 | export default Home
50 |
--------------------------------------------------------------------------------
/apps/subbridge/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/subbridge/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/apps/subbridge/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/subbridge/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/apps/subbridge/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/subbridge/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/apps/subbridge/public/background_waves.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/subbridge/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/apps/subbridge/public/favicon.ico
--------------------------------------------------------------------------------
/apps/subbridge/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/apps/subbridge/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/web-manifest-combined.json",
3 | "name": "SubBridge",
4 | "short_name": "SubBridge",
5 | "description": "Cross-chain Router, Bridging Parachain, EVM, and other chains.",
6 | "icons": [
7 | {
8 | "src": "/android-chrome-192x192.png",
9 | "sizes": "192x192",
10 | "type": "image/png"
11 | },
12 | {
13 | "src": "/android-chrome-512x512.png",
14 | "sizes": "512x512",
15 | "type": "image/png"
16 | }
17 | ],
18 | "theme_color": "#ffffff",
19 | "background_color": "#ffffff",
20 | "display": "standalone"
21 | }
22 |
--------------------------------------------------------------------------------
/apps/subbridge/store/common.ts:
--------------------------------------------------------------------------------
1 | import type {PaletteMode} from '@mui/material'
2 | import {atom} from 'jotai'
3 | import {atomWithStorage} from 'jotai/utils'
4 | import {fromAccountAtom} from './bridge'
5 |
6 | export type ColorSchemeSetting = PaletteMode | 'system'
7 |
8 | export const colorSchemeSettingAtom = atomWithStorage(
9 | 'jotai:color_scheme_setting',
10 | 'system',
11 | )
12 |
13 | export const isWalletConnectAtom = atom((get) =>
14 | Boolean(get(fromAccountAtom)),
15 | )
16 |
17 | export const khalaSunsetAlertOpenAtom = atomWithStorage(
18 | 'jotai:khala_sunset_alert_open',
19 | true,
20 | )
21 |
22 | export const moreButtonBadgeVersionAtom = atomWithStorage(
23 | 'jotai:more_button_badge_version',
24 | 0,
25 | )
26 |
--------------------------------------------------------------------------------
/apps/subbridge/store/ethers.ts:
--------------------------------------------------------------------------------
1 | import type {ethers} from 'ethers'
2 | import {atom} from 'jotai'
3 | import {fromChainAtom} from './bridge'
4 |
5 | export const ethersWeb3ProviderAtom =
6 | atom(null)
7 | export const evmAccountAtom = atom(null)
8 | export const evmChainIdAtom = atom(null)
9 | export const isNetworkWrongAtom = atom((get) => {
10 | const evmChainId = get(evmChainIdAtom)
11 | const fromChain = get(fromChainAtom)
12 | return (
13 | evmChainId !== undefined &&
14 | fromChain.kind === 'evm' &&
15 | fromChain.evmChainId !== evmChainId
16 | )
17 | })
18 |
--------------------------------------------------------------------------------
/apps/subbridge/store/polkadotWalletModal.ts:
--------------------------------------------------------------------------------
1 | import {atom} from 'jotai'
2 |
3 | export const polkadotWalletModalOpenAtom = atom(false)
4 |
--------------------------------------------------------------------------------
/apps/subbridge/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "jsx": "preserve",
6 | "jsxImportSource": "@emotion/react",
7 | "baseUrl": ".",
8 | "paths": {
9 | "@/components/*": ["components/*"],
10 | "@/hooks/*": ["hooks/*"],
11 | "@/lib/*": ["lib/*"],
12 | "@/config/*": ["config/*"],
13 | "@/assets/*": ["assets/*"],
14 | "@/store/*": ["store/*"]
15 | },
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ]
21 | },
22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
23 | "exclude": ["node_modules"]
24 | }
25 |
--------------------------------------------------------------------------------
/apps/subbridge/types/global.d.ts:
--------------------------------------------------------------------------------
1 | import '@total-typescript/ts-reset'
2 |
--------------------------------------------------------------------------------
/apps/subbridge/types/svgr.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg?react' {
2 | const ReactComponent: React.FC>
3 |
4 | export default ReactComponent
5 | }
6 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3 | "files": {
4 | "ignore": [
5 | "packages/polkadot-types/interfaces",
6 | "subsquid*.ts",
7 | "metadata.json",
8 | "assets"
9 | ],
10 | "ignoreUnknown": true
11 | },
12 | "formatter": {
13 | "indentStyle": "space"
14 | },
15 | "linter": {
16 | "rules": {
17 | "correctness": {
18 | "noUnusedImports": "error"
19 | }
20 | }
21 | },
22 | "json": {
23 | "parser": {
24 | "allowComments": true,
25 | "allowTrailingCommas": true
26 | }
27 | },
28 | "javascript": {
29 | "formatter": {
30 | "bracketSpacing": false,
31 | "quoteStyle": "single",
32 | "semicolons": "asNeeded"
33 | }
34 | },
35 | "vcs": {
36 | "clientKind": "git",
37 | "defaultBranch": "main",
38 | "enabled": true,
39 | "useIgnoreFile": true
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/bunfig.toml:
--------------------------------------------------------------------------------
1 | [install]
2 | peer = false
3 | saveTextLockfile = true
4 |
--------------------------------------------------------------------------------
/lefthook.yml:
--------------------------------------------------------------------------------
1 | pre-commit:
2 | commands:
3 | check:
4 | glob: '*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}'
5 | run: bunx biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files} && git update-index --again
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phala-apps",
3 | "version": "0.0.1",
4 | "private": true,
5 | "homepage": "https://github.com/Phala-Network/apps#readme",
6 | "bugs": {
7 | "url": "https://github.com/Phala-Network/apps/issues"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/Phala-Network/apps.git"
12 | },
13 | "license": "MIT",
14 | "workspaces": ["apps/*", "packages/*"],
15 | "scripts": {
16 | "build": "turbo run build",
17 | "check": "turbo run check",
18 | "preinstall": "lefthook install"
19 | },
20 | "devDependencies": {
21 | "@biomejs/biome": "^1.9.4",
22 | "@total-typescript/ts-reset": "^0.6.1",
23 | "@types/node": "^22.14.1",
24 | "lefthook": "^1.11.10",
25 | "tsx": "^4.19.3",
26 | "turbo": "^2.5.0",
27 | "typescript": "^5.8.3"
28 | },
29 | "packageManager": "bun@1.1.43",
30 | "patchedDependencies": {
31 | "@buildwithsygma/sygma-sdk-core@2.11.2": "patches/@buildwithsygma%2Fsygma-sdk-core@2.11.2.patch"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@phala/lib",
3 | "version": "0.0.1",
4 | "sideEffects": false,
5 | "type": "module",
6 | "main": "src/index.ts",
7 | "scripts": {
8 | "check": "tsc --project ./tsconfig.json --noEmit"
9 | },
10 | "dependencies": {
11 | "@polkadot/keyring": "^13.3.1",
12 | "@polkadot/util": "^13.3.1",
13 | "decimal.js": "^10.4.3"
14 | },
15 | "peerDependencies": {
16 | "@phala/store": "workspace:*",
17 | "@polkadot/api": ">=15",
18 | "@polkadot/types": ">=15",
19 | "@talismn/connect-wallets": "^1",
20 | "jotai": "^2",
21 | "react": ">=18"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/lib/src/JotaiDevTools.tsx:
--------------------------------------------------------------------------------
1 | import {DevTools} from 'jotai-devtools'
2 | import 'jotai-devtools/styles.css'
3 | import type {FC} from 'react'
4 |
5 | const JotaiDevTools: FC = () => {
6 | return
7 | }
8 |
9 | export default JotaiDevTools
10 |
--------------------------------------------------------------------------------
/packages/lib/src/compactFormat.ts:
--------------------------------------------------------------------------------
1 | import type Decimal from 'decimal.js'
2 |
3 | const compactFormat = (
4 | value: Decimal | number,
5 | maximumFractionDigits = 2,
6 | ): string => {
7 | return Intl.NumberFormat('en-US', {
8 | notation: 'compact',
9 | maximumFractionDigits,
10 | }).format(
11 | typeof value === 'number' ? value : BigInt(value.floor().toString()),
12 | )
13 | }
14 |
15 | export default compactFormat
16 |
--------------------------------------------------------------------------------
/packages/lib/src/createPolkadotApi.ts:
--------------------------------------------------------------------------------
1 | import type {ApiPromise} from '@polkadot/api'
2 | import type {ApiOptions} from '@polkadot/api/types'
3 | import type {HexString} from '@polkadot/util/types'
4 |
5 | export const createPolkadotApi = async (
6 | endpoint: string,
7 | options?: ApiOptions,
8 | ): Promise => {
9 | const metadata = await fetch(
10 | `https://phala.network/api/rpc-metadata?rpc=${endpoint}`,
11 | ).then(
12 | (res) => res.json() as unknown as Record,
13 | () => {
14 | return undefined
15 | },
16 | )
17 | const {ApiPromise, WsProvider, HttpProvider} = await import('@polkadot/api')
18 | const provider = endpoint.startsWith('ws')
19 | ? new WsProvider(endpoint)
20 | : new HttpProvider(endpoint)
21 | const api = await ApiPromise.create({
22 | provider,
23 | metadata,
24 | noInitWarn: true,
25 | ...options,
26 | })
27 |
28 | return api
29 | }
30 |
--------------------------------------------------------------------------------
/packages/lib/src/getDecimalPattern.ts:
--------------------------------------------------------------------------------
1 | const getDecimalPattern = (decimals: number): string =>
2 | `^[0-9]+\\.?[0-9]{0,${decimals}}$`
3 |
4 | export default getDecimalPattern
5 |
--------------------------------------------------------------------------------
/packages/lib/src/index.ts:
--------------------------------------------------------------------------------
1 | export {default as compactFormat} from './compactFormat'
2 | export * from './createPolkadotApi'
3 | export {default as getDecimalPattern} from './getDecimalPattern'
4 | export * from './signAndSend'
5 | export * from './sleep'
6 | export * from './toCurrency'
7 | export * from './toFixed'
8 | export * from './toPercentage'
9 | export {default as transformSs58Format} from './transformSs58Format'
10 | export {default as trimAddress} from './trimAddress'
11 | export {useConnectPolkadotWallet} from './useConnectPolkadotWallet'
12 | export {useInterval} from './useInterval'
13 | export {useTimeout} from './useTimeout'
14 | export * from './validateAddress'
15 | export * from './weightedAverage'
16 | export {default as JotaiDevTools} from './JotaiDevTools'
17 |
--------------------------------------------------------------------------------
/packages/lib/src/signAndSend.ts:
--------------------------------------------------------------------------------
1 | import type {ApiPromise} from '@polkadot/api'
2 | import type {AddressOrPair, SubmittableExtrinsic} from '@polkadot/api/types'
3 | import type {ExtrinsicStatus} from '@polkadot/types/interfaces'
4 | import type {Signer} from '@polkadot/types/types'
5 |
6 | interface SignAndSendProps {
7 | account: AddressOrPair
8 | api: ApiPromise
9 | extrinsic: SubmittableExtrinsic<'promise'>
10 | signer: Signer
11 | onStatus?: (status: ExtrinsicStatus) => void
12 | onReady?: () => void
13 | }
14 |
15 | export interface ExtrinsicResult {
16 | txHash: `0x${string}`
17 | method: string
18 | section: string
19 | }
20 |
21 | export const waitSignAndSend = async ({
22 | account,
23 | api,
24 | extrinsic,
25 | onStatus,
26 | signer,
27 | onReady,
28 | }: SignAndSendProps): Promise => {
29 | const nonce = await api.rpc.system.accountNextIndex(account.toString())
30 |
31 | const extrinsicResultPromise = new Promise(
32 | (resolve, reject) => {
33 | const {section, method} = extrinsic.method.toHuman() as {
34 | section: string
35 | method: string
36 | }
37 | extrinsic
38 | .signAndSend(
39 | account,
40 | {signer, nonce},
41 | ({status, isCompleted, txHash, dispatchError}) => {
42 | if (isCompleted) {
43 | if (dispatchError != null) {
44 | let errorInfo: string
45 | if (dispatchError.isModule) {
46 | const decoded = api.registry.findMetaError(
47 | dispatchError.asModule,
48 | )
49 | errorInfo = `${decoded.section}.${
50 | decoded.method
51 | }: ${decoded.docs.join(' ')}`
52 | } else {
53 | errorInfo = dispatchError.toString()
54 | }
55 | // TODO: add txHash
56 | reject(new Error(errorInfo))
57 | } else {
58 | resolve({txHash: txHash.toHex(), section, method})
59 | }
60 | }
61 |
62 | onStatus?.(status)
63 | if (status.isReady) {
64 | onReady?.()
65 | }
66 | },
67 | )
68 | .then((unsubscribe) => {
69 | void extrinsicResultPromise.finally(() => {
70 | unsubscribe()
71 | })
72 | }, reject)
73 | },
74 | )
75 |
76 | return await extrinsicResultPromise
77 | }
78 |
--------------------------------------------------------------------------------
/packages/lib/src/sleep.ts:
--------------------------------------------------------------------------------
1 | export const sleep = async (ms: number): Promise => {
2 | await new Promise((resolve) => setTimeout(resolve, ms))
3 | }
4 |
--------------------------------------------------------------------------------
/packages/lib/src/toCurrency.ts:
--------------------------------------------------------------------------------
1 | import Decimal from 'decimal.js'
2 | import {toFixed} from './toFixed'
3 |
4 | export function toCurrency(
5 | value: Decimal | string | number,
6 | fractionDigits = 2,
7 | ): string {
8 | const fixedValue = toFixed(
9 | typeof value === 'string' ? new Decimal(value) : value,
10 | fractionDigits,
11 | )
12 |
13 | const n = fixedValue
14 | const p = n.indexOf('.')
15 | return n.replace(/\d(?=(?:\d{3})+(?:\.|$))/g, (m, i) =>
16 | p < 0 || i < p ? `${m},` : m,
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/packages/lib/src/toFixed.ts:
--------------------------------------------------------------------------------
1 | import Decimal from 'decimal.js'
2 |
3 | export function toFixed(
4 | value: number | Decimal | string,
5 | fractionDigits = 4,
6 | ): string {
7 | const decimalValue = new Decimal(value)
8 | // NOTE: https://stackoverflow.com/a/63988968/7920298
9 | const str = decimalValue.toFixed(fractionDigits, Decimal.ROUND_DOWN)
10 | if (fractionDigits > 0) {
11 | return str.replace(/(\.0+|0+)$/, '')
12 | }
13 |
14 | return str
15 | }
16 |
--------------------------------------------------------------------------------
/packages/lib/src/toPercentage.ts:
--------------------------------------------------------------------------------
1 | import Decimal from 'decimal.js'
2 | import {toFixed} from './toFixed'
3 |
4 | export function toPercentage(
5 | value: Decimal | string | number | null | undefined,
6 | fractionDigits = 2,
7 | ): string | undefined | null {
8 | if (value == null) return null
9 | const decimal = new Decimal(value)
10 |
11 | return `${toFixed(decimal.times(100), fractionDigits)}%`
12 | }
13 |
--------------------------------------------------------------------------------
/packages/lib/src/transformSs58Format.ts:
--------------------------------------------------------------------------------
1 | import {decodeAddress, encodeAddress} from '@polkadot/keyring'
2 |
3 | const transformSs58Format = (address: string, ss58Format: number): string => {
4 | try {
5 | return encodeAddress(decodeAddress(address), ss58Format)
6 | } catch (err) {
7 | return ''
8 | }
9 | }
10 |
11 | export default transformSs58Format
12 |
--------------------------------------------------------------------------------
/packages/lib/src/trimAddress.ts:
--------------------------------------------------------------------------------
1 | const trimAddress = (str: string, start = 4, end = 4): string => {
2 | if (str.length < start + end) return str
3 |
4 | return `${str.slice(0, start)}…${str.slice(-end)}`
5 | }
6 |
7 | export default trimAddress
8 |
--------------------------------------------------------------------------------
/packages/lib/src/useConnectPolkadotWallet.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import {polkadotAccountsAtom, walletAtom, walletNameAtom} from '@phala/store'
3 | import type {WalletAccount} from '@talismn/connect-wallets'
4 | import {useAtom} from 'jotai'
5 | import {useEffect} from 'react'
6 | import transformSs58Format from './transformSs58Format'
7 | import {validateAddress} from './validateAddress'
8 |
9 | export const useConnectPolkadotWallet = (
10 | dappName: string,
11 | ss58Format?: number,
12 | ): void => {
13 | const [, setAccounts] = useAtom(polkadotAccountsAtom)
14 | const [wallet, setWallet] = useAtom(walletAtom)
15 | const [walletName] = useAtom(walletNameAtom)
16 |
17 | useEffect(() => {
18 | if (wallet != null || walletName == null) return
19 | let unmounted = false
20 | const connect = async (): Promise => {
21 | const {getWallets} = await import('@talismn/connect-wallets')
22 | const wallets = getWallets()
23 | const newWallet = wallets.find((w) => w.title === walletName)
24 | if (newWallet != null) {
25 | try {
26 | await newWallet.enable(dappName)
27 | if (!unmounted) {
28 | setWallet(newWallet)
29 | }
30 | } catch (err) {
31 | // Ignore auto connect errors
32 | }
33 | }
34 | }
35 | void connect()
36 | return () => {
37 | unmounted = true
38 | }
39 | }, [setWallet, dappName, walletName, wallet])
40 |
41 | useEffect(() => {
42 | let unsub: () => void
43 | let unmounted = false
44 | const saveAccounts = (accounts?: WalletAccount[]): void => {
45 | if (accounts == null || unmounted) return
46 | if (ss58Format === undefined) {
47 | setAccounts(accounts)
48 | } else {
49 | setAccounts(
50 | accounts.map((a) => {
51 | return {
52 | ...a,
53 | address: transformSs58Format(a.address, ss58Format),
54 | }
55 | }),
56 | )
57 | }
58 | }
59 | const updateAccounts = async (): Promise => {
60 | if (wallet != null) {
61 | // Some wallets don't implement subscribeAccounts correctly, so call getAccounts anyway
62 | const walletAccounts = await wallet.getAccounts()
63 | const accounts = walletAccounts.filter((account) =>
64 | validateAddress(account.address),
65 | )
66 | saveAccounts(accounts)
67 | if (!unmounted) {
68 | unsub = (await wallet.subscribeAccounts(saveAccounts)) as () => void
69 | }
70 | } else {
71 | setAccounts(null)
72 | }
73 | }
74 | void updateAccounts()
75 | return () => {
76 | unmounted = true
77 | unsub?.()
78 | }
79 | }, [wallet, setAccounts, ss58Format])
80 | }
81 |
--------------------------------------------------------------------------------
/packages/lib/src/useInterval.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import {useEffect, useRef} from 'react'
3 |
4 | export const useInterval = (
5 | callback: () => void,
6 | delay: number | null,
7 | ): void => {
8 | const savedCallback = useRef(callback)
9 | savedCallback.current = callback
10 | useEffect(() => {
11 | if (delay == null) {
12 | return
13 | }
14 | const id = setInterval(() => {
15 | savedCallback.current()
16 | }, delay)
17 | return () => {
18 | clearInterval(id)
19 | }
20 | }, [delay])
21 | }
22 |
--------------------------------------------------------------------------------
/packages/lib/src/useTimeout.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import {useEffect, useRef} from 'react'
3 |
4 | export const useTimeout = (
5 | callback: () => void,
6 | delay: number | null,
7 | ): void => {
8 | const savedCallback = useRef(callback)
9 | savedCallback.current = callback
10 | useEffect(() => {
11 | if (delay == null) {
12 | return
13 | }
14 | const id = setTimeout(() => {
15 | savedCallback.current()
16 | }, delay)
17 | return () => {
18 | clearTimeout(id)
19 | }
20 | }, [delay])
21 | }
22 |
--------------------------------------------------------------------------------
/packages/lib/src/validateAddress.ts:
--------------------------------------------------------------------------------
1 | import {decodeAddress, encodeAddress} from '@polkadot/keyring'
2 | import {hexToU8a, isHex} from '@polkadot/util'
3 |
4 | // https://polkadot.js.org/docs/util-crypto/examples/validate-address
5 | export const validateAddress = (address: string): boolean => {
6 | // forbid public key
7 | if (address.startsWith('0x')) return false
8 | try {
9 | encodeAddress(isHex(address) ? hexToU8a(address) : decodeAddress(address))
10 |
11 | return true
12 | } catch (error) {
13 | return false
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/lib/src/weightedAverage.ts:
--------------------------------------------------------------------------------
1 | import Decimal from 'decimal.js'
2 |
3 | export const weightedAverage = (input: Array<[Decimal, Decimal]>): Decimal => {
4 | const [sum, weightSum] = input.reduce(
5 | (acc, [num, weight]) => {
6 | acc[0] = acc[0].plus(num.times(weight))
7 | acc[1] = acc[1].plus(weight)
8 | return acc
9 | },
10 | [new Decimal(0), new Decimal(0)],
11 | )
12 | return sum.div(weightSum)
13 | }
14 |
--------------------------------------------------------------------------------
/packages/lib/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/polkadot-types/interfaces/augment-api.ts:
--------------------------------------------------------------------------------
1 | // Auto-generated via `yarn polkadot-types-from-chain`, do not edit
2 | /* eslint-disable */
3 |
4 | import './augment-api-consts.js';
5 | import './augment-api-errors.js';
6 | import './augment-api-events.js';
7 | import './augment-api-query.js';
8 | import './augment-api-tx.js';
9 | import './augment-api-rpc.js';
10 | import './augment-api-runtime.js';
11 |
--------------------------------------------------------------------------------
/packages/polkadot-types/interfaces/definitions.ts:
--------------------------------------------------------------------------------
1 | export {}
2 |
--------------------------------------------------------------------------------
/packages/polkadot-types/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | // Auto-generated via `yarn polkadot-types-from-defs`, do not edit
2 | /* eslint-disable */
3 |
4 | export * from './types.js';
5 |
--------------------------------------------------------------------------------
/packages/polkadot-types/interfaces/types.ts:
--------------------------------------------------------------------------------
1 | // Auto-generated via `yarn polkadot-types-from-defs`, do not edit
2 | /* eslint-disable */
3 |
4 |
--------------------------------------------------------------------------------
/packages/polkadot-types/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@phala/polkadot-types",
3 | "version": "0.0.1",
4 | "type": "module",
5 | "scripts": {
6 | "generate": "npm run generate:defs && npm run generate:meta",
7 | "generate:defs": "tsx ../../node_modules/.bin/polkadot-types-from-defs --package @phala/polkadot-types/interfaces --endpoint metadata.json --input ./interfaces",
8 | "generate:meta": "tsx ../../node_modules/.bin/polkadot-types-from-chain --package @phala/polkadot-types/interfaces --endpoint metadata.json --output ./interfaces",
9 | "updateMetadata": "bun updateMetadata.ts"
10 | },
11 | "dependencies": {
12 | "@polkadot/api": "^15.8.1"
13 | },
14 | "devDependencies": {
15 | "@polkadot/typegen": "^15.8.1"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/polkadot-types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "noUnusedLocals": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/polkadot-types/updateMetadata.ts:
--------------------------------------------------------------------------------
1 | import {writeFileSync} from 'node:fs'
2 |
3 | const address = process.env.ADDRESS ?? 'https://api.phala.network/ws'
4 |
5 | void fetch(address, {
6 | method: 'POST',
7 | headers: {'content-type': 'application/json'},
8 | body: JSON.stringify({
9 | id: 1,
10 | jsonrpc: '2.0',
11 | method: 'state_getMetadata',
12 | params: [],
13 | }),
14 | })
15 | .then(async (res) => await res.text())
16 | .then((data) => {
17 | writeFileSync('metadata.json', data)
18 | })
19 |
--------------------------------------------------------------------------------
/packages/store/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@phala/store",
3 | "version": "0.0.1",
4 | "sideEffects": false,
5 | "type": "module",
6 | "main": "src/index.ts",
7 | "scripts": {
8 | "check": "tsc --project ./tsconfig.json --noEmit"
9 | },
10 | "peerDependencies": {
11 | "@talismn/connect-wallets": "^1",
12 | "jotai": "^2",
13 | "react": ">=17"
14 | },
15 | "peerDependenciesMeta": {
16 | "@talismn/connect-wallets": {
17 | "optional": true
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/store/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './polkadotAccountAtoms'
2 | export * from './walletAtoms'
3 |
--------------------------------------------------------------------------------
/packages/store/src/polkadotAccountAtoms.ts:
--------------------------------------------------------------------------------
1 | import type {WalletAccount} from '@talismn/connect-wallets'
2 | import {atom} from 'jotai'
3 | import {atomWithStorage} from 'jotai/utils'
4 |
5 | export const polkadotAccountsAtom = atom(null)
6 |
7 | const polkadotAccountAddressAtom = atomWithStorage(
8 | 'jotai:polkadot_account_address',
9 | null,
10 | )
11 |
12 | export const polkadotAccountAtom = atom(
13 | (get) => {
14 | const polkadotAccountAddress = get(polkadotAccountAddressAtom)
15 | if (polkadotAccountAddress == null) return null
16 | return (
17 | get(polkadotAccountsAtom)?.find(
18 | (account) => account.address === get(polkadotAccountAddressAtom),
19 | ) ?? null
20 | )
21 | },
22 | (_get, set, account: string | null) => {
23 | set(polkadotAccountAddressAtom, account)
24 | },
25 | )
26 |
--------------------------------------------------------------------------------
/packages/store/src/walletAtoms.ts:
--------------------------------------------------------------------------------
1 | import type {Wallet} from '@talismn/connect-wallets'
2 | import {atom} from 'jotai'
3 | import {atomWithStorage} from 'jotai/utils'
4 |
5 | export const walletNameAtom = atomWithStorage(
6 | 'jotai:wallet_name',
7 | null,
8 | )
9 |
10 | const originalWalletAtom = atom(null)
11 | export const walletAtom = atom(
12 | (get) => get(originalWalletAtom),
13 | (_get, set, newWallet: Wallet | null) => {
14 | set(originalWalletAtom, newWallet)
15 | set(walletNameAtom, newWallet != null ? newWallet.title : null)
16 | },
17 | )
18 |
--------------------------------------------------------------------------------
/packages/store/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/ui/icons/asset/aca.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/aca.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/astr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/astr.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/ausd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/ausd.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/bnc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/bnc.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/bsx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/bsx.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/crab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/crab.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/dot.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/eth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/eth.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/glmr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/glmr.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/hko.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/hko.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/kar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/kar.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/kma.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/kma.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/movr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/movr.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/para.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/para.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/pha.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/pha.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/sdn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/sdn.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/tur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/tur.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/vpha.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/vpha.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/wglmr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/wglmr.png
--------------------------------------------------------------------------------
/packages/ui/icons/asset/zlk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/asset/zlk.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/acala.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/acala.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/astar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/astar.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/astarevm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/astarevm.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/base.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/base.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/basilisk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/basilisk.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/bifrost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/bifrost.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/calamari.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/calamari.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/crab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/crab.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/ethereum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/ethereum.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/karura.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/karura.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/khala.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/khala.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/moonbase_alpha.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/moonbase_alpha.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/moonbeam.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/moonbeam.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/moonriver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/moonriver.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/parallel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/parallel.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/parallel_heiko.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/parallel_heiko.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/phala.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/phala.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/polkadot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/polkadot.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/shiden.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/shiden.png
--------------------------------------------------------------------------------
/packages/ui/icons/chain/turing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phala-Network/apps/4ab19eaf2f6ba3d12a1c25c0a745b32a5f0e6a8a/packages/ui/icons/chain/turing.png
--------------------------------------------------------------------------------
/packages/ui/index.ts:
--------------------------------------------------------------------------------
1 | export type {}
2 |
--------------------------------------------------------------------------------
/packages/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@phala/ui",
3 | "version": "0.0.1",
4 | "sideEffects": false,
5 | "main": "index.ts",
6 | "files": ["icons"],
7 | "scripts": {
8 | "check": "tsc --project ./tsconfig.json --noEmit"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json"
3 | }
4 |
--------------------------------------------------------------------------------
/patches/@buildwithsygma%2Fsygma-sdk-core@2.11.2.patch:
--------------------------------------------------------------------------------
1 | diff --git a/dist-cjs/chains/EVM/helpers.js b/dist-cjs/chains/EVM/helpers.js
2 | index cc3b76a440a6a182ba84cbe689962702a31a02c1..05fe4abb0372b902aacbf0ea185bb25dd4e4fee2 100644
3 | --- a/dist-cjs/chains/EVM/helpers.js
4 | +++ b/dist-cjs/chains/EVM/helpers.js
5 | @@ -2,7 +2,7 @@
6 | Object.defineProperty(exports, "__esModule", { value: true });
7 | exports.toHex = exports.getSubstrateRecipientAddressInBytes = exports.getEVMRecipientAddressInBytes = exports.constructSubstrateRecipient = exports.createPermissionlessGenericDepositData = exports.createPermissionedGenericDepositData = exports.createERCDepositData = void 0;
8 | const ethers_1 = require("ethers");
9 | -const create_1 = require("@polkadot/types/create");
10 | +const create_1 = require("../../../node_modules/@polkadot/api/node_modules/@polkadot/types/create");
11 | const util_crypto_1 = require("@polkadot/util-crypto");
12 | const registry = new create_1.TypeRegistry();
13 | /**
14 | diff --git a/dist-esm/chains/EVM/helpers.js b/dist-esm/chains/EVM/helpers.js
15 | index caee2324bdb05ec255c82659aa96788f022e593f..47af8dfb4759e9b2566580dcff4157a9fba53b20 100644
16 | --- a/dist-esm/chains/EVM/helpers.js
17 | +++ b/dist-esm/chains/EVM/helpers.js
18 | @@ -1,5 +1,5 @@
19 | import { utils, BigNumber } from 'ethers';
20 | -import { TypeRegistry } from '@polkadot/types/create';
21 | +import { TypeRegistry } from '../../../node_modules/@polkadot/api/node_modules/@polkadot/types/create';
22 | import { decodeAddress } from '@polkadot/util-crypto';
23 | const registry = new TypeRegistry();
24 | /**
25 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowImportingTsExtensions": true,
4 | "allowJs": true,
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "incremental": true,
8 | "isolatedModules": true,
9 | "jsx": "preserve",
10 | "lib": ["ESNext"],
11 | "module": "ESNext",
12 | "moduleResolution": "Bundler",
13 | "noEmit": true,
14 | "noFallthroughCasesInSwitch": true,
15 | // "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "resolveJsonModule": true,
18 | "skipLibCheck": true,
19 | "strict": true,
20 | "strictNullChecks": true,
21 | "target": "ESNext"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "tasks": {
4 | "build": {
5 | "dependsOn": ["^build"],
6 | "outputs": [
7 | "out/**",
8 | ".next/**",
9 | "build/**",
10 | ".svelte-kit/**",
11 | ".vercel/**"
12 | ],
13 | "env": ["NODE_ENV", "VERCEL_ENV"]
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------