Skip to content

Commit 4b785ff

Browse files
committed
feat: added asset clawback and tests
1 parent dff04e7 commit 4b785ff

File tree

3 files changed

+364
-4
lines changed

3 files changed

+364
-4
lines changed

src/features/transaction-wizard/components/asset-clawback-transaction-builder.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { asAddressOrNfd } from '../mappers/as-address-or-nfd'
2525

2626
const clawbackTargetLabel = 'Clawback target'
2727

28-
const formSchema = z
28+
export const assetClawbackFormSchema = z
2929
.object({
3030
...commonSchema,
3131
...senderFieldSchema,
@@ -65,7 +65,7 @@ const formSchema = z
6565
}
6666
})
6767

68-
const formData = zfd.formData(formSchema)
68+
const formData = zfd.formData(assetClawbackFormSchema)
6969

7070
type FormFieldsProps = {
7171
helper: FormFieldHelper<z.infer<typeof formData>>

src/features/transaction-wizard/utils/transactions-url-search-params.test.tsx

Lines changed: 325 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,8 +1760,6 @@ describe('Render transactions page with search params', () => {
17601760
baseParams[key] = value.toString()
17611761
}
17621762
const searchParams = new URLSearchParams(baseParams)
1763-
console.log(key, mode, value)
1764-
console.log(searchParams.toString())
17651763
renderTxnsWizardPageWithSearchParams({ searchParams })
17661764
const toastElement = await screen.findByText(expected)
17671765
expect(toastElement).toBeInTheDocument()
@@ -1820,4 +1818,329 @@ describe('Render transactions page with search params', () => {
18201818
cleanup()
18211819
})
18221820
})
1821+
1822+
describe('asset clawback transaction search params', () => {
1823+
const sender = 'I3345FUQQ2GRBHFZQPLYQQX5HJMMRZMABCHRLWV6RCJYC6OO4MOLEUBEGU'
1824+
const receiver = 'AAOLENX3Z76HBMQOLQF4VW26ZQSORVX7ZQJ66LCPX36T2QNAUYOYEY76RM'
1825+
const clawbackfrom = 'DJ76C74DI7EDNSHQLAJXGMBHFINBLATVGNRAVCO3VILPCQR7LKY7GPUL7Y'
1826+
const assetId = '12345'
1827+
const amount = '10.5'
1828+
const decimals = '6'
1829+
const unitName = 'USDC'
1830+
const assetclawback = 'I3345FUQQ2GRBHFZQPLYQQX5HJMMRZMABCHRLWV6RCJYC6OO4MOLEUBEGU' // Must be same as sender
1831+
const fee = '2000'
1832+
const note = 'Asset clawback test'
1833+
1834+
it('should render asset clawback transaction with minimal required fields', () => {
1835+
renderTxnsWizardPageWithSearchParams({
1836+
searchParams: new URLSearchParams({
1837+
'type[0]': 'AssetClawback',
1838+
'sender[0]': sender,
1839+
'receiver[0]': receiver,
1840+
'clawbackfrom[0]': clawbackfrom,
1841+
'assetid[0]': assetId,
1842+
'amount[0]': amount,
1843+
'decimals[0]': decimals,
1844+
'assetclawback[0]': assetclawback,
1845+
}),
1846+
})
1847+
1848+
expect(screen.getByText(sender)).toBeInTheDocument()
1849+
expect(screen.getByText(receiver)).toBeInTheDocument()
1850+
expect(screen.getByText(clawbackfrom)).toBeInTheDocument()
1851+
expect(screen.getByText(assetId)).toBeInTheDocument()
1852+
expect(screen.getByText(amount)).toBeInTheDocument()
1853+
})
1854+
1855+
it('should render asset clawback transaction with all optional fields', () => {
1856+
renderTxnsWizardPageWithSearchParams({
1857+
searchParams: new URLSearchParams({
1858+
'type[0]': 'AssetClawback',
1859+
'sender[0]': sender,
1860+
'receiver[0]': receiver,
1861+
'clawbackfrom[0]': clawbackfrom,
1862+
'assetid[0]': assetId,
1863+
'amount[0]': amount,
1864+
'decimals[0]': decimals,
1865+
'assetclawback[0]': assetclawback,
1866+
'unitname[0]': unitName,
1867+
'fee[0]': fee,
1868+
'note[0]': note,
1869+
}),
1870+
})
1871+
1872+
expect(screen.getByText(sender)).toBeInTheDocument()
1873+
expect(screen.getByText(receiver)).toBeInTheDocument()
1874+
expect(screen.getByText(clawbackfrom)).toBeInTheDocument()
1875+
expect(screen.getByText(assetId)).toBeInTheDocument()
1876+
expect(screen.getByText(`${amount} ${unitName}`)).toBeInTheDocument()
1877+
expect(screen.getByText('0.002')).toBeInTheDocument()
1878+
expect(screen.getByText(note)).toBeInTheDocument()
1879+
})
1880+
1881+
it('should render asset clawback transaction with fee only', () => {
1882+
renderTxnsWizardPageWithSearchParams({
1883+
searchParams: new URLSearchParams({
1884+
'type[0]': 'AssetClawback',
1885+
'sender[0]': sender,
1886+
'receiver[0]': receiver,
1887+
'clawbackfrom[0]': clawbackfrom,
1888+
'assetid[0]': assetId,
1889+
'amount[0]': amount,
1890+
'decimals[0]': decimals,
1891+
'assetclawback[0]': assetclawback,
1892+
'fee[0]': fee,
1893+
}),
1894+
})
1895+
1896+
expect(screen.getByText(sender)).toBeInTheDocument()
1897+
expect(screen.getByText(receiver)).toBeInTheDocument()
1898+
expect(screen.getByText(clawbackfrom)).toBeInTheDocument()
1899+
expect(screen.getByText(assetId)).toBeInTheDocument()
1900+
expect(screen.getByText(amount)).toBeInTheDocument()
1901+
expect(screen.getByText('0.002')).toBeInTheDocument()
1902+
})
1903+
1904+
it('should render asset clawback transaction with note only', () => {
1905+
renderTxnsWizardPageWithSearchParams({
1906+
searchParams: new URLSearchParams({
1907+
'type[0]': 'AssetClawback',
1908+
'sender[0]': sender,
1909+
'receiver[0]': receiver,
1910+
'clawbackfrom[0]': clawbackfrom,
1911+
'assetid[0]': assetId,
1912+
'amount[0]': amount,
1913+
'decimals[0]': decimals,
1914+
'assetclawback[0]': assetclawback,
1915+
'note[0]': note,
1916+
}),
1917+
})
1918+
1919+
expect(screen.getByText(sender)).toBeInTheDocument()
1920+
expect(screen.getByText(receiver)).toBeInTheDocument()
1921+
expect(screen.getByText(clawbackfrom)).toBeInTheDocument()
1922+
expect(screen.getByText(assetId)).toBeInTheDocument()
1923+
expect(screen.getByText(amount)).toBeInTheDocument()
1924+
expect(screen.getByText(note)).toBeInTheDocument()
1925+
})
1926+
1927+
it('should render asset clawback transaction with unit name only', () => {
1928+
renderTxnsWizardPageWithSearchParams({
1929+
searchParams: new URLSearchParams({
1930+
'type[0]': 'AssetClawback',
1931+
'sender[0]': sender,
1932+
'receiver[0]': receiver,
1933+
'clawbackfrom[0]': clawbackfrom,
1934+
'assetid[0]': assetId,
1935+
'amount[0]': amount,
1936+
'decimals[0]': decimals,
1937+
'assetclawback[0]': assetclawback,
1938+
'unitname[0]': unitName,
1939+
}),
1940+
})
1941+
1942+
expect(screen.getByText(sender)).toBeInTheDocument()
1943+
expect(screen.getByText(receiver)).toBeInTheDocument()
1944+
expect(screen.getByText(clawbackfrom)).toBeInTheDocument()
1945+
expect(screen.getByText(assetId)).toBeInTheDocument()
1946+
expect(screen.getByText(`${amount} ${unitName}`)).toBeInTheDocument()
1947+
})
1948+
1949+
it('should render asset clawback transaction with clawbacktarget parameter', () => {
1950+
renderTxnsWizardPageWithSearchParams({
1951+
searchParams: new URLSearchParams({
1952+
'type[0]': 'AssetClawback',
1953+
'sender[0]': sender,
1954+
'receiver[0]': receiver,
1955+
'clawbacktarget[0]': clawbackfrom, // Using clawbacktarget instead of clawbackfrom
1956+
'assetid[0]': assetId,
1957+
'amount[0]': amount,
1958+
'decimals[0]': decimals,
1959+
'assetclawback[0]': assetclawback,
1960+
}),
1961+
})
1962+
1963+
expect(screen.getByText(sender)).toBeInTheDocument()
1964+
expect(screen.getByText(receiver)).toBeInTheDocument()
1965+
expect(screen.getByText(clawbackfrom)).toBeInTheDocument()
1966+
expect(screen.getByText(assetId)).toBeInTheDocument()
1967+
expect(screen.getByText(amount)).toBeInTheDocument()
1968+
})
1969+
1970+
it.each([
1971+
// Missing required field cases
1972+
{
1973+
key: 'sender[0]',
1974+
mode: 'missing',
1975+
expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-resolvedAddress',
1976+
},
1977+
{
1978+
key: 'receiver[0]',
1979+
mode: 'missing',
1980+
expected: 'Error in transaction at index 0 in the following fields: receiver-value, receiver-resolvedAddress',
1981+
},
1982+
{
1983+
key: 'clawbackfrom[0]',
1984+
mode: 'missing',
1985+
expected: 'Error in transaction at index 0 in the following fields: clawbackTarget-value, clawbackTarget-resolvedAddress',
1986+
},
1987+
{
1988+
key: 'assetid[0]',
1989+
mode: 'missing',
1990+
expected: 'Error in transaction at index 0: Cannot convert undefined to a BigInt',
1991+
},
1992+
{
1993+
key: 'amount[0]',
1994+
mode: 'missing',
1995+
expected: 'Error in transaction at index 0: [DecimalError] Invalid argument: undefined',
1996+
},
1997+
{
1998+
key: 'decimals[0]',
1999+
mode: 'missing',
2000+
expected: 'Error in transaction at index 0 in the following fields: asset-id',
2001+
},
2002+
{
2003+
key: 'assetclawback[0]',
2004+
mode: 'missing',
2005+
expected: 'Error in transaction at index 0 in the following fields: asset-id',
2006+
},
2007+
// Invalid field value cases
2008+
{
2009+
key: 'sender[0]',
2010+
mode: 'invalid',
2011+
value: 'invalid-address',
2012+
expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-value, sender.value',
2013+
},
2014+
{
2015+
key: 'receiver[0]',
2016+
mode: 'invalid',
2017+
value: 'invalid-address',
2018+
expected: 'Error in transaction at index 0 in the following fields: receiver-value, receiver-value',
2019+
},
2020+
{
2021+
key: 'clawbackfrom[0]',
2022+
mode: 'invalid',
2023+
value: 'invalid-address',
2024+
expected: 'Error in transaction at index 0 in the following fields: clawbackTarget-value, clawbackTarget-value',
2025+
},
2026+
{
2027+
key: 'assetid[0]',
2028+
mode: 'invalid',
2029+
value: 'not-a-number',
2030+
expected: 'Error in transaction at index 0: Cannot convert not-a-number to a BigInt',
2031+
},
2032+
{
2033+
key: 'assetid[0]',
2034+
mode: 'invalid',
2035+
value: '0',
2036+
expected: 'Error in transaction at index 0 in the following fields: asset-id',
2037+
},
2038+
{
2039+
key: 'assetid[0]',
2040+
mode: 'invalid',
2041+
value: '-1',
2042+
expected: 'Error in transaction at index 0 in the following fields: asset-id',
2043+
},
2044+
{
2045+
key: 'amount[0]',
2046+
mode: 'invalid',
2047+
value: 'not-a-number',
2048+
expected: 'Error in transaction at index 0: [DecimalError] Invalid argument: not-a-number',
2049+
},
2050+
{
2051+
key: 'amount[0]',
2052+
mode: 'invalid',
2053+
value: '-10',
2054+
expected: 'Error in transaction at index 0 in the following fields: amount',
2055+
},
2056+
{
2057+
key: 'fee[0]',
2058+
mode: 'invalid',
2059+
value: 'not-a-number',
2060+
expected: 'Error in transaction at index 0: The number NaN cannot be converted to a BigInt because it is not an integer',
2061+
},
2062+
{
2063+
key: 'fee[0]',
2064+
mode: 'invalid',
2065+
value: '-100',
2066+
expected: 'Error in transaction at index 0: Microalgos should be positive and less than 2^53 - 1.',
2067+
},
2068+
])('should show error toast for $mode $key', async ({ key, mode, value, expected }) => {
2069+
const baseParams: Record<string, string> = {
2070+
'type[0]': 'AssetClawback',
2071+
'sender[0]': sender,
2072+
'receiver[0]': receiver,
2073+
'clawbackfrom[0]': clawbackfrom,
2074+
'assetid[0]': assetId,
2075+
'amount[0]': amount,
2076+
'decimals[0]': decimals,
2077+
'assetclawback[0]': assetclawback,
2078+
}
2079+
if (mode === 'missing') {
2080+
delete baseParams[key]
2081+
} else if (mode === 'invalid' && value !== undefined) {
2082+
baseParams[key] = value.toString()
2083+
}
2084+
const searchParams = new URLSearchParams(baseParams)
2085+
renderTxnsWizardPageWithSearchParams({ searchParams })
2086+
const toastElement = await screen.findByText(expected)
2087+
expect(toastElement).toBeInTheDocument()
2088+
cleanup()
2089+
})
2090+
2091+
it('should show "Asset does not exist" error when decimals is undefined', async () => {
2092+
const baseParams: Record<string, string> = {
2093+
'type[0]': 'AssetClawback',
2094+
'sender[0]': sender,
2095+
'receiver[0]': receiver,
2096+
'clawbackfrom[0]': clawbackfrom,
2097+
'assetid[0]': assetId,
2098+
'amount[0]': amount,
2099+
'assetclawback[0]': assetclawback,
2100+
// Note: deliberately omitting decimals[0] to trigger "asset does not exist"
2101+
}
2102+
const searchParams = new URLSearchParams(baseParams)
2103+
renderTxnsWizardPageWithSearchParams({ searchParams })
2104+
const toastElement = await screen.findByText('Error in transaction at index 0 in the following fields: asset-id')
2105+
expect(toastElement).toBeInTheDocument()
2106+
cleanup()
2107+
})
2108+
2109+
it('should show "Asset cannot be clawed back" error when assetclawback is undefined', async () => {
2110+
const baseParams: Record<string, string> = {
2111+
'type[0]': 'AssetClawback',
2112+
'sender[0]': sender,
2113+
'receiver[0]': receiver,
2114+
'clawbackfrom[0]': clawbackfrom,
2115+
'assetid[0]': assetId,
2116+
'amount[0]': amount,
2117+
'decimals[0]': decimals,
2118+
// Note: deliberately omitting assetclawback[0] to trigger "asset cannot be clawed back"
2119+
}
2120+
const searchParams = new URLSearchParams(baseParams)
2121+
renderTxnsWizardPageWithSearchParams({ searchParams })
2122+
const toastElement = await screen.findByText('Error in transaction at index 0 in the following fields: asset-id')
2123+
expect(toastElement).toBeInTheDocument()
2124+
cleanup()
2125+
})
2126+
2127+
it('should show "Must be the clawback account of the asset" error when sender is not the asset clawback account', async () => {
2128+
const differentSender = 'AAOLENX3Z76HBMQOLQF4VW26ZQSORVX7ZQJ66LCPX36T2QNAUYOYEY76RM'
2129+
const baseParams: Record<string, string> = {
2130+
'type[0]': 'AssetClawback',
2131+
'sender[0]': differentSender, // Different from assetclawback
2132+
'receiver[0]': receiver,
2133+
'clawbackfrom[0]': clawbackfrom,
2134+
'assetid[0]': assetId,
2135+
'amount[0]': amount,
2136+
'decimals[0]': decimals,
2137+
'assetclawback[0]': assetclawback,
2138+
}
2139+
const searchParams = new URLSearchParams(baseParams)
2140+
renderTxnsWizardPageWithSearchParams({ searchParams })
2141+
const toastElement = await screen.findByText('Error in transaction at index 0 in the following fields: sender.value')
2142+
expect(toastElement).toBeInTheDocument()
2143+
cleanup()
2144+
})
2145+
})
18232146
})

0 commit comments

Comments
 (0)