Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6df8ef8
feat: add multi-step tracker for EOA TWAP order creation flow
Danziger Jul 28, 2026
799edf8
Merge branch 'develop' into feat/eoa-twap-order-tracker
Danziger Jul 28, 2026
2362af0
chore(i18n): extract i18n strings [automatic]
cowswap-release-sync[bot] Jul 28, 2026
e53d630
chore: update bundle sizes [automatic]
cowswap-release-sync[bot] Jul 28, 2026
9780b1c
chore: update bundle sizes [automatic]
cowswap-release-sync[bot] Jul 28, 2026
425e295
chore: update bundle sizes [automatic]
cowswap-release-sync[bot] Jul 28, 2026
fac5579
fix: check allowances based on sell amount + buffer
Danziger Jul 30, 2026
2b741c8
Merge branch 'feat/eoa-twap-order-tracker' of github.com:cowprotocol/…
Danziger Jul 30, 2026
4cb0c7e
chore: merge develop and resolve conflicts
Danziger Jul 31, 2026
a9c09ca
chore: update bundle sizes [automatic]
cowswap-release-sync[bot] Jul 31, 2026
f7502a3
feat: set EOA_TWAP_POC_DEBUG to true
Danziger Jul 31, 2026
f92bde9
Merge branch 'feat/eoa-twap-order-tracker' of github.com:cowprotocol/…
Danziger Jul 31, 2026
5e93d66
chore: update TSDoc comment
Danziger Jul 31, 2026
e431071
chore: update bundle sizes [automatic]
cowswap-release-sync[bot] Jul 31, 2026
cb2cb1e
fix: abort TWAP flow if dismissed, improve allowance checks
Danziger Jul 31, 2026
8ab8a91
Merge branch 'feat/eoa-twap-order-tracker' of github.com:cowprotocol/…
Danziger Jul 31, 2026
56f49e6
chore: merge develop
Danziger Jul 31, 2026
d16ecfb
chore: update bundle sizes [automatic]
cowswap-release-sync[bot] Jul 31, 2026
70cdce7
fix: keep old 2-step tracker and new multi-step tracker independent
Danziger Jul 31, 2026
18f59cd
Merge branch 'feat/eoa-twap-order-tracker' of github.com:cowprotocol/…
Danziger Jul 31, 2026
5410080
chore(i18n): extract i18n strings [automatic]
cowswap-release-sync[bot] Jul 31, 2026
8beb263
chore: update bundle sizes [automatic]
cowswap-release-sync[bot] Jul 31, 2026
c46438d
chore: update bundle sizes [automatic]
cowswap-release-sync[bot] Jul 31, 2026
37a53b1
feat: check approva tx receip
Danziger Aug 3, 2026
28d731b
Merge branch 'feat/eoa-twap-order-tracker' of github.com:cowprotocol/…
Danziger Aug 3, 2026
3eb6de6
chore: merge develop
Danziger Aug 3, 2026
b2946d0
feat: updated designs for mutli-step tracker
Danziger Aug 4, 2026
1828d49
fix: check for -1 from indexOf when building signing plan
Danziger Aug 4, 2026
9070a2c
Merge branch 'develop' into feat/eoa-twap-order-tracker
Danziger Aug 4, 2026
f8290ba
chore(i18n): extract i18n strings [automatic]
cowswap-release-sync[bot] Aug 4, 2026
365f253
chore: update bundle sizes [automatic]
cowswap-release-sync[bot] Aug 4, 2026
284baac
feat: update multi expandable button hover styles
Danziger Aug 4, 2026
0ecdf22
Merge branch 'feat/eoa-twap-order-tracker' of github.com:cowprotocol/…
Danziger Aug 4, 2026
ab53e95
chore: merge develop
Danziger Aug 4, 2026
d8a5964
chore: update bundle sizes [automatic]
cowswap-release-sync[bot] Aug 4, 2026
b9642a8
fix: fix test
Danziger Aug 4, 2026
2c68566
Merge branch 'feat/eoa-twap-order-tracker' of github.com:cowprotocol/…
Danziger Aug 4, 2026
91e9b59
chore: merge develop and resolve conflicts
Danziger Aug 5, 2026
04b52be
Merge branch 'develop' into feat/eoa-twap-order-tracker
Danziger Aug 5, 2026
204842d
fix: fix unit test
Danziger Aug 5, 2026
693b771
Merge branch 'develop' into feat/eoa-twap-order-tracker
Danziger Aug 5, 2026
0d425f5
feat: add constants for Composable CoW poller
Danziger Aug 5, 2026
214bef1
feat: update steps for JIT polling (WIP)
Danziger Aug 5, 2026
d542f63
chore(i18n): extract i18n strings [automatic]
cowswap-release-sync[bot] Aug 5, 2026
82bd2b6
feat: v2 SC integration WIP, missing deployment with registerWithSign…
Danziger Aug 7, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,33 @@ import { AccountIcon } from 'modules/account'
import { Wrapper, CloseIcon, UpperSection, WalletIcon, LowerSection, BackButtonStyled } from './styled'

