Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions knip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ const config: KnipConfig = {
'packages/web3-constants': {
entry: ['constants.ts'],
},
'packages/web3-contracts': {
ignoreDependencies: ['@typechain/web3-v1'],
},
'packages/injected-script': {
ignore: ['main/debugger.ts'],
entry: ['main/index.ts'],
Expand Down
78 changes: 47 additions & 31 deletions packages/mask/entry-sdk/bridge/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { maskSDK } from '../index.js'
import { sample } from 'lodash-es'
import { AsyncCall, JSONEncoder } from 'async-call-rpc/full'
import { isValidChecksumAddress } from '@ethereumjs/util'
import { recoverMessageAddress } from 'viem'
import { recoverMessageAddress, type Address } from 'viem'

const PassthroughMethods = [
...readonlyMethodType,
Expand Down Expand Up @@ -237,53 +237,69 @@ const methods: Methods = {
throw err.wallet_watchAsset.the_token_address_seems_invalid()
}
}
async function readRequired<T>(contract: Promise<T | undefined>) {
const value = await contract
if (value === undefined) throw err.wallet_watchAsset.the_token_address_seems_invalid()
return value
}
if (type === 'ERC20') {
const contract = providers.EVMContractReadonly.getERC20Contract(address)!.methods
const contract = providers.EVMContractReadonly.getERC20Contract(address)
if (!contract) return err.wallet_watchAsset.the_token_address_seems_invalid()
try {
// try verify if it is a ERC20 contract
await contract.totalSupply().call()
await providers.EVMContractReadonly.readContract(contract, 'totalSupply')
} catch {
return err.wallet_watchAsset.the_token_address_seems_invalid()
}

await Promise.all([
verifySymbol(contract.symbol().call()),
contract
.decimals()
.call()
.then(
(realDecimal) => {
const realDecimals = Number.parseInt(realDecimal, 10)
if (decimals && realDecimals !== decimals)
throw err.wallet_watchAsset.the_decimals_in_the_request_request_do_not_match_the_decimals_in_the_contract_decimals(
{ decimals: realDecimal + '', request: decimals + '' },
)
decimals = realDecimals
},
() => {
if (decimals) return
throw err.wallet_watchAsset.decimals_are_required_but_were_not_found_in_either_the_request_or_contract()
},
),
verifySymbol(readRequired(providers.EVMContractReadonly.readContract(contract, 'symbol'))),
providers.EVMContractReadonly.readContract(contract, 'decimals').then(
(realDecimal) => {
const realDecimals = Number.parseInt(String(realDecimal), 10)
if (decimals && realDecimals !== decimals)
throw err.wallet_watchAsset.the_decimals_in_the_request_request_do_not_match_the_decimals_in_the_contract_decimals(
{ decimals: String(realDecimal), request: decimals + '' },
)
decimals = realDecimals
},
() => {
if (decimals) return
throw err.wallet_watchAsset.decimals_are_required_but_were_not_found_in_either_the_request_or_contract()
},
),
])
} else if (type === 'ERC721') {
const contract = providers.EVMContractReadonly.getERC721Contract(address)!.methods
if (!tokenId) return err.wallet_watchAsset.the_token_address_seems_invalid()
const contract = providers.EVMContractReadonly.getERC721Contract(address)
if (!contract) return err.wallet_watchAsset.the_token_address_seems_invalid()

await verifyContractInterface(contract.supportsInterface('0x780e9d63').call())
await verifySymbol(contract.symbol().call())
await verifyContractInterface(
readRequired(providers.EVMContractReadonly.readContract(contract, 'supportsInterface', ['0x780e9d63'])),
)
await verifySymbol(readRequired(providers.EVMContractReadonly.readContract(contract, 'symbol')))

const owner = await contract.ownerOf(tokenId).call()
const owner = await readRequired(
providers.EVMContractReadonly.readContract(contract, 'ownerOf', [BigInt(tokenId)]),
)
if (!isSameAddress(owner, (await Services.Wallet.sdk_eth_accounts(location.origin))[0] || '')) {
return err.wallet_watchAsset.unable_to_verify_ownership_possibly_because_the_standard_is_not_supported_or_the_users_currently_selected_network_does_not_match_the_chain_of_the_asset_in_question()
}
} else if (type === 'ERC1155') {
const contract = providers.EVMContractReadonly.getERC1155Contract(address)!.methods
await verifyContractInterface(contract.supportsInterface('0xd9b67a26').call())
if (!tokenId) return err.wallet_watchAsset.the_token_address_seems_invalid()
const contract = providers.EVMContractReadonly.getERC1155Contract(address)
if (!contract) return err.wallet_watchAsset.the_token_address_seems_invalid()
await verifyContractInterface(
readRequired(providers.EVMContractReadonly.readContract(contract, 'supportsInterface', ['0xd9b67a26'])),
)

const balance = await contract
.balanceOf((await Services.Wallet.sdk_eth_accounts(location.origin))[0], tokenId)
.call()
if (balance === '0') {
const balance = await readRequired(
providers.EVMContractReadonly.readContract(contract, 'balanceOf', [
((await Services.Wallet.sdk_eth_accounts(location.origin))[0] || '') as Address,
BigInt(tokenId),
]),
)
if (balance === 0n) {
return err.wallet_watchAsset.unable_to_verify_ownership_possibly_because_the_standard_is_not_supported_or_the_users_currently_selected_network_does_not_match_the_chain_of_the_asset_in_question()
}
}
Expand Down
43 changes: 21 additions & 22 deletions packages/plugins/Claim/src/hooks/useClaimAirdrop.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import { useRef, useCallback } from 'react'
import { useAsyncFn } from 'react-use'
import { useTheme } from '@mui/material'
import { AirdropV2Abi, type AirdropV2 } from '@masknet/web3-contracts/types/AirdropV2.js'
import { AirdropV2Abi } from '@masknet/web3-contracts/types/AirdropV2.js'
import { useChainContext } from '@masknet/web3-hooks-base'
import { useContract } from '@masknet/web3-hooks-evm'
import {
useAirdropClaimersConstants,
type ChainId,
ProviderType,
ContractTransaction,
formatEtherToWei,
} from '@masknet/web3-shared-evm'
import { useAirdropClaimersConstants, type ChainId, ProviderType, formatEtherToWei } from '@masknet/web3-shared-evm'
import { type SnackbarKey, useCustomSnackbar, type SnackbarMessage, type ShowSnackbarOptions } from '@masknet/theme'
import { toFixed } from '@masknet/web3-shared-base'
import { useRemoteControlledDialog } from '@masknet/shared-base-ui'
import { EVMWeb3 } from '@masknet/web3-providers'
import { EVMContract, EVMWeb3 } from '@masknet/web3-providers'
import { PluginClaimMessage } from '../message.js'
import { Trans } from '@lingui/react/macro'
import type { Address, Hex } from 'viem'

export function useClaimAirdrop(
chainId: ChainId,
Expand All @@ -29,7 +24,7 @@ export function useClaimAirdrop(
const theme = useTheme()
const { account, providerType, chainId: globalChainId } = useChainContext()
const { CONTRACT_ADDRESS } = useAirdropClaimersConstants(chainId)
const airdropContract = useContract<AirdropV2>(chainId, CONTRACT_ADDRESS, AirdropV2Abi)
const airdropContract = useContract(chainId, CONTRACT_ADDRESS, AirdropV2Abi)

const { setDialog } = useRemoteControlledDialog(PluginClaimMessage.claimSuccessDialogEvent)

Expand All @@ -56,18 +51,22 @@ export function useClaimAirdrop(
providerType: ProviderType.WalletConnect,
})
}
const tx = await new ContractTransaction(airdropContract.options.address).fillAll(
airdropContract.methods.claim(eventIndex, merkleProof, account, formatEtherToWei(amount)),
{
from: account,
gas: toFixed(
await airdropContract.methods
.claim(eventIndex, merkleProof, account, formatEtherToWei(amount))
.estimateGas({ from: account }),
),
chainId,
},
)
const args = [
BigInt(eventIndex),
merkleProof as Hex[],
account as Address,
BigInt(formatEtherToWei(amount).toFixed(0)),
] as const
const gas = await EVMContract.estimateContractGas(airdropContract, 'claim', args, {
chainId,
from: account,
})
const tx = EVMContract.createTransactionRequest(airdropContract, 'claim', args, {
from: account,
gas: toFixed(gas ?? 0),
chainId,
})
if (!tx) return

const hash = await EVMWeb3.sendTransaction(tx, {
chainId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,13 @@ import { useChainContext, useNetworkContext, useNetworks, useWeb3State } from '@
import { Telemetry } from '@masknet/web3-telemetry'
import { EventType, EventID, ExceptionType, ExceptionID } from '@masknet/web3-telemetry/types'

interface ConnectionContentProps {
onClose?: () => void
}

const useStyles = makeStyles()({
container: {
overflow: 'auto',
},
})

export function ConnectionContent(props: ConnectionContentProps) {
export function ConnectionContent() {
const { classes } = useStyles()
const { pluginID } = useNetworkContext()
const { account, chainId } = useChainContext<NetworkPluginID.PLUGIN_EVM>()
Expand Down Expand Up @@ -81,7 +77,10 @@ export function ConnectionContent(props: ConnectionContentProps) {
from: '0x66b57885E8E9D84742faBda0cE6E3496055b012d',
to: '0x2b9e7ccdf0f4e5b24757c1e1a80e311e34cb10c7',
value: '1',
data: contract?.methods.approve('0x31f42841c2db5173425b5223809cf3a38fede360', '1').encodeABI(),
data: EVMContract.encodeContractFunctionData(contract!.abi, 'approve', [
'0x31f42841c2db5173425b5223809cf3a38fede360',
1n,
]),
},
0,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function ConnectionDialog() {
<InjectedDialog title="Connection" fullWidth open={open} onClose={closeDialog}>
<DialogContent>
<EVMWeb3ContextProvider>
<ConnectionContent onClose={closeDialog} />
<ConnectionContent />
</EVMWeb3ContextProvider>
</DialogContent>
</InjectedDialog>
Expand Down
29 changes: 20 additions & 9 deletions packages/plugins/RedPacket/src/SiteAdaptor/RedPacket/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,25 @@ import { LoadingStatus, TransactionConfirmModal } from '@masknet/shared'
import { EMPTY_LIST, NetworkPluginID, Sniffings } from '@masknet/shared-base'
import { queryClient } from '@masknet/shared-base-ui'
import { makeStyles } from '@masknet/theme'
import type { HappyRedPacketV4 } from '@masknet/web3-contracts/types/HappyRedPacketV4.js'
import { NetworkContextProvider, useChainContext, useNetwork } from '@masknet/web3-hooks-base'
import { EVMChainResolver } from '@masknet/web3-providers'
import { EVMChainResolver, EVMContract } from '@masknet/web3-providers'
import { RedPacketStatus, type RedPacketJSONPayload } from '@masknet/web3-providers/types'
import { TokenType, formatBalance, isZero, minus } from '@masknet/web3-shared-base'
import { ChainId, useRedPacketConstant } from '@masknet/web3-shared-evm'
import { Card, Grow, Link } from '@mui/material'
import { memo, useCallback, useMemo, useState } from 'react'
import { RedPacketEnvelope } from '../components/RedPacketEnvelope.js'
import { Conditions } from '../Conditions/index.js'
import { formatRedPacketAvailability } from '../hooks/useAvailability.js'
import { useAvailabilityComputed } from '../hooks/useAvailabilityComputed.js'
import { useClaimCallback } from '../hooks/useClaimCallback.js'
import { useIsFireflyRedpacket } from '../hooks/useIsFireflyRedpacket.js'
import { useRedPacketContract } from '../hooks/useRedPacketContract.js'
import { asHappyRedPacketV4Contract, useRedPacketContract } from '../hooks/useRedPacketContract.js'
import { useRedPacketCover } from '../hooks/useRedPacketCover.js'
import { useRefundCallback } from '../hooks/useRefundCallback.js'
import { OperationFooter } from './OperationFooter.js'
import { ClaimOnFirefly } from '../components/ClaimOnFirefly.js'
import type { Hex } from 'viem'

const useStyles = makeStyles()((theme) => {
return {
Expand Down Expand Up @@ -142,12 +143,22 @@ export const RedPacket = memo(function RedPacket({ payload, currentPluginID }: R
redpacketChainId,
)

const redPacketContract = useRedPacketContract(redpacketChainId, payload.contract_version) as HappyRedPacketV4
const redPacketContract = useRedPacketContract(redpacketChainId, payload.contract_version)
const checkResult = useCallback(async () => {
const data = await redPacketContract.methods.check_availability(payload.rpid).call({
// check availability is ok w/o account
from: account,
})
if (!redPacketContract) return
const data = formatRedPacketAvailability(
await EVMContract.readContract(
asHappyRedPacketV4Contract(redPacketContract),
'check_availability',
[payload.rpid as Hex],
{
chainId: redpacketChainId,
// check availability is ok w/o account
from: account,
},
),
)
if (!data) return
if (isZero(data.claimed_amount)) return
TransactionConfirmModal.open({
shareText: claimedShareText,
Expand All @@ -160,7 +171,7 @@ export const RedPacket = memo(function RedPacket({ payload, currentPluginID }: R
title: _(msg`Lucky Drop`),
share: (text) => share?.(text, source ? source : undefined),
})
}, [redPacketContract.methods, payload.rpid, account, claimedShareText, token, _, source])
}, [redPacketContract, payload.rpid, redpacketChainId, account, claimedShareText, token, _, source])

const [showRequirements, setShowRequirements] = useState(false)
const onClaimOrRefund = useCallback(async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
import type { ChainId, ProviderType, Transaction } from '@masknet/web3-shared-evm'
import type { BaseConnectionOptions } from '@masknet/web3-providers/types'
import type { HappyRedPacketV4 } from '@masknet/web3-contracts/types/HappyRedPacketV4.js'
import { EVMContract } from '@masknet/web3-providers'
import { useChainContext } from '@masknet/web3-hooks-base'
import type { NetworkPluginID } from '@masknet/shared-base'
import { useRedPacketContract } from './useRedPacketContract.js'
import { asHappyRedPacketV4Contract, useRedPacketContract } from './useRedPacketContract.js'
import { useQuery } from '@tanstack/react-query'
import type { Hex } from 'viem'

export type RedPacketAvailability = {
token_address: string
balance: string
total: string
claimed: string
expired: boolean
claimed_amount: string
}

export function formatRedPacketAvailability(value: unknown): RedPacketAvailability | null {
if (!Array.isArray(value)) return null
const [tokenAddress, balance, total, claimed, expired, claimedAmountOrIfClaimed] = value
return {
token_address: String(tokenAddress ?? ''),
balance: String(balance ?? '0'),
total: String(total ?? '0'),
claimed: String(claimed ?? '0'),
expired: Boolean(expired),
claimed_amount:
typeof claimedAmountOrIfClaimed === 'boolean' ?
claimedAmountOrIfClaimed ? '1'
: '0'
: String(claimedAmountOrIfClaimed ?? '0'),
}
}

export function useAvailability(
id: string,
Expand All @@ -15,17 +42,24 @@ export function useAvailability(
account: options?.account,
chainId: options?.chainId,
})
const redPacketContract = useRedPacketContract(chainId, version) as HappyRedPacketV4
const redPacketContract = useRedPacketContract(chainId, version)
return useQuery({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: ['red-packet', 'check-availability', chainId, version, id, account],
queryFn: async () => {
if (!id || !redPacketContract) return null
const availability = await redPacketContract.methods.check_availability(id).call({
// check availability is ok w/o account
from: account,
})
return availability
return formatRedPacketAvailability(
await EVMContract.readContract(
asHappyRedPacketV4Contract(redPacketContract),
'check_availability',
[id as Hex],
{
chainId,
// check availability is ok w/o account
from: account,
},
),
)
},
refetchInterval(query) {
const { data } = query.state
Expand Down
Loading
Loading