mirror of
https://github.com/zhigang1992/xverse-web-extension.git
synced 2026-04-30 13:42:52 +08:00
Merge pull request #874 from secretkeylabs/release/v0.48.1
release: v0.48.1 to main
This commit is contained in:
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright Secret Key Labs Limited 2024. All rights reserved.
|
||||
Copyright Secret Key Labs Limited 2025. All rights reserved.
|
||||
|
||||
You acknowledge and agree that Secret Key Labs Limited own all legal right, title and interest in and to the work, software, application, source code, documentation and any other documents in this repository (collectively, the “Program”), including any intellectual property rights which subsist in the Program (whether those rights happen to be registered or not, and wherever in the world those rights may exist), whether in source code or any other form.
|
||||
|
||||
|
||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "xverse-web-extension",
|
||||
"version": "0.48.0",
|
||||
"version": "0.48.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "xverse-web-extension",
|
||||
"version": "0.48.0",
|
||||
"version": "0.48.1",
|
||||
"dependencies": {
|
||||
"@aryzing/superqs": "0.0.6",
|
||||
"@ledgerhq/hw-transport-webusb": "6.27.17",
|
||||
@@ -17,7 +17,7 @@
|
||||
"@sats-connect/core": "0.5.2",
|
||||
"@scure/base": "1.1.9",
|
||||
"@scure/btc-signer": "1.2.1",
|
||||
"@secretkeylabs/xverse-core": "36.0.1",
|
||||
"@secretkeylabs/xverse-core": "36.0.2",
|
||||
"@stacks/connect": "7.9.0",
|
||||
"@stacks/stacks-blockchain-api-types": "7.14.1",
|
||||
"@stacks/transactions": "7.0.2",
|
||||
@@ -1265,9 +1265,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@secretkeylabs/xverse-core": {
|
||||
"version": "36.0.1",
|
||||
"resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/36.0.1/9b8902ab25b7f7e08d45a885c70c6bc3d9714075",
|
||||
"integrity": "sha512-6mJWaN2W3qFZ8f++NqdAbuNAkk8VlnwKwDBgh6AVI+t2l9qjIt74p5VVHwuppAZcM6mi3Aiwem3ggdNXU90TUw==",
|
||||
"version": "36.0.2",
|
||||
"resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/36.0.2/1e4854a0378c9c6960cf3467634f0ab42991b43f",
|
||||
"integrity": "sha512-zlPjXcSW2IZOcGBS/dKeONm8cOvphi5AhnNXUZl2vbTPDhXnGII5HWXF7QoJtaRKw7QcNGqNmJ7bK+QfPsyGIw==",
|
||||
"dependencies": {
|
||||
"@bitcoinerlab/secp256k1": "1.0.2",
|
||||
"@noble/curves": "1.3.0",
|
||||
@@ -14148,9 +14148,9 @@
|
||||
}
|
||||
},
|
||||
"@secretkeylabs/xverse-core": {
|
||||
"version": "36.0.1",
|
||||
"resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/36.0.1/9b8902ab25b7f7e08d45a885c70c6bc3d9714075",
|
||||
"integrity": "sha512-6mJWaN2W3qFZ8f++NqdAbuNAkk8VlnwKwDBgh6AVI+t2l9qjIt74p5VVHwuppAZcM6mi3Aiwem3ggdNXU90TUw==",
|
||||
"version": "36.0.2",
|
||||
"resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/36.0.2/1e4854a0378c9c6960cf3467634f0ab42991b43f",
|
||||
"integrity": "sha512-zlPjXcSW2IZOcGBS/dKeONm8cOvphi5AhnNXUZl2vbTPDhXnGII5HWXF7QoJtaRKw7QcNGqNmJ7bK+QfPsyGIw==",
|
||||
"requires": {
|
||||
"@bitcoinerlab/secp256k1": "1.0.2",
|
||||
"@noble/curves": "1.3.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xverse-web-extension",
|
||||
"description": "A Bitcoin wallet for Web3",
|
||||
"version": "0.48.0",
|
||||
"description": "The Bitcoin Wallet for everyone",
|
||||
"version": "0.48.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "^18.18.2"
|
||||
@@ -32,6 +32,7 @@
|
||||
"tsc-files --noEmit src/styled.d.ts src/react-app-env.d.ts"
|
||||
],
|
||||
"*.json": [
|
||||
"node scripts/pin_all_deps.js",
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
@@ -45,7 +46,7 @@
|
||||
"@sats-connect/core": "0.5.2",
|
||||
"@scure/base": "1.1.9",
|
||||
"@scure/btc-signer": "1.2.1",
|
||||
"@secretkeylabs/xverse-core": "36.0.1",
|
||||
"@secretkeylabs/xverse-core": "36.0.2",
|
||||
"@stacks/connect": "7.9.0",
|
||||
"@stacks/stacks-blockchain-api-types": "7.14.1",
|
||||
"@stacks/transactions": "7.0.2",
|
||||
|
||||
@@ -8,21 +8,46 @@
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const packageLock = require('../package-lock.json');
|
||||
const packageJson = require('../package.json');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
for (const packageName in packageJson.dependencies) {
|
||||
const installedPathKey = `node_modules/${packageName}`;
|
||||
if (packageJson.dependencies.hasOwnProperty(packageName) && packageLock.packages[installedPathKey]) {
|
||||
packageJson.dependencies[packageName] = packageLock.packages[installedPathKey].version;
|
||||
const packageJsonPath = path.resolve(__dirname, '../package.json');
|
||||
const packageJson = require(packageJsonPath);
|
||||
|
||||
const allDependenciesVersions = Object.values(packageJson.dependencies).concat(
|
||||
Object.values(packageJson.devDependencies),
|
||||
);
|
||||
|
||||
// if any version has a ^ or ~, we need to pin it, otherwise we can skip
|
||||
const hasUnpinnedVersions = allDependenciesVersions.some(
|
||||
(version) => version.startsWith('^') || version.startsWith('~'),
|
||||
);
|
||||
|
||||
if (hasUnpinnedVersions) {
|
||||
const packages = require('../package-lock.json').packages;
|
||||
|
||||
for (const packageName in packageJson.dependencies) {
|
||||
const installedVersion = packages[`node_modules/${packageName}`].version;
|
||||
if (packageJson.dependencies.hasOwnProperty(packageName) && installedVersion) {
|
||||
packageJson.dependencies[packageName] = installedVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const packageName in packageJson.devDependencies) {
|
||||
const installedPathKey = `node_modules/${packageName}`;
|
||||
if (packageJson.devDependencies.hasOwnProperty(packageName) && packageLock.packages[installedPathKey]) {
|
||||
packageJson.devDependencies[packageName] = packageLock.packages[installedPathKey].version;
|
||||
for (const packageName in packageJson.devDependencies) {
|
||||
const installedVersion = packages[`node_modules/${packageName}`].version;
|
||||
if (packageJson.devDependencies.hasOwnProperty(packageName) && installedVersion) {
|
||||
packageJson.devDependencies[packageName] = installedVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync('../package.json', JSON.stringify(packageJson, null, 2));
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
|
||||
|
||||
// Run npm install to update package-lock.json
|
||||
console.log('Running npm install to update package-lock.json...');
|
||||
execSync('npm install', { stdio: 'inherit' });
|
||||
// execSync('git add package.json package-lock.json', { stdio: 'inherit' });
|
||||
|
||||
console.log('Successfully pinned all dependency versions');
|
||||
} else {
|
||||
console.log('All dependencies are already pinned to exact versions');
|
||||
}
|
||||
|
||||
@@ -55,20 +55,14 @@ export const getAmountFromPostCondition = (
|
||||
if (pc.conditionType === PostConditionType.NonFungible) return '1';
|
||||
};
|
||||
|
||||
function hasAssetInfo(pc: any): pc is { assetInfo: { assetName: { content: string } } } {
|
||||
return (
|
||||
pc &&
|
||||
pc.assetInfo &&
|
||||
pc.assetInfo.assetName &&
|
||||
typeof pc.assetInfo.assetName.content === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
export const getSymbolFromPostCondition = (
|
||||
pc: STXPostConditionWire | FungiblePostConditionWire | NonFungiblePostConditionWire,
|
||||
) => {
|
||||
if (hasAssetInfo(pc)) {
|
||||
return pc.assetInfo.assetName?.content?.slice(0, 3).toUpperCase();
|
||||
if (
|
||||
pc.conditionType === PostConditionType.Fungible ||
|
||||
pc.conditionType === PostConditionType.NonFungible
|
||||
) {
|
||||
return pc.asset.assetName.content.slice(0, 3).toUpperCase();
|
||||
}
|
||||
return 'STX';
|
||||
};
|
||||
@@ -76,8 +70,11 @@ export const getSymbolFromPostCondition = (
|
||||
export const getNameFromPostCondition = (
|
||||
pc: STXPostConditionWire | FungiblePostConditionWire | NonFungiblePostConditionWire,
|
||||
) => {
|
||||
if (hasAssetInfo(pc)) {
|
||||
return pc.assetInfo.assetName.content;
|
||||
if (
|
||||
pc.conditionType === PostConditionType.Fungible ||
|
||||
pc.conditionType === PostConditionType.NonFungible
|
||||
) {
|
||||
return pc.asset.assetName.content;
|
||||
}
|
||||
return 'STX';
|
||||
};
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import {
|
||||
addressToString,
|
||||
deserializeAddress,
|
||||
FungibleConditionCode,
|
||||
NonFungibleConditionCode,
|
||||
serializePrincipal,
|
||||
addressToString,
|
||||
type PostConditionWire,
|
||||
} from '@stacks/transactions';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -48,9 +46,10 @@ function PostConditionsView({ postCondition, amount, icon }: Props) {
|
||||
const name = getNameFromPostCondition(postCondition);
|
||||
const contractName =
|
||||
'contractName' in postCondition.principal && postCondition.principal.contractName.content;
|
||||
const address = addressToString(deserializeAddress(serializePrincipal(postCondition?.principal)));
|
||||
const isSending = address === stxAddress;
|
||||
const isContractPrincipal = !!contractName || address.includes('.');
|
||||
const addressString =
|
||||
'address' in postCondition.principal ? addressToString(postCondition.principal.address) : '';
|
||||
const isSending = addressString === stxAddress;
|
||||
const isContractPrincipal = !!contractName || addressString.includes('.');
|
||||
return (
|
||||
<TransferAmountComponent
|
||||
title={`${
|
||||
@@ -59,7 +58,7 @@ function PostConditionsView({ postCondition, amount, icon }: Props) {
|
||||
value={`${amount} ${ticker}`}
|
||||
subValue={name !== 'STX' ? name : ''}
|
||||
icon={icon}
|
||||
address={`${address}${contractName ? `.${contractName}` : ''}`}
|
||||
address={`${addressString}${contractName ? `.${contractName}` : ''}`}
|
||||
subTitle={`${
|
||||
isContractPrincipal
|
||||
? t('CONTRACT_ADDRESS')
|
||||
|
||||
@@ -58,6 +58,8 @@ const TestnetContainer = styled.div((props) => ({
|
||||
background: props.theme.colors.elevation1,
|
||||
paddingTop: props.theme.spacing(3),
|
||||
paddingBottom: props.theme.spacing(3),
|
||||
borderTopLeftRadius: 'inherit',
|
||||
borderTopRightRadius: 'inherit',
|
||||
}));
|
||||
|
||||
const TestnetText = styled.p((props) => ({
|
||||
|
||||
@@ -86,6 +86,7 @@ export default function ContractCallRequest({
|
||||
onSignTransaction,
|
||||
}: Props) {
|
||||
const selectedNetwork = useNetworkSelector();
|
||||
const navigate = useNavigate();
|
||||
const [hasTabClosed, setHasTabClosed] = useState(false);
|
||||
const { t } = useTranslation('translation');
|
||||
const [fee, setFee] = useState<BigNumber | undefined>(
|
||||
@@ -115,7 +116,7 @@ export default function ContractCallRequest({
|
||||
</PostConditionAlertText>
|
||||
</PostConditionContainer>
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const broadcastTx = async (
|
||||
tx: StacksTransactionWire[],
|
||||
txAttachment: Buffer | undefined = undefined,
|
||||
@@ -202,7 +203,7 @@ export default function ContractCallRequest({
|
||||
txid: response.txid,
|
||||
currency: 'STX',
|
||||
error: '',
|
||||
browserTx: false,
|
||||
browserTx: true,
|
||||
tabId,
|
||||
messageId,
|
||||
rpcMethod,
|
||||
|
||||
@@ -3,10 +3,10 @@ import useSelectedAccount from '@hooks/useSelectedAccount';
|
||||
import useWalletSelector from '@hooks/useWalletSelector';
|
||||
import type { FungibleToken } from '@secretkeylabs/xverse-core';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback } from 'react';
|
||||
import { handleRetries, InvalidParamsError } from '@utils/query';
|
||||
|
||||
export default function useRuneUtxosQueryPerMarketplace(
|
||||
rune: FungibleToken,
|
||||
rune?: FungibleToken,
|
||||
backgroundRefetch = true,
|
||||
) {
|
||||
const { network } = useWalletSelector();
|
||||
@@ -16,7 +16,11 @@ export default function useRuneUtxosQueryPerMarketplace(
|
||||
throw new Error('Only available on Mainnet');
|
||||
}
|
||||
|
||||
const queryFn = useCallback(async () => {
|
||||
const queryFn = async () => {
|
||||
if (!rune) {
|
||||
throw new InvalidParamsError('rune token is required');
|
||||
}
|
||||
|
||||
const res = await xverseApi.listings.getListedUtxos({
|
||||
address: ordinalsAddress,
|
||||
rune: {
|
||||
@@ -52,13 +56,14 @@ export default function useRuneUtxosQueryPerMarketplace(
|
||||
listedItems,
|
||||
unlistedItems,
|
||||
};
|
||||
}, [xverseApi, ordinalsAddress, rune.name, rune.ticker]);
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
refetchOnWindowFocus: backgroundRefetch,
|
||||
refetchOnReconnect: backgroundRefetch,
|
||||
queryKey: ['get-listed-rune-utxos', ordinalsAddress, rune.name],
|
||||
enabled: Boolean(ordinalsAddress && rune.name),
|
||||
queryKey: ['get-listed-rune-utxos', ordinalsAddress, rune?.name],
|
||||
enabled: Boolean(ordinalsAddress && rune?.name),
|
||||
queryFn,
|
||||
retry: handleRetries,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ const CoinContainer = styled.div({
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const CoinTitleText = styled.p<{ isEnabled?: boolean }>((props) => ({
|
||||
...props.theme.typography[props.isEnabled ? 'body_bold_m' : 'body_m'],
|
||||
color: props.theme.colors[props.isEnabled ? 'white_0' : 'white_400'],
|
||||
const CoinTitleText = styled.p<{ $isEnabled?: boolean }>((props) => ({
|
||||
...props.theme.typography[props.$isEnabled ? 'body_bold_m' : 'body_m'],
|
||||
color: props.theme.colors[props.$isEnabled ? 'white_0' : 'white_400'],
|
||||
textAlign: 'left',
|
||||
marginLeft: props.theme.space.m,
|
||||
overflow: 'hidden',
|
||||
@@ -78,7 +78,7 @@ function CoinItem({
|
||||
}
|
||||
size={32}
|
||||
/>
|
||||
<CoinTitleText aria-label="Coin Title" isEnabled={isEnabled}>
|
||||
<CoinTitleText aria-label="Coin Title" $isEnabled={isEnabled}>
|
||||
{name}
|
||||
</CoinTitleText>
|
||||
</CoinContainer>
|
||||
|
||||
77
src/app/screens/manageTokens/index.styled.ts
Normal file
77
src/app/screens/manageTokens/index.styled.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { StyledP } from '@ui-library/common.styled';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TokenContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
margin-bottom: ${(props) => props.theme.space.xl};
|
||||
> *:not(:last-child) {
|
||||
border-bottom: 1px solid ${(props) => props.theme.colors.elevation3};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16,
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
export const ScrollableContainer = styled.div`
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
export const FtInfoContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginBottom: props.theme.space.m,
|
||||
}));
|
||||
|
||||
export const Header = styled.h1((props) => ({
|
||||
...props.theme.typography.headline_xs,
|
||||
marginBottom: props.theme.space.m,
|
||||
}));
|
||||
|
||||
export const Description = styled.h1((props) => ({
|
||||
...props.theme.typography.body_m,
|
||||
color: props.theme.colors.white_200,
|
||||
marginBottom: props.theme.space.xl,
|
||||
}));
|
||||
|
||||
export const ErrorsText = styled.p((props) => ({
|
||||
...props.theme.typography.body_bold_m,
|
||||
color: props.theme.colors.white_200,
|
||||
marginTop: props.theme.space.xl,
|
||||
marginBottom: 'auto',
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
export const ButtonRow = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
flex-direction: row;
|
||||
padding-left: ${(props) => props.theme.space.m};
|
||||
padding-right: ${(props) => props.theme.space.m};
|
||||
padding-top: ${(props) => props.theme.space.s};
|
||||
padding-bottom: ${(props) => props.theme.space.s};
|
||||
transition: background-color 0.2s ease;
|
||||
:hover {
|
||||
background-color: ${(props) => props.theme.colors.elevation3};
|
||||
}
|
||||
:active {
|
||||
background-color: ${(props) => props.theme.colors.elevation3};
|
||||
}
|
||||
`;
|
||||
|
||||
export const TokenText = styled(StyledP)`
|
||||
margin-left: ${(props) => props.theme.space.m};
|
||||
`;
|
||||
@@ -21,108 +21,24 @@ import {
|
||||
setShowSpamTokensAction,
|
||||
setSip10ManageTokensAction,
|
||||
} from '@stores/wallet/actions/actionCreators';
|
||||
import { StyledP } from '@ui-library/common.styled';
|
||||
import { TabItem } from '@ui-library/tabs';
|
||||
import { SPAM_OPTIONS_WIDTH } from '@utils/constants';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import Theme from 'theme';
|
||||
|
||||
const TokenContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
margin-bottom: ${(props) => props.theme.space.xl};
|
||||
> *:not(:last-child) {
|
||||
border-bottom: 1px solid ${(props) => props.theme.colors.elevation3};
|
||||
}
|
||||
`;
|
||||
|
||||
const Container = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16,
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
const ScrollableContainer = styled.div`
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const FtInfoContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginBottom: props.theme.spacing(8),
|
||||
}));
|
||||
|
||||
const Button = styled.button<{
|
||||
isSelected: boolean;
|
||||
}>((props) => ({
|
||||
...props.theme.typography.body_bold_l,
|
||||
fontSize: 12,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: 31,
|
||||
paddingLeft: props.theme.spacing(6),
|
||||
paddingRight: props.theme.spacing(6),
|
||||
marginRight: props.theme.spacing(2),
|
||||
borderRadius: 44,
|
||||
background: props.isSelected ? props.theme.colors.elevation3 : 'transparent',
|
||||
color: props.theme.colors.white_0,
|
||||
opacity: props.isSelected ? 1 : 0.6,
|
||||
userSelect: 'none',
|
||||
}));
|
||||
|
||||
const Header = styled.h1((props) => ({
|
||||
...props.theme.typography.headline_xs,
|
||||
marginBottom: props.theme.spacing(8),
|
||||
}));
|
||||
|
||||
const Description = styled.h1((props) => ({
|
||||
...props.theme.typography.body_m,
|
||||
color: props.theme.colors.white_200,
|
||||
marginBottom: props.theme.spacing(16),
|
||||
}));
|
||||
|
||||
const ErrorsText = styled.p((props) => ({
|
||||
...props.theme.typography.body_bold_m,
|
||||
color: props.theme.colors.white_200,
|
||||
marginTop: props.theme.spacing(16),
|
||||
marginBottom: 'auto',
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
const ButtonRow = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
flex-direction: row;
|
||||
padding-left: ${(props) => props.theme.space.m};
|
||||
padding-right: ${(props) => props.theme.space.m};
|
||||
padding-top: ${(props) => props.theme.space.s};
|
||||
padding-bottom: ${(props) => props.theme.space.s};
|
||||
transition: background-color 0.2s ease;
|
||||
:hover {
|
||||
background-color: ${(props) => props.theme.colors.elevation3};
|
||||
}
|
||||
:active {
|
||||
background-color: ${(props) => props.theme.colors.elevation3};
|
||||
}
|
||||
`;
|
||||
|
||||
const TokenText = styled(StyledP)`
|
||||
margin-left: ${(props) => props.theme.space.m};
|
||||
`;
|
||||
import {
|
||||
ButtonRow,
|
||||
Container,
|
||||
Description,
|
||||
ErrorsText,
|
||||
FtInfoContainer,
|
||||
Header,
|
||||
ScrollableContainer,
|
||||
TokenContainer,
|
||||
TokenText,
|
||||
} from './index.styled';
|
||||
|
||||
function Stacks() {
|
||||
const { hideStx } = useWalletSelector();
|
||||
@@ -157,9 +73,7 @@ function ManageTokens() {
|
||||
data.filter((ft) => ft.showToggle),
|
||||
);
|
||||
|
||||
const [selectedProtocol, setSelectedProtocol] = useState<FungibleTokenProtocol>(
|
||||
selectedAccount?.stxAddress ? 'stacks' : 'brc-20',
|
||||
);
|
||||
const [selectedProtocol, setSelectedProtocol] = useState<FungibleTokenProtocol>('runes');
|
||||
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
@@ -221,6 +135,7 @@ function ManageTokens() {
|
||||
disabled={false}
|
||||
toggled={toggled}
|
||||
enabled={coin.isEnabled}
|
||||
protocol={selectedProtocol}
|
||||
/>
|
||||
))}
|
||||
{!coins.length && <ErrorsText>{t('NO_COINS')}</ErrorsText>}
|
||||
@@ -258,26 +173,26 @@ function ManageTokens() {
|
||||
<Header>{t('ADD_COINS')}</Header>
|
||||
<Description>{t('DESCRIPTION')}</Description>
|
||||
<FtInfoContainer>
|
||||
{selectedAccount?.stxAddress && (
|
||||
<Button
|
||||
isSelected={selectedProtocol === 'stacks'}
|
||||
onClick={() => setSelectedProtocol('stacks')}
|
||||
>
|
||||
SIP-10
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
isSelected={selectedProtocol === 'brc-20'}
|
||||
onClick={() => setSelectedProtocol('brc-20')}
|
||||
>
|
||||
BRC-20
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={selectedProtocol === 'runes'}
|
||||
<TabItem
|
||||
$active={selectedProtocol === 'runes'}
|
||||
onClick={() => setSelectedProtocol('runes')}
|
||||
>
|
||||
RUNES
|
||||
</Button>
|
||||
</TabItem>
|
||||
<TabItem
|
||||
$active={selectedProtocol === 'brc-20'}
|
||||
onClick={() => setSelectedProtocol('brc-20')}
|
||||
>
|
||||
BRC-20
|
||||
</TabItem>
|
||||
{selectedAccount?.stxAddress && (
|
||||
<TabItem
|
||||
$active={selectedProtocol === 'stacks'}
|
||||
onClick={() => setSelectedProtocol('stacks')}
|
||||
>
|
||||
STACKS
|
||||
</TabItem>
|
||||
)}
|
||||
</FtInfoContainer>
|
||||
<TokenContainer>{getCoinsList()}</TokenContainer>
|
||||
</ScrollableContainer>
|
||||
|
||||
@@ -32,10 +32,10 @@ function SendBtcScreen() {
|
||||
const transactionContext = useTransactionContext(overridePaymentType);
|
||||
const userCanSwitchPayType = useCanUserSwitchPaymentType();
|
||||
|
||||
const [recipientAddress, setRecipientAddress] = useState<string>('');
|
||||
const [recipientAddress, setRecipientAddress] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [amountSats, setAmountSats] = useState<string>('');
|
||||
const [amountSats, setAmountSats] = useState('');
|
||||
const [feeRate, setFeeRate] = useState('');
|
||||
const [sendMax, setSendMax] = useState(false);
|
||||
|
||||
@@ -137,7 +137,7 @@ function SendBtcScreen() {
|
||||
txid: txnId,
|
||||
currency: 'BTC',
|
||||
error: '',
|
||||
browserTx: isInOption,
|
||||
browserTx: false,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -147,7 +147,7 @@ function SendBtcScreen() {
|
||||
txid: '',
|
||||
currency: 'BTC',
|
||||
error: `${e}`,
|
||||
browserTx: isInOption,
|
||||
browserTx: false,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
|
||||
@@ -163,7 +163,7 @@ function SendOrdinalScreen() {
|
||||
txid: txnId,
|
||||
currency: 'BTC',
|
||||
error: '',
|
||||
browserTx: isInOption,
|
||||
browserTx: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -180,7 +180,7 @@ function SendOrdinalScreen() {
|
||||
txid: '',
|
||||
currency: 'BTC',
|
||||
error: `${e}`,
|
||||
browserTx: isInOption,
|
||||
browserTx: false,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
|
||||
@@ -170,7 +170,7 @@ function SendRuneScreen() {
|
||||
txid: txnId,
|
||||
currency: 'BTC',
|
||||
error: '',
|
||||
browserTx: isInOption,
|
||||
browserTx: false,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -180,7 +180,7 @@ function SendRuneScreen() {
|
||||
txid: '',
|
||||
currency: 'BTC',
|
||||
error: `${e}`,
|
||||
browserTx: isInOption,
|
||||
browserTx: false,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
|
||||
@@ -206,7 +206,7 @@ function ChangeNetworkScreen() {
|
||||
isSelected={formInputs.type === 'Signet'}
|
||||
onNetworkSelected={onNetworkSelected}
|
||||
disabled={isChangingNetwork}
|
||||
showDivider={false}
|
||||
showDivider
|
||||
/>
|
||||
<NetworkRow
|
||||
network={savedRegtest || defaultRegtest}
|
||||
|
||||
@@ -70,7 +70,7 @@ function Setting() {
|
||||
<SettingComponent
|
||||
text={t('NETWORK')}
|
||||
onClick={openChangeNetworkScreen}
|
||||
textDetail={network.type}
|
||||
textDetail={network.type === 'Testnet' ? 'Testnet3' : network.type}
|
||||
showDivider
|
||||
/>
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ const useFromTokens = (toToken?: FungibleToken) => {
|
||||
|
||||
const filteredTokens = toToken
|
||||
? sortedTokens.filter(
|
||||
(token) => token.principal !== toToken.ticker && toToken.protocol === token.protocol,
|
||||
(token) =>
|
||||
token.principal !== toToken.ticker &&
|
||||
token.principal !== toToken.principal &&
|
||||
toToken.protocol === token.protocol,
|
||||
)
|
||||
: sortedTokens;
|
||||
|
||||
|
||||
@@ -79,7 +79,8 @@ const ProtocolItem = styled.button<{ $selected: boolean }>`
|
||||
const supportedProtocols: Protocol[] = ['runes', 'sip10']; // add more protocols here
|
||||
|
||||
const mapProtocolName = (protocol: Protocol) => {
|
||||
if (protocol === 'sip10') return 'SIP-10';
|
||||
if (protocol === 'runes') return 'Bitcoin';
|
||||
if (protocol === 'sip10') return 'Stacks';
|
||||
return protocol.toUpperCase();
|
||||
};
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ export default function QuoteSummary({
|
||||
} = usePlaceUtxoOrder();
|
||||
|
||||
useEffect(() => {
|
||||
if (true) {
|
||||
if (placeOrderError || placeUtxoOrderError) {
|
||||
onError(placeOrderError ?? placeUtxoOrderError ?? '');
|
||||
}
|
||||
}, [placeOrderError, placeUtxoOrderError]);
|
||||
@@ -299,11 +299,9 @@ export default function QuoteSummary({
|
||||
.toFixed(2);
|
||||
})();
|
||||
|
||||
const showBadQuoteWarning =
|
||||
quote.slippageSupported &&
|
||||
new BigNumber(toTokenFiatValue).isLessThan(
|
||||
new BigNumber(fromTokenFiatValue).multipliedBy(BAD_QUOTE_PERCENTAGE),
|
||||
);
|
||||
const showBadQuoteWarning = new BigNumber(toTokenFiatValue).isLessThan(
|
||||
new BigNumber(fromTokenFiatValue).multipliedBy(BAD_QUOTE_PERCENTAGE),
|
||||
);
|
||||
const valueLossPercentage = new BigNumber(fromTokenFiatValue)
|
||||
.minus(new BigNumber(toTokenFiatValue))
|
||||
.dividedBy(new BigNumber(fromTokenFiatValue))
|
||||
@@ -330,9 +328,13 @@ export default function QuoteSummary({
|
||||
<CalloutContainer>
|
||||
<Callout
|
||||
titleText={t('SWAP_SCREEN.BAD_QUOTE_WARNING_TITLE')}
|
||||
bodyText={t('SWAP_SCREEN.BAD_QUOTE_WARNING_DESC', {
|
||||
percentage: valueLossPercentage,
|
||||
})}
|
||||
bodyText={
|
||||
quote.slippageSupported && BigNumber(toTokenFiatValue).isGreaterThan(0)
|
||||
? t('SWAP_SCREEN.BAD_QUOTE_WARNING_DESC', {
|
||||
percentage: valueLossPercentage,
|
||||
})
|
||||
: t('SWAP_SCREEN.UNKNOWN_QUOTE_VALUE_WARNING_DESC')
|
||||
}
|
||||
variant="warning"
|
||||
/>
|
||||
</CalloutContainer>
|
||||
|
||||
@@ -27,7 +27,8 @@ export default function UnlistRuneScreen() {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'LIST_RUNE_SCREEN' });
|
||||
const navigate = useNavigate();
|
||||
const { runeId } = useParams();
|
||||
const { data: runesCoinsList = [] } = useVisibleRuneFungibleTokens();
|
||||
const { data: runesCoinsList = [], isLoading: isLoadingRunesCoinsList } =
|
||||
useVisibleRuneFungibleTokens();
|
||||
const selectedRune = runesCoinsList.find((ft) => ft.principal === runeId);
|
||||
const showRunesListing =
|
||||
useHasFeature(FeatureId.RUNES_LISTING) || process.env.NODE_ENV === 'development';
|
||||
@@ -43,10 +44,10 @@ export default function UnlistRuneScreen() {
|
||||
isInitialLoading: isFetchingListedItems,
|
||||
isRefetching: isRefetchingListedItems,
|
||||
refetch,
|
||||
} = useRuneUtxosQueryPerMarketplace(selectedRune as FungibleToken, false);
|
||||
} = useRuneUtxosQueryPerMarketplace(selectedRune, false);
|
||||
const { listedItems } = data || {};
|
||||
|
||||
const isLoading = isFetchingListedItems || isRefetchingListedItems;
|
||||
const isLoading = isFetchingListedItems || isRefetchingListedItems || isLoadingRunesCoinsList;
|
||||
const showContent = !isLoading && !!listedItems?.length;
|
||||
|
||||
const handleGoBack = () =>
|
||||
|
||||
@@ -1528,7 +1528,8 @@
|
||||
"LIST_YOUR_RUNES": "List your Runes on a marketplace",
|
||||
"SLIPPAGE_WARNING": "Your transaction may be frontrun and result in an unfavorable trade",
|
||||
"BAD_QUOTE_WARNING_TITLE": "The quote you are receiving may result in significant value loss.",
|
||||
"BAD_QUOTE_WARNING_DESC": "The minimum amount you will receive will result in a loss of over {{percentage}}% of your trade’s value. This is due to low liquidity in the pool, causing discrepancies in pricing. Please review the details carefully before proceeding with the swap."
|
||||
"BAD_QUOTE_WARNING_DESC": "The minimum amount you will receive will result in a loss of over {{percentage}}% of your trade’s value. This is due to low liquidity in the pool, causing discrepancies in pricing. Please review the details carefully before proceeding with the swap.",
|
||||
"UNKNOWN_QUOTE_VALUE_WARNING_DESC": "The market value of the token you will receive is unknown. Review details carefully before proceeding with the swap."
|
||||
},
|
||||
"SWAP_CONFIRM_SCREEN": {
|
||||
"TOKEN_SWAP": "Token swap",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "Xverse Wallet",
|
||||
"description": "A Bitcoin Wallet for Web3",
|
||||
"name": "Xverse Wallet: Buy Bitcoin",
|
||||
"description": "Ordinals, Runes, NFTs & DeFi",
|
||||
"manifest_version": 3,
|
||||
"options_page": "options.html",
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": "xverse_icon.png",
|
||||
"default_title": "Xverse Wallet"
|
||||
"default_title": "Xverse Wallet: Buy Bitcoin"
|
||||
},
|
||||
"chrome_url_overrides": {},
|
||||
"background": {
|
||||
|
||||
@@ -62,7 +62,7 @@ export default class Wallet {
|
||||
|
||||
readonly checkboxTokenInactive: Locator;
|
||||
|
||||
readonly buttonSip10: Locator;
|
||||
readonly buttonStacks: Locator;
|
||||
|
||||
readonly buttonBRC20: Locator;
|
||||
|
||||
@@ -376,7 +376,7 @@ export default class Wallet {
|
||||
this.navigationExplore = page.getByTestId('nav-explore');
|
||||
this.navigationSettings = page.getByTestId('nav-settings');
|
||||
// this.balance = page.getByTestId('total-balance-value');
|
||||
this.balance = page.getByLabel(/^Total balance:/);
|
||||
this.balance = page.getByLabel(/^Total balance/);
|
||||
this.textCurrency = page.getByTestId('currency-text');
|
||||
this.allUpperButtons = page.getByTestId('transaction-buttons-row').getByRole('button');
|
||||
this.buttonTransactionSend = this.allUpperButtons.nth(0);
|
||||
@@ -439,7 +439,7 @@ export default class Wallet {
|
||||
this.checkboxToken = page.locator('label[role="checkbox"]');
|
||||
this.checkboxTokenActive = page.locator('label[role="checkbox"][aria-checked="true"]');
|
||||
this.checkboxTokenInactive = page.locator('label[role="checkbox"][aria-checked="false"]');
|
||||
this.buttonSip10 = page.getByRole('button', { name: 'SIP-10' });
|
||||
this.buttonStacks = page.getByRole('button', { name: 'STACKS' });
|
||||
this.buttonBRC20 = page.getByRole('button', { name: 'BRC-20' });
|
||||
this.buttonRunes = page.getByRole('button', { name: 'RUNES' });
|
||||
this.headingTokens = page.getByRole('heading', { name: 'Manage tokens' });
|
||||
@@ -1163,13 +1163,15 @@ export default class Wallet {
|
||||
return totalBalance;
|
||||
}
|
||||
|
||||
async selectLastToken(tokenType: 'BRC20' | 'SIP10'): Promise<string> {
|
||||
async selectLastToken(tokenType: 'BRC20' | 'STACKS'): Promise<string> {
|
||||
await this.manageTokenButton.click();
|
||||
expect(this.page.url()).toContain('manage-tokens');
|
||||
|
||||
// Click on the specific token type button if BRC20 is selected
|
||||
if (tokenType === 'BRC20') {
|
||||
await this.buttonBRC20.click();
|
||||
} else if (tokenType === 'STACKS') {
|
||||
await this.buttonStacks.click();
|
||||
}
|
||||
|
||||
const chosenToken = this.divTokenRow.last();
|
||||
|
||||
@@ -15,14 +15,14 @@ test.describe('Token Management', () => {
|
||||
await wallet.manageTokenButton.click();
|
||||
expect(page.url()).toContain('manage-tokens');
|
||||
await expect(wallet.buttonBack).toBeVisible();
|
||||
await expect(wallet.buttonSip10).toBeVisible();
|
||||
await expect(wallet.buttonStacks).toBeVisible();
|
||||
await expect(wallet.buttonBRC20).toBeVisible();
|
||||
await expect(wallet.buttonRunes).toBeVisible();
|
||||
await expect(wallet.headingTokens).toBeVisible();
|
||||
|
||||
// Check SIP10 token tab - only Stacks and sBTC should be showing when user has no sip10 balances
|
||||
await test.step('Check SIP10 token tab', async () => {
|
||||
await wallet.buttonSip10.click();
|
||||
// Check STACKS tokens tab - only Stacks and sBTC should be showing when user has no sip10 balances
|
||||
await test.step('Check STACKS tokens tab', async () => {
|
||||
await wallet.buttonStacks.click();
|
||||
await expect(wallet.labelCoinTitle).toHaveCount(2);
|
||||
await expect(wallet.checkboxToken).toHaveCount(2);
|
||||
await expect(wallet.checkboxTokenActive).toHaveCount(2);
|
||||
@@ -88,7 +88,7 @@ test.describe('Token Management', () => {
|
||||
|
||||
await test.step('Toggle a random token', async () => {
|
||||
await wallet.manageTokenButton.click();
|
||||
await wallet.buttonSip10.click();
|
||||
await wallet.buttonStacks.click();
|
||||
|
||||
// NOTE: requires an account with at least 1 sip10 token with balance
|
||||
await expect(wallet.checkboxTokenActive.first()).toBeVisible();
|
||||
@@ -106,7 +106,7 @@ test.describe('Token Management', () => {
|
||||
|
||||
// enable the token again
|
||||
await wallet.manageTokenButton.click();
|
||||
await wallet.buttonSip10.click();
|
||||
await wallet.buttonStacks.click();
|
||||
await page.getByTestId(tokenName).locator('label').click();
|
||||
|
||||
// expect to be visible again on dashboard
|
||||
|
||||
@@ -43,7 +43,7 @@ test.describe('Swap Flow Visuals', () => {
|
||||
|
||||
// Select the second Coin
|
||||
await wallet.buttonDownArrow.nth(1).click();
|
||||
|
||||
|
||||
// Had problems with loading of all tokens so I check that a 'DOG' is loaded
|
||||
await expect(page.getByText('DOG').first()).toBeVisible();
|
||||
await expect(await wallet.divTokenRow.count()).toBeGreaterThan(0);
|
||||
@@ -87,7 +87,7 @@ test.describe('Swap Flow Visuals', () => {
|
||||
|
||||
// Select the second Coin
|
||||
await wallet.buttonDownArrow.nth(1).click();
|
||||
|
||||
|
||||
// Had problems with loading of all tokens so I check that a 'DOG' is loaded
|
||||
await expect(page.getByText('DOG').first()).toBeVisible();
|
||||
await expect(await wallet.divTokenRow.count()).toBeGreaterThan(0);
|
||||
|
||||
@@ -5,7 +5,7 @@ test.describe('Transaction', () => {
|
||||
test('Visual Check SIP 10 Token Transaction history mainnet', async ({ page, extensionId }) => {
|
||||
const wallet = new Wallet(page);
|
||||
await wallet.setupTest(extensionId, 'SEED_WORDS1', false);
|
||||
const tokenName = await wallet.selectLastToken('SIP10');
|
||||
const tokenName = await wallet.selectLastToken('STACKS');
|
||||
|
||||
await wallet.clickOnSpecificToken(tokenName);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user