interface ConfirmationPendingContentShellProps {
onDismiss: Command
title: ReactNode
description: ReactNode
children: ReactNode
modalMode?: boolean
/** Hide the back/close button if `onDismiss` is `undefined`. */
onDismiss?: Command
}

// TODO: Add proper return type annotation
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function ConfirmationPendingContentShell({
title,
onDismiss,
description,
children: body,
modalMode,
onDismiss,
}: ConfirmationPendingContentShellProps) {
return (
<Wrapper>
{modalMode}
<UpperSection>
{!modalMode && <BackButtonStyled onClick={onDismiss} />}
{!modalMode && onDismiss && <BackButtonStyled onClick={onDismiss} />}
<WalletIcon>
<AccountIcon size={56} />
</WalletIcon>
<span>{title}</span>
{modalMode && <CloseIcon onClick={onDismiss} />}
{modalMode && onDismiss && <CloseIcon onClick={onDismiss} />}
</UpperSection>
<LowerSection>
<h3>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export { ConfirmationPendingContent } from './ConfirmationPendingContent'
export { MultiConfirmationPendingContent } from './multi/MultiConfirmationPendingContent'
export type {
MultiConfirmationPendingStep,
MultiConfirmationPendingStepStatus,
} from './multi/MultiConfirmationPendingContent'
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import { UI } from '@cowprotocol/ui'

import styled from 'styled-components/macro'

export const StepsList = styled.ol`
--circle-size: 24px;
--inner-circle-size: 12px;
--icon-size: 14px;
--status-bg: var(${UI.COLOR_TEXT_OPACITY_10});
--status-color: var(${UI.COLOR_TEXT_OPACITY_70});
--spacing-around: 12px;

list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-flow: column nowrap;
gap: 0;
width: 100%;
margin-inline: auto;
`

export const StepItem = styled.li`
position: relative;
display: flex;
flex-direction: column;
position: relative;
padding: 0 0 var(--spacing-around) calc(var(--circle-size) + var(--spacing-around));

&:not(:last-child)::before {
content: '';
position: absolute;
top: calc(1.5 * var(--spacing-around) + var(--circle-size));
bottom: calc(-0.5 * var(--spacing-around));
left: calc(var(--circle-size) / 2);
transform: translateX(-50%);
border-left: 2px solid var(${UI.COLOR_TEXT_OPACITY_10});
}
`

export const StepsIconWrapper = styled.div`
position: absolute;
top: var(--spacing-around);
left: 0;
border-radius: var(--circle-size);
height: var(--circle-size);
width: var(--circle-size);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
background: var(--status-bg);
color: var(--status-color);
font-size: var(--icon-size);

&[data-status='active'],
&[data-status='loading'] {
--status-bg: var(${UI.COLOR_INFO_BG});
--status-color: var(${UI.COLOR_INFO_TEXT});
}

&[data-status='loading'] {
animation: statusBgPulse 1.4s ease-in-out infinite;
}

&[data-status='loading']::before {
animation: statusDotPulse 1.4s ease-in-out infinite;
}

@keyframes statusBgPulse {
0%,
100% {
background: var(${UI.COLOR_INFO_BG});
}
50% {
background: var(${UI.COLOR_PRIMARY_OPACITY_25});
}
}

@keyframes statusDotPulse {
0%,
100% {
transform: translate(-50%, -50%) scale(1);
}
50% {
transform: translate(-50%, -50%) scale(1.25);
}
}

&[data-status='warning'] {
--status-bg: var(${UI.COLOR_ALERT_BG});
--status-color: var(${UI.COLOR_ALERT_TEXT});
}

&[data-status='success'] {
--status-bg: var(${UI.COLOR_SUCCESS_BG});
--status-color: var(${UI.COLOR_SUCCESS_TEXT});
}

&[data-status='error'] {
--status-bg: var(${UI.COLOR_DANGER_BG});
--status-color: var(${UI.COLOR_DANGER_TEXT});
}

&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: var(--inner-circle-size);
height: var(--inner-circle-size);
border-radius: 100%;
border: 2px solid currentColor;
transition: border-width 0.2s ease-in-out;
}

&[data-status='active']::before,
&[data-status='loading']::before {
border-width: calc(var(--inner-circle-size) / 2);
}

&[data-status='success'],
&[data-status='error'],
&[data-status='warning'] {
&::before {
display: none;
}
}

> svg {
width: 1em;
height: 1em;
stroke: currentColor;
}
`

export const StepHeaderButton = styled.button`
position: relative;
padding: var(--spacing-around) calc(var(--circle-size) + var(--spacing-around)) 0 0;
border: none;
background: transparent;

&:not(:disabled) {
cursor: pointer;
}

&::before {
content: '';
position: absolute;
top: 0;
left: calc(-0.5 * var(--spacing-around));
right: calc(-0.5 * var(--spacing-around));
bottom: calc(-1 * var(--spacing-around));
border-radius: 14px;
}

&:not(:disabled):hover::before {
background: var(${UI.COLOR_TEXT_OPACITY_10});
}
`

export const StepLabel = styled.strong`
display: block;
flex: 1;
font-size: 15px;
line-height: 1.4;
text-align: left;
margin: 0;
color: var(${UI.COLOR_TEXT_OPACITY_70});
font-weight: var(${UI.FONT_WEIGHT_BOLD});

[data-status='active'] &,
[data-status='loading'] & {
color: var(${UI.COLOR_TEXT});
}

[data-status='upcoming'] & {
font-weight: var(${UI.FONT_WEIGHT_NORMAL});
}
`

export const StepExpandIcon = styled.button`
position: absolute;
right: 0;
top: 14px;
background: none;
border: none;
cursor: pointer;
font-size: 20px;
line-height: 1;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
color: var(${UI.COLOR_TEXT});

& > svg {
width: 1em;
height: 1em;
transition: transform 0.2s ease-in-out;
}

&[aria-expanded='true'] > svg {
transform: rotate(180deg);
}
`

export const StepDetailsInner = styled.div`
display: flex;
flex-flow: column nowrap;
gap: 4px;
padding: var(--spacing-around) 0 0 0;

& p {
margin: 0;
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { ReactNode, useState } from 'react'

import { Command } from '@cowprotocol/types'

import { Trans } from '@lingui/react/macro'
import { Check, X, AlertTriangle, ChevronDown } from 'react-feather'

import { ExpandableContent } from 'common/pure/ExpandableContent/ExpandableContent.pure'

import * as styledEl from './MultiConfirmationPendingContent.styled'

import { ConfirmationPendingContentShell } from '../ConfirmationPendingContentShell'

export interface MultiConfirmationPendingStep {
id: string
label: ReactNode
description?: string | ReactNode
status: MultiConfirmationPendingStepStatus
}

export type MultiConfirmationPendingStepStatus = 'upcoming' | 'active' | 'loading' | 'success' | 'error' | 'warning'

interface MultiConfirmationItemProps {
step: MultiConfirmationPendingStep
}

interface MultiConfirmationPendingContentProps {
title: ReactNode
description: ReactNode
modalMode?: boolean

/**
* Arbitrary multi-step progress.
*/
steps: MultiConfirmationPendingStep[]

/** Hide the back/close button if `onDismiss` is `undefined`. */
onDismiss?: Command
}

export function MultiConfirmationPendingContent({
title,
description,
modalMode,
steps,
onDismiss,
}: MultiConfirmationPendingContentProps): ReactNode {
return (
<ConfirmationPendingContentShell
title={title}
onDismiss={onDismiss}
modalMode={modalMode}
description={
<>
<span>{description}</span>
<br />
<Trans>Follow these steps:</Trans>
</>
}
>
<styledEl.StepsList>
{steps.map((step) => (
<MultiConfirmationItem key={step.id} step={step} />
))}
</styledEl.StepsList>
</ConfirmationPendingContentShell>
)
}

const ICON_STROKE_WIDTH = 2.5

const ICONS_BY_STATUS = {
upcoming: null,
active: null,
loading: null,
success: <Check strokeWidth={ICON_STROKE_WIDTH} />,
error: <X strokeWidth={ICON_STROKE_WIDTH} />,
warning: <AlertTriangle strokeWidth={ICON_STROKE_WIDTH} />,
} as const satisfies Record<MultiConfirmationPendingStepStatus, ReactNode | null>

const EXPANDABLE_STATUSES = new Set(['success', 'error', 'warning'])
const ALWAYS_EXPANDED_STATUSES = new Set(['active', 'loading'])

function MultiConfirmationItem({ step: { label, description, status } }: MultiConfirmationItemProps): ReactNode {
const [isUserExpanded, setIsUserExpanded] = useState(false)
const canExpand = !!description && EXPANDABLE_STATUSES.has(status)
const isExpanded = (canExpand && isUserExpanded) || ALWAYS_EXPANDED_STATUSES.has(status)

const toggleIsUserExpanded = (): void => setIsUserExpanded((prev) => !prev)

return (
<styledEl.StepItem data-status={status}>
<styledEl.StepsIconWrapper data-status={status}>{ICONS_BY_STATUS[status]}</styledEl.StepsIconWrapper>

<styledEl.StepHeaderButton onClick={toggleIsUserExpanded} aria-expanded={isUserExpanded} disabled={!canExpand}>
<styledEl.StepLabel>{label}</styledEl.StepLabel>

{canExpand ? (
<styledEl.StepExpandIcon>
<ChevronDown aria-label={isUserExpanded ? 'Collapse' : 'Expand'} strokeWidth={ICON_STROKE_WIDTH} />
</styledEl.StepExpandIcon>
) : null}
</styledEl.StepHeaderButton>

{description != null ? (
<ExpandableContent expanded={isExpanded}>
<styledEl.StepDetailsInner>
{typeof description === 'string' ? <p>{description}</p> : description}
</styledEl.StepDetailsInner>
</ExpandableContent>
) : null}
</styledEl.StepItem>
)
}
Loading
Loading