Compare commits

..
Author SHA1 Message Date
Matthew Russell 2534315f1a chore: make team stats aggregation epochs 192 2024-02-01 09:16:27 -08:00
25 changed files with 158 additions and 1396 deletions
@@ -71,27 +71,18 @@ const CreateTeamFormContainer = ({ isSolo }: { isSolo: boolean }) => {
if (status === 'confirmed') {
return (
<div
className="flex flex-col items-start gap-2"
data-testid="team-creation-success-message"
>
<div className="flex flex-col items-start gap-2">
<p className="text-sm">{t('Team creation transaction successful')}</p>
{code && (
<>
<p className="text-sm">
Your team ID is:{' '}
<span
className="font-mono break-all"
data-testid="team-id-display"
>
{code}
</span>
<span className="font-mono break-all">{code}</span>
</p>
<TradingAnchorButton
href={Links.COMPETITIONS_TEAM(code)}
intent={Intent.Info}
size="small"
data-testid="view-team-button"
>
{t('View team')}
</TradingAnchorButton>
@@ -16,8 +16,6 @@ import { CompetitionsLeaderboard } from '../../components/competitions/competiti
import { useTeams } from '../../lib/hooks/use-teams';
import take from 'lodash/take';
import { usePageTitle } from '../../lib/hooks/use-page-title';
import { TeamCard } from '../../components/competitions/team-card';
import { useMyTeam } from '../../lib/hooks/use-my-team';
export const CompetitionsHome = () => {
const t = useT();
@@ -35,13 +33,6 @@ export const CompetitionsHome = () => {
const { data: teamsData, loading: teamsLoading } = useTeams();
const {
team: myTeam,
stats: myTeamStats,
games: myTeamGames,
rank: myTeamRank,
} = useMyTeam();
return (
<ErrorBoundary>
<CompetitionsHeader title={t('Competitions')}>
@@ -52,83 +43,65 @@ export const CompetitionsHome = () => {
</p>
</CompetitionsHeader>
{/** Team card */}
{myTeam ? (
<>
<h2 className="text-2xl mb-6">{t('My team')}</h2>
<div className="mb-12">
<TeamCard
team={myTeam}
rank={myTeamRank}
stats={myTeamStats}
games={myTeamGames}
/>
</div>
</>
) : (
<>
{/** Get started */}
<h2 className="text-2xl mb-6">{t('Get started')}</h2>
{/** Get started */}
<h2 className="text-2xl mb-6">{t('Get started')}</h2>
<CompetitionsActionsContainer>
<CompetitionsAction
variant="A"
title={t('Create a team')}
description={t(
'Lorem ipsum dolor sit amet, consectetur adipisicing elit placeat ipsum minus nemo error dicta.'
)}
actionElement={
<TradingButton
intent={Intent.Primary}
onClick={(e) => {
e.preventDefault();
navigate(Links.COMPETITIONS_CREATE_TEAM());
}}
data-testId="create-public-team-button"
>
{t('Create a public team')}
</TradingButton>
}
/>
<CompetitionsAction
variant="B"
title={t('Solo team / lone wolf')}
description={t(
'Lorem ipsum dolor sit amet, consectetur adipisicing elit placeat ipsum minus nemo error dicta.'
)}
actionElement={
<TradingButton
intent={Intent.Primary}
onClick={(e) => {
e.preventDefault();
navigate(Links.COMPETITIONS_CREATE_TEAM_SOLO());
}}
>
{t('Create a private team')}
</TradingButton>
}
/>
<CompetitionsAction
variant="C"
title={t('Join a team')}
description={t(
'Lorem ipsum dolor sit amet, consectetur adipisicing elit placeat ipsum minus nemo error dicta.'
)}
actionElement={
<TradingButton
intent={Intent.Primary}
onClick={(e) => {
e.preventDefault();
navigate(Links.COMPETITIONS_TEAMS());
}}
>
{t('Choose a team')}
</TradingButton>
}
/>
</CompetitionsActionsContainer>
</>
)}
<CompetitionsActionsContainer>
<CompetitionsAction
variant="A"
title={t('Create a team')}
description={t(
'Lorem ipsum dolor sit amet, consectetur adipisicing elit placeat ipsum minus nemo error dicta.'
)}
actionElement={
<TradingButton
intent={Intent.Primary}
onClick={(e) => {
e.preventDefault();
navigate(Links.COMPETITIONS_CREATE_TEAM());
}}
>
{t('Create a public team')}
</TradingButton>
}
/>
<CompetitionsAction
variant="B"
title={t('Solo team / lone wolf')}
description={t(
'Lorem ipsum dolor sit amet, consectetur adipisicing elit placeat ipsum minus nemo error dicta.'
)}
actionElement={
<TradingButton
intent={Intent.Primary}
onClick={(e) => {
e.preventDefault();
navigate(Links.COMPETITIONS_CREATE_TEAM_SOLO());
}}
>
{t('Create a private team')}
</TradingButton>
}
/>
<CompetitionsAction
variant="C"
title={t('Join a team')}
description={t(
'Lorem ipsum dolor sit amet, consectetur adipisicing elit placeat ipsum minus nemo error dicta.'
)}
actionElement={
<TradingButton
intent={Intent.Primary}
onClick={(e) => {
e.preventDefault();
navigate(Links.COMPETITIONS_TEAMS());
}}
>
{t('Choose a team')}
</TradingButton>
}
/>
</CompetitionsActionsContainer>
{/** List of available games */}
<h2 className="text-2xl mb-6">{t('Games')}</h2>
@@ -105,11 +105,7 @@ export const JoinButton = ({
// Party is in a team, but not this one
else if (partyTeam && partyTeam.teamId !== team.teamId) {
return (
<Button
onClick={() => onJoin('switch')}
intent={Intent.Primary}
data-testid="switch-team-button"
>
<Button onClick={() => onJoin('switch')} intent={Intent.Primary}>
{t('Switch team')}{' '}
</Button>
);
@@ -219,11 +215,7 @@ const DialogContent = ({
</>
)}
<div className="flex justify-between gap-2">
<Button
onClick={joinTeam}
intent={Intent.Success}
data-testid="confirm-switch-button"
>
<Button onClick={joinTeam} intent={Intent.Success}>
{t('Confirm')}
</Button>
<Button onClick={onCancel} intent={Intent.Danger}>
@@ -115,12 +115,9 @@ export const TeamForm = ({
<form onSubmit={handleSubmit(sendTransaction)}>
<input type="hidden" {...register('id')} />
<TradingFormGroup label={t('Team name')} labelFor="name">
<TradingInput
{...register('name', { required: t('Required') })}
data-testid="team-name-input"
/>
<TradingInput {...register('name', { required: t('Required') })} />
{errors.name?.message && (
<TradingInputError forInput="name" data-testid="team-name-error">
<TradingInputError forInput="name">
{errors.name.message}
</TradingInputError>
)}
@@ -136,10 +133,9 @@ export const TeamForm = ({
{...register('url', {
pattern: { value: URL_REGEX, message: t('Invalid URL') },
})}
data-testid="team-url-input"
/>
{errors.url?.message && (
<TradingInputError forInput="url" data-testid="team-url-error">
<TradingInputError forInput="url">
{errors.url.message}
</TradingInputError>
)}
@@ -156,13 +152,9 @@ export const TeamForm = ({
message: t('Invalid image URL'),
},
})}
data-testid="avatar-url-input"
/>
{errors.avatarUrl?.message && (
<TradingInputError
forInput="avatarUrl"
data-testid="avatar-url-error"
>
<TradingInputError forInput="avatarUrl">
{errors.avatarUrl.message}
</TradingInputError>
)}
@@ -187,7 +179,6 @@ export const TeamForm = ({
onCheckedChange={(value) => {
field.onChange(value);
}}
data-testid="team-private-checkbox"
/>
);
}}
@@ -216,13 +207,9 @@ export const TeamForm = ({
},
},
})}
data-testid="team-allow-list-textarea"
/>
{errors.allowList?.message && (
<TradingInputError
forInput="avatarUrl"
data-testid="team-allow-list-error"
>
<TradingInputError forInput="avatarUrl">
{errors.allowList.message}
</TradingInputError>
)}
@@ -297,12 +284,7 @@ const SubmitButton = ({
return (
<div className="flex gap-2 items-baseline">
<TradingButton
type="submit"
intent={Intent.Info}
disabled={disabled}
data-testid="team-form-submit-button"
>
<TradingButton type="submit" intent={Intent.Info} disabled={disabled}>
{text}
</TradingButton>
{status === 'confirmed' && confirmed}
@@ -1,24 +1,16 @@
import { type Team } from '../../lib/hooks/use-team';
import { type ComponentProps } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { type Team } from '../../lib/hooks/use-team';
import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
import { Links } from '../../lib/links';
import { useT } from '../../lib/use-t';
export const UpdateTeamButton = ({
team,
size = 'medium',
}: {
team: Pick<Team, 'teamId' | 'referrer'>;
size?: ComponentProps<typeof TradingAnchorButton>['size'];
}) => {
export const UpdateTeamButton = ({ team }: { team: Team }) => {
const t = useT();
const { pubKey, isReadOnly } = useVegaWallet();
if (pubKey && !isReadOnly && pubKey === team.referrer) {
return (
<TradingAnchorButton
size={size}
data-testid="update-team-button"
href={Links.COMPETITIONS_UPDATE_TEAM(team.teamId)}
intent={Intent.Info}
+3 -20
View File
@@ -5,11 +5,7 @@ export const BORDER_COLOR = 'border-vega-clight-500 dark:border-vega-cdark-500';
export const GRADIENT =
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
export const Box = ({
children,
backgroundImage,
...props
}: HTMLAttributes<HTMLDivElement> & { backgroundImage?: string }) => {
export const Box = (props: HTMLAttributes<HTMLDivElement>) => {
return (
<div
{...props}
@@ -17,22 +13,9 @@ export const Box = ({
BORDER_COLOR,
GRADIENT,
'border rounded-lg',
'relative p-6 overflow-hidden',
'p-6',
props.className
)}
>
{Boolean(backgroundImage?.length) && (
<div
className={classNames(
'pointer-events-none',
'bg-no-repeat bg-center bg-[length:500px_500px]',
'absolute top-0 left-0 w-full h-full -z-10 opacity-30 blur-lg'
)}
style={{ backgroundImage: `url("${backgroundImage}")` }}
></div>
)}
{children}
</div>
/>
);
};
@@ -3,7 +3,7 @@ import classNames from 'classnames';
const NUM_AVATARS = 20;
const AVATAR_PATHNAME_PATTERN = '/team-avatars/{id}.png';
export const getFallbackAvatar = (teamId: string) => {
const getFallbackAvatar = (teamId: string) => {
const avatarId = ((parseInt(teamId, 16) % NUM_AVATARS) + 1)
.toString()
.padStart(2, '0'); // between 01 - 20
@@ -1,154 +0,0 @@
import { type TeamGame, type TeamStats } from '../../lib/hooks/use-team';
import { type TeamsFieldsFragment } from '../../lib/hooks/__generated__/Teams';
import { TeamAvatar, getFallbackAvatar } from './team-avatar';
import { FavoriteGame, Stat } from './team-stats';
import { useT } from '../../lib/use-t';
import { formatNumberRounded } from '@vegaprotocol/utils';
import BigNumber from 'bignumber.js';
import { Box } from './box';
import { Intent, Tooltip, TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
import { Links } from '../../lib/links';
import orderBy from 'lodash/orderBy';
import { take } from 'lodash';
import { DispatchMetricLabels } from '@vegaprotocol/types';
import classNames from 'classnames';
import { UpdateTeamButton } from '../../client-pages/competitions/update-team-button';
export const TeamCard = ({
rank,
team,
stats,
games,
}: {
rank: number;
team: TeamsFieldsFragment;
stats?: TeamStats;
games?: TeamGame[];
}) => {
const t = useT();
const lastGames = take(
orderBy(
games?.map((g) => ({
rank: g.team.rank,
metric: g.team.rewardMetric,
epoch: g.epoch,
})),
(i) => i.epoch,
'desc'
),
5
);
return (
<div
className={classNames(
'gap-6 grid grid-cols-1 grid-rows-1',
'md:grid-cols-3'
)}
>
{/** Card */}
<Box
backgroundImage={team.avatarUrl || getFallbackAvatar(team.teamId)}
className="flex flex-col items-center gap-3 min-w-[80px] lg:min-w-[112px]"
>
<TeamAvatar teamId={team.teamId} imgUrl={team.avatarUrl} />
<h1 className="calt lg:text-2xl" data-testid="team-name">
{team.name}
</h1>
{games && <FavoriteGame games={games} noLabel />}
<TradingAnchorButton
size="extra-small"
intent={Intent.Primary}
href={Links.COMPETITIONS_TEAM(team.teamId)}
>
{t('Profile')}
</TradingAnchorButton>
<UpdateTeamButton team={team} size="extra-small" />
</Box>
{/** Tiles */}
<Box className="w-full md:col-span-2">
<div
className={classNames(
'grid gap-3 w-full mb-4',
'md:grid-cols-3 md:grid-rows-2',
'grid-cols-2 grid-rows-3'
)}
>
<Stat
className="flex flex-col-reverse"
value={rank}
label={t('Rank')}
valueTestId="team-rank"
/>
<Stat
className="flex flex-col-reverse"
value={team.totalMembers || 0}
label={t('Members')}
valueTestId="members-count-stat"
/>
<Stat
className="flex flex-col-reverse"
value={stats?.totalGamesPlayed || 0}
label={t('Total games')}
valueTestId="total-games-stat"
/>
<Stat
className="flex flex-col-reverse"
value={
stats?.totalQuantumVolume
? formatNumberRounded(
new BigNumber(stats.totalQuantumVolume || 0),
'1e3'
)
: 0
}
label={t('Total volume')}
valueTestId="total-volume-stat"
/>
<Stat
className="flex flex-col-reverse"
value={
stats?.totalQuantumRewards
? formatNumberRounded(
new BigNumber(stats.totalQuantumRewards || 0),
'1e3'
)
: 0
}
label={t('Rewards paid out')}
valueTestId="rewards-paid-stat"
/>
</div>
<dl className="w-full pt-4 border-t border-vega-clight-700 dark:border-vega-cdark-700">
<dt className="mb-1 text-sm text-muted">
{t('Last {{games}} games result', {
replace: { games: lastGames.length || '' },
})}
</dt>
<dd className="flex flex-row flex-wrap gap-2">
{lastGames.length === 0 && t('None available')}
{lastGames.map((game, i) => (
<Tooltip key={i} description={DispatchMetricLabels[game.metric]}>
<button className="cursor-help text-sm bg-vega-clight-700 dark:bg-vega-cdark-700 px-2 py-1 rounded-full">
<RankLabel rank={game.rank} />
</button>
</Tooltip>
))}
</dd>
</dl>
</Box>
</div>
);
};
/**
* Sets the english ordinal for given rank only if the current language is set
* to english.
*/
const RankLabel = ({ rank }: { rank: number }) => {
const t = useT();
return t('place', { count: rank, ordinal: true });
};
@@ -15,7 +15,6 @@ import {
} from '../../lib/hooks/use-team';
import { useT } from '../../lib/use-t';
import { DispatchMetricLabels, type DispatchMetric } from '@vegaprotocol/types';
import classNames from 'classnames';
export const TeamStats = ({
stats,
@@ -103,13 +102,7 @@ const LatestResults = ({ games }: { games: TeamGame[] }) => {
);
};
export const FavoriteGame = ({
games,
noLabel = false,
}: {
games: TeamGame[];
noLabel?: boolean;
}) => {
const FavoriteGame = ({ games }: { games: TeamGame[] }) => {
const t = useT();
const rewardMetrics = games.map(
@@ -135,13 +128,7 @@ export const FavoriteGame = ({
return (
<dl className="flex flex-col gap-1">
<dt
className={classNames('text-muted text-sm', {
hidden: noLabel,
})}
>
{t('Favorite game')}
</dt>
<dt className="text-muted text-sm">{t('Favorite game')}</dt>
<dd>
<Pill className="inline-flex items-center gap-1 bg-transparent text-sm">
<VegaIcon
@@ -155,7 +142,7 @@ export const FavoriteGame = ({
);
};
export const StatSection = ({ children }: { children: ReactNode }) => {
const StatSection = ({ children }: { children: ReactNode }) => {
return (
<section className="flex flex-col lg:flex-row gap-4 lg:gap-8">
{children}
@@ -163,11 +150,11 @@ export const StatSection = ({ children }: { children: ReactNode }) => {
);
};
export const StatSectionSeparator = () => {
const StatSectionSeparator = () => {
return <div className="hidden md:block border-r border-default" />;
};
export const StatList = ({ children }: { children: ReactNode }) => {
const StatList = ({ children }: { children: ReactNode }) => {
return (
<dl className="grid grid-cols-2 md:flex gap-4 md:gap-6 lg:gap-8 whitespace-nowrap">
{children}
@@ -175,21 +162,19 @@ export const StatList = ({ children }: { children: ReactNode }) => {
);
};
export const Stat = ({
const Stat = ({
value,
label,
tooltip,
valueTestId,
className,
}: {
value: ReactNode;
label: ReactNode;
tooltip?: string;
valueTestId?: string;
className?: classNames.Argument;
}) => {
return (
<div className={classNames(className)}>
<div>
<dd className="text-3xl lg:text-4xl" data-testid={valueTestId}>
{value}
</dd>
@@ -152,11 +152,7 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => {
if (!enrichedTransfers || !enrichedTransfers.length) return null;
return (
<Card
title={t('Active rewards')}
className="lg:col-span-full"
data-testid="active-rewards-card"
>
<Card title={t('Active rewards')} className="lg:col-span-full">
{enrichedTransfers.length > 1 && (
<TradingInput
onChange={(e) =>
@@ -375,7 +371,6 @@ export const ActiveRewardCard = ({
'rounded-lg',
gradientClassName
)}
data-testid="active-rewards-card"
>
<div
className={classNames(
@@ -387,7 +382,7 @@ export const ActiveRewardCard = ({
<div className="flex flex-col gap-2 items-center text-center">
<EntityIcon transfer={transfer} />
{entityScope && (
<span className="text-muted text-xs" data-testid="entity-scope">
<span className="text-muted text-xs">
{EntityScopeLabelMapping[entityScope] || t('Unspecified')}
</span>
)}
@@ -395,7 +390,7 @@ export const ActiveRewardCard = ({
<div className="flex flex-col gap-2 items-center text-center">
<h3 className="flex flex-col gap-1 text-2xl shrink-1 text-center">
<span className="font-glitch" data-testid="reward-value">
<span className="font-glitch">
{addDecimalsFormatNumber(
transferNode.transfer.amount,
transferNode.transfer.asset?.decimals || 0,
@@ -416,7 +411,7 @@ export const ActiveRewardCard = ({
)}
underline={true}
>
<span className="text-xs" data-testid="distribution-strategy">
<span className="text-xs">
{
DistributionStrategyMapping[
dispatchStrategy.distributionStrategy
@@ -434,10 +429,7 @@ export const ActiveRewardCard = ({
'Number of epochs after distribution to delay vesting of rewards by'
)}
/>
<span
className="text-muted text-xs whitespace-nowrap"
data-testid="locked-for"
>
<span className="text-muted text-xs whitespace-nowrap">
{t('numberEpochs', '{{count}} epochs', {
count: kind.dispatchStrategy?.lockPeriod,
})}
@@ -446,7 +438,7 @@ export const ActiveRewardCard = ({
</div>
<span className="border-[0.5px] border-gray-700" />
<span data-testid="dispatch-metric-info">
<span>
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]} {' '}
<Tooltip
underline={suspended}
@@ -466,8 +458,8 @@ export const ActiveRewardCard = ({
<div className="flex items-center gap-8 flex-wrap">
{kind.endEpoch && (
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Ends in')} </span>
<span data-testid="ends-in">
<span className="text-muted text-xs">{t('Ends in')}</span>
<span>
{t('numberEpochs', '{{count}} epochs', {
count: kind.endEpoch - currentEpoch,
})}
@@ -478,7 +470,7 @@ export const ActiveRewardCard = ({
{
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Assessed over')}</span>
<span data-testid="assessed-over">
<span>
{t('numberEpochs', '{{count}} epochs', {
count: dispatchStrategy.windowLength,
})}
@@ -521,7 +513,7 @@ const RewardRequirements = ({
entity: EntityScopeLabelMapping[dispatchStrategy.entityScope],
})}
</dt>
<dd className="flex items-center gap-1" data-testid="scope">
<dd className="flex items-center gap-1">
<RewardEntityScope dispatchStrategy={dispatchStrategy} />
</dd>
</div>
@@ -530,10 +522,7 @@ const RewardRequirements = ({
<dt className="flex items-center gap-1 text-muted">
{t('Staked VEGA')}
</dt>
<dd
className="flex items-center gap-1"
data-testid="staking-requirement"
>
<dd className="flex items-center gap-1">
{addDecimalsFormatNumber(
dispatchStrategy?.stakingRequirement || 0,
assetDecimalPlaces
@@ -545,7 +534,7 @@ const RewardRequirements = ({
<dt className="flex items-center gap-1 text-muted">
{t('Average position')}
</dt>
<dd className="flex items-center gap-1" data-testid="average-position">
<dd className="flex items-center gap-1">
{addDecimalsFormatNumber(
dispatchStrategy?.notionalTimeWeightedAveragePositionRequirement ||
0,
@@ -59,7 +59,7 @@ def test_filtered_cards(continuous_market, vega: VegaServiceNull, page: Page):
next_epoch(vega=vega)
page.reload()
expect(page.get_by_test_id("active-rewards-card")).to_be_visible(timeout=15000)
expect(page.locator(".from-vega-cdark-400")).to_be_visible(timeout=15000)
governance.submit_oracle_data(
wallet=vega.wallet,
payload={"trading.terminated": "true"},
@@ -67,4 +67,4 @@ def test_filtered_cards(continuous_market, vega: VegaServiceNull, page: Page):
)
next_epoch(vega=vega)
page.reload()
expect(page.get_by_test_id("active-rewards-card")).not_to_be_in_viewport()
expect(page.locator(".from-vega-cdark-400")).not_to_be_in_viewport()
+41 -151
View File
@@ -3,7 +3,7 @@ from playwright.sync_api import expect, Page
import vega_sim.proto.vega as vega_protos
from vega_sim.null_service import VegaServiceNull
from conftest import init_vega
from actions.utils import next_epoch, change_keys
from actions.utils import next_epoch
from fixtures.market import setup_continuous_market
from conftest import auth_setup, init_page, init_vega, risk_accepted_setup
from wallet_config import PARTY_A, PARTY_B, PARTY_C, PARTY_D, MM_WALLET
@@ -14,7 +14,6 @@ def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def team_page(vega, browser, request, setup_teams_and_games):
with init_page(vega, browser, request) as page:
@@ -24,19 +23,9 @@ def team_page(vega, browser, request, setup_teams_and_games):
page.goto(f"/#/competitions/teams/{team_id}")
yield page
@pytest.fixture(scope="module")
def competitions_page(vega, browser, request, setup_teams_and_games):
with init_page(vega, browser, request) as page:
risk_accepted_setup(page)
auth_setup(vega, page)
team_id = setup_teams_and_games["team_id"]
page.goto(f"/#/competitions/")
yield page
@pytest.fixture(scope="module")
def setup_teams_and_games(vega: VegaServiceNull):
tDAI_market = setup_continuous_market(vega, custom_quantum=100000)
tDAI_market = setup_continuous_market(vega)
tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
vega.mint(key_name=PARTY_B.name, asset=tDAI_asset_id, amount=100000)
vega.mint(key_name=PARTY_C.name, asset=tDAI_asset_id, amount=100000)
@@ -57,18 +46,6 @@ def setup_teams_and_games(vega: VegaServiceNull):
# list_teams actually returns a dictionary {"team_id": Team}
team_id = list(teams.keys())[0]
vega.create_referral_set(
key_name="market_maker",
name="test",
team_url="https://vega.xyz",
avatar_url="http://placekitten.com/200/200",
closed=False,
)
next_epoch(vega)
teams = vega.list_teams()
team_id_2 = list(teams.keys())[0]
vega.apply_referral_code("Key 1", team_id_2)
vega.apply_referral_code(PARTY_B.name, team_id)
@@ -86,7 +63,7 @@ def setup_teams_and_games(vega: VegaServiceNull):
current_epoch = vega.statistics().epoch_seq
game_start = current_epoch + 1
game_end = current_epoch + 14
game_end = current_epoch + 11
current_epoch = vega.statistics().epoch_seq
print(f"[EPOCH: {current_epoch}] creating recurring transfer")
@@ -107,42 +84,9 @@ def setup_teams_and_games(vega: VegaServiceNull):
factor=1.0,
start_epoch=game_start,
end_epoch=game_end,
window_length=15,
)
vega.wait_fn(1)
vega.wait_for_total_catchup()
vega.recurring_transfer(
from_key_name=PARTY_B.name,
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
asset=tDAI_asset_id,
reference="reward",
asset_for_metric=tDAI_asset_id,
metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID,
entity_scope=vega_protos.vega.ENTITY_SCOPE_INDIVIDUALS,
individual_scope=vega_protos.vega.INDIVIDUAL_SCOPE_IN_TEAM,
n_top_performers=1,
amount=100,
factor=1.0,
window_length=15
)
vega.wait_fn(1)
vega.wait_for_total_catchup()
vega.recurring_transfer(
from_key_name=PARTY_C.name,
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
asset=tDAI_asset_id,
reference="reward",
asset_for_metric=tDAI_asset_id,
metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID,
entity_scope=vega_protos.vega.ENTITY_SCOPE_INDIVIDUALS,
individual_scope=vega_protos.vega.INDIVIDUAL_SCOPE_NOT_IN_TEAM,
n_top_performers=1,
amount=100,
factor=1.0,
window_length=15
window_length=10
)
next_epoch(vega)
print(f"[EPOCH: {vega.statistics().epoch_seq}] starting order activity")
@@ -169,22 +113,6 @@ def setup_teams_and_games(vega: VegaServiceNull):
side="SIDE_BUY",
volume=1,
)
vega.submit_order(
trading_key="Key 1",
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.submit_order(
trading_key="market_maker",
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
next_epoch(vega)
print(f"[EPOCH: {vega.statistics().epoch_seq}] {i} epoch passed")
@@ -192,7 +120,6 @@ def setup_teams_and_games(vega: VegaServiceNull):
"market_id": tDAI_market,
"asset_id": tDAI_asset_id,
"team_id": team_id,
"team_id_2": team_id_2,
"team_name": team_name,
}
@@ -209,103 +136,66 @@ def create_team(vega: VegaServiceNull):
return team_name
def test_team_page_games_table(team_page: Page):
team_page.pause()
team_page.get_by_test_id("games-toggle").click()
expect(team_page.get_by_test_id("games-toggle")).to_have_text("Games (1)")
expect(team_page.get_by_test_id("rank-0")).to_have_text("2")
expect(team_page.get_by_test_id("epoch-0")).to_have_text("19")
expect(team_page.get_by_test_id("rank-0")).to_have_text("1")
expect(team_page.get_by_test_id("epoch-0")).to_have_text("18")
expect(team_page.get_by_test_id("type-0")).to_have_text("Price maker fees paid")
expect(team_page.get_by_test_id("amount-0")).to_have_text("74")
expect(team_page.get_by_test_id("participatingTeams-0")).to_have_text("2")
expect(team_page.get_by_test_id("participatingMembers-0")).to_have_text("4")
expect(team_page.get_by_test_id("amount-0")).to_have_text("100,000,000")
expect(team_page.get_by_test_id("participatingTeams-0")).to_have_text(
"1"
)
expect(team_page.get_by_test_id("participatingMembers-0")).to_have_text(
"2"
)
def test_team_page_members_table(team_page: Page):
team_page.get_by_test_id("members-toggle").click()
expect(team_page.get_by_test_id("members-toggle")).to_have_text("Members (4)")
expect(team_page.get_by_test_id("referee-0")).to_be_visible()
expect(team_page.get_by_test_id("joinedAt-0")).to_be_visible()
expect(team_page.get_by_test_id("joinedAtEpoch-0")).to_have_text("9")
expect(team_page.get_by_test_id("joinedAtEpoch-0")).to_have_text("8")
def test_team_page_headline(team_page: Page, setup_teams_and_games):
def test_team_page_headline(team_page: Page, setup_teams_and_games
):
team_name = setup_teams_and_games["team_name"]
expect(team_page.get_by_test_id("team-name")).to_have_text(team_name)
expect(team_page.get_by_test_id("members-count-stat")).to_have_text("4")
expect(team_page.get_by_test_id("total-games-stat")).to_have_text("2")
expect(team_page.get_by_test_id("total-games-stat")).to_have_text(
"1"
)
# TODO this still seems wrong as its always 0
expect(team_page.get_by_test_id("total-volume-stat")).to_have_text("0")
expect(team_page.get_by_test_id("total-volume-stat")).to_have_text(
"0"
)
expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text("214")
def test_switch_teams(team_page: Page, vega: VegaServiceNull):
team_page.get_by_test_id("switch-team-button").click()
team_page.get_by_test_id("confirm-switch-button").click()
expect(team_page.get_by_test_id("dialog-content").first).to_be_visible()
vega.wait_fn(1)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
team_page.reload()
expect(team_page.get_by_test_id("members-count-stat")).to_have_text("5")
expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text(
"100m"
)
@pytest.fixture(scope="module")
def competitions_page(vega, browser, request):
with init_page(vega, browser, request) as page:
risk_accepted_setup(page)
auth_setup(vega, page)
yield page
def test_leaderboard(competitions_page: Page, setup_teams_and_games):
team_name = setup_teams_and_games["team_name"]
competitions_page.reload()
expect(
competitions_page.get_by_test_id("rank-0").locator(".text-yellow-300")
).to_have_count(1)
expect(
competitions_page.get_by_test_id("rank-1").locator(".text-vega-clight-500")
).to_have_count(1)
expect(competitions_page.get_by_test_id("team-1")).to_have_text(team_name)
expect(competitions_page.get_by_test_id("status-1")).to_have_text("Open")
competitions_page.goto(f"/#/competitions/")
expect(competitions_page.get_by_test_id("rank-0").locator(".text-yellow-300")).to_have_count(1)
expect(competitions_page.get_by_test_id("team-0")).to_have_text(team_name)
expect(competitions_page.get_by_test_id("status-0")).to_have_text("Open")
expect(competitions_page.get_by_test_id("earned-1")).to_have_text("160")
expect(competitions_page.get_by_test_id("games-1")).to_have_text("2")
expect(competitions_page.get_by_test_id("earned-0")).to_have_text("100,000,000")
expect(competitions_page.get_by_test_id("games-0")).to_have_text("1")
# TODO still odd that this is 0
expect(competitions_page.get_by_test_id("volume-0")).to_have_text("-")
def test_game_card(competitions_page: Page):
expect(competitions_page.get_by_test_id("active-rewards-card")).to_have_count(2)
game_1 = competitions_page.get_by_test_id("active-rewards-card").first
expect(game_1).to_be_visible()
expect(game_1.get_by_test_id("entity-scope")).to_have_text("Individual")
expect(game_1.get_by_test_id("locked-for")).to_have_text("1 epoch")
expect(game_1.get_by_test_id("reward-value")).to_have_text("100.00")
expect(game_1.get_by_test_id("distribution-strategy")).to_have_text("Pro rata")
expect(game_1.get_by_test_id("dispatch-metric-info")).to_have_text("Price maker fees paid • ")
expect(game_1.get_by_test_id("assessed-over")).to_have_text("15 epochs")
expect(game_1.get_by_test_id("scope")).to_have_text("In team")
expect(game_1.get_by_test_id("staking-requirement")).to_have_text("0.00")
expect(game_1.get_by_test_id("average-position")).to_have_text("0.00")
def test_create_team(competitions_page: Page, vega: VegaServiceNull):
change_keys(competitions_page, vega, "market_maker_2")
competitions_page.get_by_test_id("create-public-team-button").click()
competitions_page.get_by_test_id("team-name-input").fill("e2e")
competitions_page.get_by_test_id("team-url-input").fill("https://vega.xyz")
competitions_page.get_by_test_id("avatar-url-input").fill(
"http://placekitten.com/200/200"
)
competitions_page.get_by_test_id("team-form-submit-button").click()
expect(competitions_page.get_by_test_id("team-form-submit-button")).to_have_text(
"Confirming transaction..."
)
vega.wait_fn(2)
vega.wait_for_total_catchup()
expect(
competitions_page.get_by_test_id("team-creation-success-message")
).to_be_visible()
expect(competitions_page.get_by_test_id("team-id-display")).to_be_visible()
expect(competitions_page.get_by_test_id("team-id-display")).to_be_visible()
competitions_page.get_by_test_id("view-team-button").click()
expect(competitions_page.get_by_test_id("team-name")).to_have_text("e2e")
#TODO def test_games(competitions_page: Page):
#TODO currently no games appear which i think is a bug
+8 -13
View File
@@ -1,20 +1,15 @@
fragment TeamsFields on Team {
teamId
referrer
name
teamUrl
avatarUrl
createdAt
createdAtEpoch
closed
totalMembers
}
query Teams($teamId: ID, $partyId: ID) {
teams(teamId: $teamId, partyId: $partyId) {
edges {
node {
...TeamsFields
teamId
referrer
name
teamUrl
avatarUrl
createdAt
createdAtEpoch
closed
}
}
}
+11 -18
View File
@@ -3,40 +3,33 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type TeamsFieldsFragment = { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, totalMembers: number };
export type TeamsQueryVariables = Types.Exact<{
teamId?: Types.InputMaybe<Types.Scalars['ID']>;
partyId?: Types.InputMaybe<Types.Scalars['ID']>;
}>;
export type TeamsQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, totalMembers: number } }> } | null };
export type TeamsQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean } }> } | null };
export const TeamsFieldsFragmentDoc = gql`
fragment TeamsFields on Team {
teamId
referrer
name
teamUrl
avatarUrl
createdAt
createdAtEpoch
closed
totalMembers
}
`;
export const TeamsDocument = gql`
query Teams($teamId: ID, $partyId: ID) {
teams(teamId: $teamId, partyId: $partyId) {
edges {
node {
...TeamsFields
teamId
referrer
name
teamUrl
avatarUrl
createdAt
createdAtEpoch
closed
}
}
}
}
${TeamsFieldsFragmentDoc}`;
`;
/**
* __useTeamsQuery__
-25
View File
@@ -1,25 +0,0 @@
import { useVegaWallet } from '@vegaprotocol/wallet';
import compact from 'lodash/compact';
import first from 'lodash/first';
import { useTeamsQuery } from './__generated__/Teams';
import { useTeam } from './use-team';
import { useTeams } from './use-teams';
export const useMyTeam = () => {
const { pubKey } = useVegaWallet();
const { data: teams } = useTeams();
const { data: maybeMyTeam } = useTeamsQuery({
variables: {
partyId: pubKey,
},
skip: !pubKey,
fetchPolicy: 'cache-and-network',
});
const team = first(compact(maybeMyTeam?.teams?.edges.map((n) => n.node)));
const rank = teams.findIndex((t) => t.teamId === team?.teamId) + 1;
const { games, stats } = useTeam(team?.teamId);
return { team, stats, games, rank };
};
+1 -4
View File
@@ -33,7 +33,6 @@ import { PartyActiveOrdersHandler } from './party-active-orders-handler';
import { MaybeConnectEagerly } from './maybe-connect-eagerly';
import { TransactionHandlers } from './transaction-handlers';
import { useT } from '../lib/use-t';
import { Test } from './test/test';
const Title = () => {
const t = useT();
@@ -136,6 +135,4 @@ function VegaTradingApp(props: AppProps) {
);
}
export default function () {
return <Test />;
}
export default VegaTradingApp;
-17
View File
@@ -1,17 +0,0 @@
export type Chain = {
id: string;
testnet: boolean;
name: string;
};
export const fairground = {
id: 'vega-fairground-202305051805',
testnet: true,
name: 'Fairground',
};
export const stagnet = {
id: 'vega-stagnet1-202307191148',
testnet: true,
name: 'Stagnet',
};
-47
View File
@@ -1,47 +0,0 @@
import {
type Transaction,
type TransactionResponse,
} from '@vegaprotocol/wallet';
export enum JsonRpcMethod {
ConnectWallet = 'client.connect_wallet',
DisconnectWallet = 'client.disconnect_wallet',
ListKeys = 'client.list_keys',
SignTransaction = 'client.sign_transaction',
SendTransaction = 'client.send_transaction',
GetChainId = 'client.get_chain_id',
}
export interface IWalletError {
error: string;
}
export interface TransactionParams {
publicKey: string;
transaction: Transaction;
sendingMode: 'TYPE_SYNC';
}
export interface Connector {
id: string;
connectWallet(chainId: string): Promise<{ success: boolean } | IWalletError>;
disconnectWallet(): Promise<{ success: boolean } | IWalletError>;
getChainId(): Promise<{ chainId: string } | IWalletError>;
listKeys(): Promise<
Array<{ publicKey: string; name: string }> | IWalletError
>;
isConnected(): Promise<{ connected: boolean } | IWalletError>;
sendTransaction(
params: TransactionParams
): Promise<TransactionResponse | IWalletError>;
on(event: string, callback: () => void): void;
off(event: string, callback: () => void): void;
}
@@ -1,75 +0,0 @@
import { TransactionParams, type Connector } from '.';
export class InjectedConnector implements Connector {
id = 'injected';
async connectWallet(chainId: string) {
try {
await window.vega.connectWallet({ chainId });
return { success: true };
} catch (err) {
return { error: 'failed to connect' };
}
}
async disconnectWallet() {
try {
await window.vega.disconnectWallet();
return { success: true };
} catch (err) {
return { error: 'failed to disconnect' };
}
}
// deprecated, pass chain on connect
async getChainId() {
try {
const res = await window.vega.getChainId();
return { chainId: res.chainID };
} catch (err) {
return { error: 'failed to get chain id' };
}
}
async listKeys() {
try {
const res = await window.vega.listKeys();
return res.keys;
} catch (err) {
return { error: 'failed to list keys' };
}
}
async isConnected() {
try {
const res = await window.vega.isConnected();
return { connected: res };
} catch (err) {
return { error: 'failed to check isConnected' };
}
}
async sendTransaction(params: TransactionParams) {
try {
const res = await window.vega.sendTransaction(params);
return {
transactionHash: res.transactionHash,
signature: res.transaction.signature.value,
receivedAt: res.receivedAt,
sentAt: res.sentAt,
};
} catch (err) {
console.error(err);
return { error: 'failed to send transaction' };
}
}
on(event: 'client.disconnected', callback: () => void) {
window.vega.on(event, callback);
}
off(event: 'client.disconnected') {
window.vega.off(event);
}
}
@@ -1,126 +0,0 @@
import { JsonRpcMethod, type Connector, type TransactionParams } from '.';
type JsonRpcConnectorConfig = { url: string };
export class JsonRpcConnector implements Connector {
id = 'json-rpc';
config: JsonRpcConnectorConfig;
requestId: number = 0;
token: string | null = null;
constructor(config: JsonRpcConnectorConfig) {
this.config = config;
}
async connectWallet(desiredChainId: string) {
try {
const { chainId } = await this.getChainId();
if (chainId !== desiredChainId) {
throw new Error('incorrect chain id');
}
const { response } = await this.request(JsonRpcMethod.ConnectWallet, {
hostname: window.location.hostname,
});
const token = response.headers.get('Authorization');
if (response.ok && token) {
this.token = token;
return { success: true };
}
return { error: 'failed to connect' };
} catch (err) {
return { error: 'failed to connect' };
}
}
async disconnectWallet() {
try {
await this.request(JsonRpcMethod.DisconnectWallet);
return { success: true };
} catch (err) {
return { error: 'failed to disconnect' };
}
}
// deprecated, pass chain on connect
async getChainId() {
try {
const { data } = await this.request(JsonRpcMethod.GetChainId);
return { chainId: data.result.chainID };
} catch (err) {
return { error: 'failed to get chain id' };
}
}
async listKeys() {
try {
const { data } = await this.request(JsonRpcMethod.ListKeys);
return data.result.keys as Array<{ publicKey: string; name: string }>;
} catch (err) {
return { error: 'failed to list keys' };
}
}
async isConnected() {
try {
await this.listKeys();
return { connected: true };
} catch (err) {
return { error: 'failed to check isConnected' };
}
}
async sendTransaction(params: TransactionParams) {
try {
const { data } = await this.request(
JsonRpcMethod.SendTransaction,
params
);
return {
transactionHash: data.result.transactionHash,
signature: data.result.transaction.signature.value,
receivedAt: data.result.receivedAt,
sentAt: data.result.sentAt,
};
} catch (err) {
return { error: 'failed to send transaction' };
}
}
private async request(method: JsonRpcMethod, params?: any) {
const headers = new Headers();
if (this.token) {
headers.set('Authorization', this.token);
}
const response = await fetch(this.config.url, {
method: 'POST',
headers,
body: JSON.stringify({
id: `${this.requestId++}`,
jsonrpc: '2.0',
method,
params,
}),
});
const data = await response.json();
return {
data,
response,
};
}
on() {
console.warn('events are not supported in json rpc wallet');
}
off() {
console.warn('events are not supported in json rpc wallet');
}
}
-141
View File
@@ -1,141 +0,0 @@
import { JsonRpcMethod, type Connector, type TransactionParams } from '.';
enum EthereumMethod {
RequestSnaps = 'wallet_requestSnaps',
GetSnaps = 'wallet_getSnaps',
InvokeSnap = 'wallet_invokeSnap',
}
type SnapConfig = {
id: string;
node: string;
version: string;
};
export class SnapConnector implements Connector {
id = 'snap';
config: SnapConfig;
constructor(config: SnapConfig) {
this.config = config;
}
async connectWallet(desiredChainId: string) {
try {
await this.requestSnap();
const { chainId } = await this.getChainId();
if (chainId !== desiredChainId) {
throw new Error('incorrect chain id');
}
return { success: true };
} catch (err) {
return { error: 'failed to connect' };
}
}
async disconnectWallet() {
console.warn('disconnectWallet not implemented');
return { error: 'failed to disconnect' };
}
// deprecated, pass chain on connect
async getChainId() {
try {
const res = await this.invokeSnap(JsonRpcMethod.GetChainId, {
networkEndpoints: [this.config.node],
});
return { chainId: res.chainID };
} catch (err) {
return { error: 'failed to get chain id' };
}
}
async listKeys() {
try {
const res = await this.invokeSnap(JsonRpcMethod.ListKeys);
return res.keys as Array<{ publicKey: string; name: string }>;
} catch (err) {
return { error: 'failed to list keys' };
}
}
async isConnected() {
console.warn('isConnected not implemented');
return { error: 'failed to check if connected' };
}
async sendTransaction(params: TransactionParams) {
try {
const res = await this.invokeSnap(JsonRpcMethod.SendTransaction, {
publicKey: params.publicKey,
sendingMode: params.sendingMode,
transaction: params.transaction,
networkEndpoints: [this.config.node],
});
return {
transactionHash: res.transactionHash,
signature: res.transaction.signature.value,
receivedAt: res.receivedAt,
sentAt: res.sentAt,
};
} catch (err) {
console.error(err);
return { error: 'failed to send transaction' };
}
}
////////////////////////////////////
// Snap methods
////////////////////////////////////
async requestSnap() {
const res = await this.request(EthereumMethod.RequestSnaps, {
[this.config.id]: {
version: this.config.version,
},
});
console.log(res);
}
async getSnap() {
const snaps = await this.request(EthereumMethod.GetSnaps);
return Object.values(snaps).find(
(s) => s.id === this.config.id && s.version === this.config.version
);
}
async invokeSnap(method: JsonRpcMethod, params?: any) {
return await this.request(EthereumMethod.InvokeSnap, {
snapId: this.config.id,
request: {
method: method,
params,
},
});
}
async request(method: EthereumMethod, params?: any) {
if (!window.ethereum) {
throw new Error('no window.ethereum');
}
// @ts-ignore ethereum types a bit mangled from web3-react
return window.ethereum.request({
method,
params,
});
}
on() {
console.warn('events are not supported in json rpc wallet');
}
off() {
console.warn('events are not supported in json rpc wallet');
}
}
-333
View File
@@ -1,333 +0,0 @@
import { StoreApi, createStore } from 'zustand/vanilla';
import { useStore } from 'zustand';
import { InjectedConnector } from './injected-connector';
import { JsonRpcConnector } from './json-rpc-connector';
import { SnapConnector } from './snap-connector';
import { Chain, fairground, stagnet } from './chains';
import { Connector, TransactionParams } from '.';
import {
PropsWithChildren,
createContext,
createElement,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import { TransactionResponse } from '@vegaprotocol/wallet';
import { AccountType } from '@vegaprotocol/types';
const injected = new InjectedConnector();
const jsonRpc = new JsonRpcConnector({
url: 'http://localhost:1789/api/v2/requests',
});
const snap = new SnapConnector({
node: 'https://api.n08.testnet.vega.rocks',
id: 'npm:@vegaprotocol/snap',
version: '0.3.1',
});
type Key = {
publicKey: string;
name: string;
};
type Store = {
chainId: string;
status: 'disconnected' | 'connecting' | 'connected';
current: string | undefined;
keys: Key[];
setKeys: (keys: Key[]) => void;
};
type Config = { chains: Chain[]; connectors: Connector[] };
type Wallet = {
store: StoreApi<Store>;
connectors: Connector[];
connect: (id: string) => Promise<void>;
disconnect: () => Promise<void>;
sendTransaction: (params: TransactionParams) => Promise<TransactionResponse>;
};
function createConfig(cfg: Config): Wallet {
const connectors = createStore(() => cfg.connectors);
const store = createStore<Store>((set) => ({
chainId: cfg.chains[0].id,
status: 'disconnected',
current: undefined,
keys: [],
setKeys: (keys) => {
set({ keys });
},
}));
async function connect(id: string) {
if (store.getState().status === 'connecting') {
return;
}
const connector = connectors.getState().find((x) => x.id === id);
if (!connector) return;
try {
store.setState({ status: 'connecting', current: id });
const connectWalletRes = await connector.connectWallet(
store.getState().chainId
);
if ('error' in connectWalletRes) {
throw new Error('failed to connect');
}
const listKeysRes = await connector.listKeys();
if ('error' in listKeysRes) {
throw new Error('failed to get keys');
}
store.setState({
keys: listKeysRes,
status: 'connected',
});
} catch (err) {
console.error(err);
store.setState({ status: 'disconnected', current: undefined, keys: [] });
}
}
async function disconnect() {
const connector = connectors
.getState()
.find((x) => x.id === store.getState().current);
if (!connector) return;
store.setState({ status: 'disconnected', current: undefined, keys: [] });
}
async function sendTransaction(params: TransactionParams) {
const connector = connectors
.getState()
.find((x) => x.id === store.getState().current);
if (!connector) return;
try {
const res = await connector.sendTransaction(params);
return res;
console.log('res', res);
} catch (err) {
return {
error: 'failed to send',
};
console.error(err);
}
}
return {
store,
connect,
disconnect,
sendTransaction,
get connectors() {
return connectors.getState();
},
};
}
const config = createConfig({
chains: [fairground, stagnet],
connectors: [injected, jsonRpc, snap],
});
function useWallet<T>(selector: (store: Store) => T) {
const store = useStore(config.store, selector);
return store;
}
function useConfig() {
const context = useContext(VegaWalletContext);
if (context === undefined) {
throw new Error('must be used within VegaWalletProvider');
}
return context;
}
function useConnect() {
const config = useConfig();
return {
connectors: config.connectors,
connect: config.connect,
};
}
function useDisconnect() {
const config = useConfig();
return {
disconnect: config.disconnect,
};
}
function useSendTransaction() {
const config = useConfig();
return {
sendTransaction: config.sendTransaction,
};
}
const VegaWalletContext = createContext<Wallet | undefined>(undefined);
function VegaWalletProvider({
children,
config,
}: PropsWithChildren<{ config: Wallet }>) {
return createElement(VegaWalletContext.Provider, { value: config }, children);
}
export function Test() {
return (
<VegaWalletProvider config={config}>
<App />
</VegaWalletProvider>
);
}
function App() {
const [open, setOpen] = useState(false);
const { connectors, connect } = useConnect();
const { disconnect } = useDisconnect();
const { sendTransaction } = useSendTransaction();
const status = useWallet((store) => store.status);
return (
<div>
<button onClick={() => setOpen(true)}>Connect</button>
<Dialog open={open}>
<div>
{status === 'disconnected' && (
<div className="flex gap-2">
{connectors.map((c) => (
<button
key={c.id}
onClick={async () => {
try {
await connect(c.id);
setTimeout(() => {
setOpen(false);
}, 1000);
} catch (err) {}
}}
>
connect {c.id}
</button>
))}
</div>
)}
{status === 'connecting' && <div>Approve connection in wallet</div>}
{status === 'connected' && <div>Connected!</div>}
</div>
</Dialog>
<hr className="border-t pt-2 mt-2" />
<div className="flex flex-col items-start gap-2">
<button onClick={() => disconnect()}>diconnectWallet</button>
<button
onClick={async () => {
const fromKey =
'846b2ed2fff0ddf219a5302a3856f9ac279652c4d92e72e7df762117886e69cc'; // snap
const toKey =
'5a703397f6ac1f62513af1f5d0237d1b9f8b2c93ef19fbe9fa87771a2b7f55da'; // browser
const res = await sendTransaction({
publicKey: fromKey,
sendingMode: 'TYPE_SYNC',
transaction: {
transfer: {
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: toKey,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
asset:
'16ae5dbb1fd7aa2ddef725703bfe66b3647a4da7b844bfdd04e985756f53d9d6',
amount: '100',
oneOff: {},
},
},
});
console.log(res);
}}
>
submit transfer
</button>
</div>
<div>{status}</div>
<Keys />
{/* <NoReact /> */}
</div>
);
}
const Keys = () => {
const keys = useWallet((store) => store.keys);
if (!keys.length) {
return <p>No keys</p>;
}
return (
<ul>
{keys.map((k) => {
return (
<li key={k.publicKey}>
{k.name} {k.publicKey}
</li>
);
})}
</ul>
);
};
const NoReact = () => {
const divRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!divRef.current) return;
const div = divRef.current;
const button = document.createElement('button');
button.addEventListener('click', () => {
config.connect('injected'); // should fail
});
button.textContent = 'Connect injected';
const status = document.createElement('div');
const keys = document.createElement('ul');
config.store.subscribe((state) => {
status.textContent = state.status;
state.keys.forEach((key) => {
const li = document.createElement('li');
li.textContent = `${key.name} ${key.publicKey}`;
keys.appendChild(li);
});
});
div.appendChild(button);
div.appendChild(status);
div.appendChild(keys);
}, []);
return (
<div>
<h1>No react</h1>
<div ref={divRef} />
</div>
);
};
-65
View File
@@ -1,65 +0,0 @@
import { InjectedConnector } from './injected-connector';
import { JsonRpcConnector } from './json-rpc-connector';
import { SnapConnector } from './snap-connector';
export class Wallet {
connector?: InjectedConnector | JsonRpcConnector | SnapConnector;
async connectWallet(
connector: InjectedConnector | JsonRpcConnector | SnapConnector
) {
this.connector = connector;
const res = await this.connector.connectWallet();
if (res.success !== true) {
// TODO: something better
return;
}
const keys = await this.connector.listKeys();
console.log(keys);
return keys;
}
async disconnectWallet() {
if (!this.connector) {
throw new Error('no connector');
}
await this.connector.disconnectWallet();
this.connector = undefined;
}
async listKeys() {
if (!this.connector) {
throw new Error('no connector');
}
return this.connector.listKeys();
}
async getChainId() {
if (!this.connector) {
throw new Error('no connector');
}
return this.connector.getChainId();
}
async isConnected() {
if (!this.connector) {
throw new Error('no connector');
}
return this.connector.isConnected();
}
// sendTransaction(...args: Parameters<typeof window.vega.sendTransaction>) {
// if (!this.connector) {
// throw new Error('no connector');
// }
//
// return this.connector.sendTransaction(...args);
// }
}
+1 -16
View File
@@ -417,7 +417,6 @@
"myVolume_other": "My volume (last {{count}} epochs)",
"numberEpochs": "{{count}} epochs",
"numberEpochs_one": "{{count}} epoch",
"Rewards earned": "Rewards earned",
"Rewards paid out": "Rewards paid out",
"{{reward}}x": "{{reward}}x",
"userActive": "{{active}} trader: {{count}} epochs so far",
@@ -432,19 +431,5 @@
"{{assetSymbol}} Reward pot": "{{assetSymbol}} Reward pot",
"{{checkedAssets}} Assets": "{{checkedAssets}} Assets",
"{{distance}} ago": "{{distance}} ago",
"{{instrumentCode}} liquidity provision": "{{instrumentCode}} liquidity provision",
"My team": "My team",
"Profile": "Profile",
"Last {{games}} games result_one": "Last game result",
"Last {{games}} games result_other": "Last {{games}} games result",
"Leaderboard": "Leaderboard",
"View all teams": "View all teams",
"Competitions": "Competitions",
"Be a team player! Participate in games and work together to rake in as much profit to win.": "Be a team player! Participate in games and work together to rake in as much profit to win.",
"Create a public team": "Create a public team",
"Create a private team": "Create a private team",
"Choose a team": "Choose a team",
"Join a team": "Join a team",
"Solo team / lone wolf": "Solo team / lone wolf",
"Choose a team to get involved": "Choose a team to get involved"
"{{instrumentCode}} liquidity provision": "{{instrumentCode}} liquidity provision"
}
@@ -37,9 +37,7 @@ declare global {
}>;
on: (event: VegaWalletEvent, callback: () => void) => void;
off: (event: VegaWalletEvent) => void;
isConnected: () => Promise<boolean>;
getChainId: () => Promise<{ chainID: string }>;
isConnected?: () => Promise<boolean>;
}
interface Window {