Skip to content

Commit

Permalink
🍶 Create token transaction (#4912)
Browse files Browse the repository at this point in the history
* Initial work

* Correct env endpoints

* Update joystream types

* Fix types on issue token tx

* Final touches for token submit

* Add success modal to the end of the flow

* Add symbol to the tx metadata
  • Loading branch information
WRadoslaw committed Mar 14, 2024
1 parent 9ee4725 commit 3cec8c2
Show file tree
Hide file tree
Showing 11 changed files with 401 additions and 73 deletions.
2 changes: 1 addition & 1 deletion packages/atlas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"@hcaptcha/react-hcaptcha": "^1.4.4",
"@hookform/resolvers": "^2.9.10",
"@joystream/js": "^1.4.0",
"@joystream/metadata-protobuf": "2.8.1",
"@joystream/metadata-protobuf": "2.9.0",
"@joystream/types": "4.3.0",
"@livesession/sdk": "^1.1.4",
"@loadable/component": "^5.15.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { flushSync } from 'react-dom'
import { CSSTransition, SwitchTransition } from 'react-transition-group'

import { CrtDrawer, CrtDrawerProps } from '@/components/CrtDrawer'
import { CreateTokenSuccessModal } from '@/components/_crt/CreateTokenSuccessModal'
import { useConfirmationModal } from '@/providers/confirmationModal'
import { transitions } from '@/styles'

Expand Down Expand Up @@ -36,6 +37,7 @@ type CreateTokenDrawerProps = {

export const CreateTokenDrawer = ({ show, onClose }: CreateTokenDrawerProps) => {
const [activeStep, setActiveStep] = useState(CREATE_TOKEN_STEPS.setup)
const [showSuccessModal, setShowSuccessModal] = useState(false)
const formData = useRef<CreateTokenForm>(CREATOR_TOKEN_INITIAL_DATA)
const [primaryButtonProps, setPrimaryButtonProps] =
useState<NonNullable<CrtDrawerProps['actionBar']>['primaryButton']>()
Expand Down Expand Up @@ -106,53 +108,58 @@ export const CreateTokenDrawer = ({ show, onClose }: CreateTokenDrawerProps) =>
}

return (
<CrtDrawer
steps={steps}
activeStep={activeStep}
isOpen={show}
onClose={() => onClose()}
actionBar={{
isNoneCrypto: true,
primaryButton: primaryButtonProps ?? {},
secondaryButton,
}}
preview={preview}
formWrapperRef={formRef}
>
<SwitchTransition mode="out-in">
<CSSTransition
key={activeStep}
nodeRef={nodeRef}
timeout={100}
addEndListener={(done) => {
nodeRef.current?.addEventListener('transitionend', done, false)
}}
onEntered={() => setIsGoingBack(false)}
classNames={isGoingBack ? transitions.names.backwardSlideSwitch : transitions.names.forwardSlideSwitch}
>
<div ref={nodeRef}>
{activeStep === CREATE_TOKEN_STEPS.setup && (
<SetupTokenStep
{...commonProps}
onSubmit={(data) => {
formData.current = { ...formData.current, ...data }
setActiveStep(CREATE_TOKEN_STEPS.issuance)
}}
/>
)}
{activeStep === CREATE_TOKEN_STEPS.issuance && (
<TokenIssuanceStep
{...commonProps}
onSubmit={(data) => {
formData.current = { ...formData.current, ...data }
setActiveStep(CREATE_TOKEN_STEPS.summary)
}}
/>
)}
{activeStep === CREATE_TOKEN_STEPS.summary && <TokenSummaryStep {...commonProps} />}
</div>
</CSSTransition>
</SwitchTransition>
</CrtDrawer>
<>
<CreateTokenSuccessModal show={showSuccessModal} tokenName={formData.current.name} tokenId="1" />
<CrtDrawer
steps={steps}
activeStep={activeStep}
isOpen={show}
onClose={() => onClose()}
actionBar={{
isNoneCrypto: true,
primaryButton: primaryButtonProps ?? {},
secondaryButton,
}}
preview={preview}
formWrapperRef={formRef}
>
<SwitchTransition mode="out-in">
<CSSTransition
key={activeStep}
nodeRef={nodeRef}
timeout={100}
addEndListener={(done) => {
nodeRef.current?.addEventListener('transitionend', done, false)
}}
onEntered={() => setIsGoingBack(false)}
classNames={isGoingBack ? transitions.names.backwardSlideSwitch : transitions.names.forwardSlideSwitch}
>
<div ref={nodeRef}>
{activeStep === CREATE_TOKEN_STEPS.setup && (
<SetupTokenStep
{...commonProps}
onSubmit={(data) => {
formData.current = { ...formData.current, ...data }
setActiveStep(CREATE_TOKEN_STEPS.issuance)
}}
/>
)}
{activeStep === CREATE_TOKEN_STEPS.issuance && (
<TokenIssuanceStep
{...commonProps}
onSubmit={(data) => {
formData.current = { ...formData.current, ...data }
setActiveStep(CREATE_TOKEN_STEPS.summary)
}}
/>
)}
{activeStep === CREATE_TOKEN_STEPS.summary && (
<TokenSummaryStep {...commonProps} onSuccess={() => setShowSuccessModal(true)} />
)}
</div>
</CSSTransition>
</SwitchTransition>
</CrtDrawer>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const accessOptions = [
caption: 'Only members on allowlist can own your token. ',
icon: <SvgActionLock />,
value: false,
disabled: true,
},
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export const TokenIssuanceStep = ({
const data =
assuranceType === 'custom'
? generateChartData(Number(customCliff ?? 0), Number(customVesting ?? 0), firstPayout ? firstPayout : 0)
: generateChartData(...getDataBasedOnType(assuranceType))
: generateChartData(...(getDataBasedOnType(assuranceType) as [number, number, number]))
setPreview(
<PreviewContainer>
<Text variant="h100" as="h1" color="colorTextMuted">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Datum } from '@nivo/line'
import { z } from 'zod'

import { IssuanceStepForm } from '@/components/_crt/CreateTokenDrawer/CreateTokenDrawer.types'

export const assuranceOptions = [
{
label: 'Secure',
Expand Down Expand Up @@ -172,13 +174,15 @@ export const generateChartData = (cliffTime: number, vestingTime: number, firstP
return data
}

export const getDataBasedOnType = (type: 'secure' | 'safe' | 'risky'): [number, number, number] => {
export const getDataBasedOnType = (type: IssuanceStepForm['assuranceType']): [number, number, number] | null => {
switch (type) {
case 'secure':
return [6, 12, 50]
case 'safe':
return [0, 6, 50]
case 'risky':
return [0, 0, 0]
return [0, 0, 100]
case 'custom':
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import { Text } from '@/components/Text'
import { Tooltip } from '@/components/Tooltip'
import { CrtFormWrapper } from '@/components/_crt/CrtFormWrapper'
import { useMountEffect } from '@/hooks/useMountEffect'
import { useFee, useJoystream } from '@/providers/joystream'
import { useTransaction } from '@/providers/transactions/transactions.hooks'
import { useUser } from '@/providers/user/user.hooks'
import { sizes } from '@/styles'
import { formatNumber } from '@/utils/number'

import { cliffOptions, vestingOptions } from './TokenIssuanceStep/TokenIssuanceStep.utils'
import { cliffOptions, getDataBasedOnType, vestingOptions } from './TokenIssuanceStep/TokenIssuanceStep.utils'
import { CommonStepProps } from './types'

const cliffBanner = (
Expand All @@ -27,10 +30,52 @@ const cliffBanner = (
/>
)

export const TokenSummaryStep = ({ setPrimaryButtonProps, form }: CommonStepProps) => {
const monthDurationToBlocks = (numberOfMonths: number) => numberOfMonths * 30 * 24 * 60 * 6
export type TokenSummaryStepProps = {
onSuccess: () => void
} & CommonStepProps
export const TokenSummaryStep = ({ setPrimaryButtonProps, form, onSuccess }: TokenSummaryStepProps) => {
const { joystream, proxyCallback } = useJoystream()
const { channelId, memberId } = useUser()
const handleTransaction = useTransaction()
const { fullFee } = useFee('issueCreatorTokenTx')
const handleSubmitTx = async () => {
if (!joystream || !channelId || !memberId) return
const [cliff, vesting, payout] = getDataBasedOnType(form.assuranceType) ?? [
form.cliff,
form.vesting,
form.firstPayout,
]
return handleTransaction({
fee: fullFee,
txFactory: async (handleUpdate) =>
(await joystream.extrinsics).issueCreatorToken(
memberId,
channelId,
form.name,
form.creatorReward,
form.revenueShare,
{
amount: String(form.creatorIssueAmount ?? 0),
cliffAmountPercentage: payout ?? 0,
vestingDuration: vesting ? monthDurationToBlocks(+vesting) : 0,
blocksBeforeCliff: cliff ? monthDurationToBlocks(+cliff) : 0,
},
proxyCallback(handleUpdate)
),
onTxSync: async () => {
onSuccess()
},
snackbarSuccessMessage: {
title: `$${form.name} minted successfuly.`,
},
})
}

useMountEffect(() => {
setPrimaryButtonProps({
text: 'Create token',
onClick: handleSubmitTx,
})
})

Expand Down Expand Up @@ -142,7 +187,7 @@ export const TokenSummaryStep = ({ setPrimaryButtonProps, form }: CommonStepProp
title="Transaction fee"
tooltipText="This action requires a blockchain transaction, which comes with a fee."
>
<NumberFormat value={9120332} variant="h300" as="p" withDenomination="before" />
<NumberFormat value={fullFee} format="short" variant="h300" as="p" withToken withDenomination="before" />
</SectionRow>
</Section>
</CrtFormWrapper>
Expand Down
Loading

0 comments on commit 3cec8c2

Please sign in to comment.