-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(deployment): implement ato top up setting
closes #412
- Loading branch information
1 parent
6e31276
commit bfa085f
Showing
16 changed files
with
666 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
216 changes: 216 additions & 0 deletions
216
apps/deploy-web/src/components/settings/AutoTopUpSetting/AutoTopUpSetting.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import React, { FC, useCallback, useEffect, useMemo } from "react"; | ||
import { Controller, SubmitHandler, useForm } from "react-hook-form"; | ||
import { Button, Form, FormField, FormInput } from "@akashnetwork/ui/components"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import addYears from "date-fns/addYears"; | ||
import format from "date-fns/format"; | ||
import { z } from "zod"; | ||
|
||
import { aktToUakt, uaktToAKT } from "@src/utils/priceUtils"; | ||
|
||
const positiveNumberSchema = z.coerce.number().min(0, { | ||
message: "Amount must be greater or equal to 0." | ||
}); | ||
|
||
const formSchema = z | ||
.object({ | ||
uaktFeeLimit: positiveNumberSchema, | ||
usdcFeeLimit: positiveNumberSchema, | ||
uaktDeploymentLimit: positiveNumberSchema, | ||
usdcDeploymentLimit: positiveNumberSchema, | ||
expiration: z.string().min(1, "Expiration is required.") | ||
}) | ||
.refine( | ||
data => { | ||
if (data.usdcDeploymentLimit > 0) { | ||
return data.usdcFeeLimit > 0; | ||
} | ||
return true; | ||
}, | ||
{ | ||
message: "Must be greater than 0 if `USDC Deployments Limit` is greater than 0", | ||
path: ["usdcFeeLimit"] | ||
} | ||
) | ||
.refine( | ||
data => { | ||
if (data.usdcFeeLimit > 0) { | ||
return data.usdcDeploymentLimit > 0; | ||
} | ||
return true; | ||
}, | ||
{ | ||
message: "Must be greater than 0 if `USDC Fees Limit` is greater than 0", | ||
path: ["usdcDeploymentLimit"] | ||
} | ||
) | ||
.refine( | ||
data => { | ||
if (data.uaktDeploymentLimit > 0) { | ||
return data.uaktFeeLimit > 0; | ||
} | ||
return true; | ||
}, | ||
{ | ||
message: "Must be greater than 0 if `AKT Deployments Limit` is greater than 0", | ||
path: ["uaktFeeLimit"] | ||
} | ||
) | ||
.refine( | ||
data => { | ||
if (data.uaktFeeLimit > 0) { | ||
return data.uaktDeploymentLimit > 0; | ||
} | ||
return true; | ||
}, | ||
{ | ||
message: "Must be greater than 0 if `AKT Fees Limit` is greater than 0", | ||
path: ["uaktDeploymentLimit"] | ||
} | ||
); | ||
|
||
type FormValues = z.infer<typeof formSchema>; | ||
|
||
type LimitFields = keyof Omit<FormValues, "expiration">; | ||
|
||
type AutoTopUpSubmitHandler = (action: "revoke-all" | "update", next: FormValues) => Promise<void>; | ||
|
||
export interface AutoTopUpSettingProps extends Partial<Record<LimitFields, number>> { | ||
onSubmit: AutoTopUpSubmitHandler; | ||
expiration?: Date; | ||
} | ||
|
||
const fields: LimitFields[] = ["uaktFeeLimit", "usdcFeeLimit", "uaktDeploymentLimit", "usdcDeploymentLimit"]; | ||
|
||
export const AutoTopUpSetting: FC<AutoTopUpSettingProps> = ({ onSubmit, expiration, ...props }) => { | ||
const hasAny = useMemo(() => fields.some(field => props[field]), [props]); | ||
|
||
const defaultLimitValues = useMemo(() => { | ||
return fields.reduce( | ||
(acc, field) => { | ||
acc[field] = uaktToAKT(props[field] || 0); | ||
return acc; | ||
}, | ||
{} as Record<LimitFields, number> | ||
); | ||
}, [props]); | ||
|
||
const form = useForm<z.infer<typeof formSchema>>({ | ||
defaultValues: { | ||
...defaultLimitValues, | ||
expiration: format(expiration || addYears(new Date(), 1), "yyyy-MM-dd'T'HH:mm") | ||
}, | ||
resolver: zodResolver(formSchema) | ||
}); | ||
const { handleSubmit, control, setValue, reset } = form; | ||
|
||
useEffect(() => { | ||
setValue("uaktFeeLimit", uaktToAKT(props.uaktFeeLimit || 0)); | ||
}, [props.uaktFeeLimit]); | ||
|
||
useEffect(() => { | ||
setValue("usdcFeeLimit", uaktToAKT(props.usdcFeeLimit || 0)); | ||
}, [props.usdcFeeLimit]); | ||
|
||
useEffect(() => { | ||
setValue("uaktDeploymentLimit", uaktToAKT(props.uaktDeploymentLimit || 0)); | ||
}, [props.uaktDeploymentLimit]); | ||
|
||
useEffect(() => { | ||
setValue("usdcDeploymentLimit", uaktToAKT(props.usdcDeploymentLimit || 0)); | ||
}, [props.usdcDeploymentLimit]); | ||
|
||
useEffect(() => { | ||
if (expiration) { | ||
setValue("expiration", format(expiration || addYears(new Date(), 1), "yyyy-MM-dd'T'HH:mm")); | ||
} | ||
}, [expiration]); | ||
|
||
const execSubmitterRoleAction: SubmitHandler<FormValues> = useCallback( | ||
async (next: FormValues, event: React.BaseSyntheticEvent<SubmitEvent>) => { | ||
const role = event.nativeEvent.submitter?.getAttribute("data-role"); | ||
await onSubmit(role as "revoke-all" | "update", convertToUakt(next)); | ||
reset(next); | ||
}, | ||
[onSubmit, reset] | ||
); | ||
|
||
return ( | ||
<div> | ||
<Form {...form}> | ||
<form onSubmit={handleSubmit(execSubmitterRoleAction)} noValidate> | ||
<div className="flex"> | ||
<div className="flex-1"> | ||
<FormField | ||
control={control} | ||
name="uaktDeploymentLimit" | ||
render={({ field, fieldState }) => { | ||
return <FormInput {...field} dirty={fieldState.isDirty} type="number" label="AKT Deployments Limit, AKT" min={0} step={0.000001} />; | ||
}} | ||
/> | ||
</div> | ||
|
||
<div className="ml-3 flex-1"> | ||
<FormField | ||
control={control} | ||
name="uaktFeeLimit" | ||
render={({ field, fieldState }) => { | ||
return <FormInput {...field} dirty={fieldState.isDirty} type="number" label="AKT Fees Limit, AKT" min={0} step={0.000001} />; | ||
}} | ||
/> | ||
</div> | ||
</div> | ||
|
||
<div className="flex"> | ||
<div className="flex-1"> | ||
<FormField | ||
control={control} | ||
name="usdcDeploymentLimit" | ||
render={({ field, fieldState }) => { | ||
return <FormInput {...field} dirty={fieldState.isDirty} type="number" label="USDC Deployments Limit, AKT" min={0} step={0.000001} />; | ||
}} | ||
/> | ||
</div> | ||
|
||
<div className="ml-3 flex-1"> | ||
<FormField | ||
control={control} | ||
name="usdcFeeLimit" | ||
render={({ field, fieldState }) => { | ||
return <FormInput {...field} dirty={fieldState.isDirty} type="number" label="USDC Fees Limit, AKT" min={0} step={0.000001} />; | ||
}} | ||
/> | ||
</div> | ||
</div> | ||
|
||
<div className="mb-4 w-full"> | ||
<Controller | ||
control={control} | ||
name="expiration" | ||
render={({ field, fieldState }) => { | ||
return <FormInput {...field} dirty={fieldState.isDirty} type="datetime-local" label="Expiration" />; | ||
}} | ||
/> | ||
</div> | ||
|
||
<Button variant="default" size="sm" className="mr-2" data-role="update" disabled={!form.formState.isDirty}> | ||
{hasAny ? "Update" : "Enable"} | ||
</Button> | ||
|
||
{hasAny && ( | ||
<Button variant="default" size="sm" data-role="revoke-all"> | ||
Disable | ||
</Button> | ||
)} | ||
</form> | ||
</Form> | ||
</div> | ||
); | ||
}; | ||
|
||
function convertToUakt({ ...values }: FormValues) { | ||
return fields.reduce((acc, field) => { | ||
acc[field] = aktToUakt(values[field]); | ||
return acc; | ||
}, values); | ||
} |
75 changes: 75 additions & 0 deletions
75
apps/deploy-web/src/components/settings/AutoTopUpSetting/AutoTopUpSettingContainer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import React, { FC, useCallback, useEffect } from "react"; | ||
|
||
import { AutoTopUpSetting, AutoTopUpSettingProps } from "@src/components/settings/AutoTopUpSetting/AutoTopUpSetting"; | ||
import { useWallet } from "@src/context/WalletProvider"; | ||
import { useAutoTopUpLimits } from "@src/hooks/useAutoTopUpLimits"; | ||
import { useAutoTopUpService } from "@src/hooks/useAutoTopUpService"; | ||
import { useDoubleTransactionAlert } from "@src/hooks/useDoubleTransactionAlert"; | ||
|
||
export const AutoTopUpSettingContainer: FC = () => { | ||
const { address, signAndBroadcastTx } = useWallet(); | ||
const { fetch, uaktFeeLimit, usdcFeeLimit, uaktDeploymentLimit, usdcDeploymentLimit, expiration } = useAutoTopUpLimits(); | ||
const autoTopUpMessageService = useAutoTopUpService(); | ||
const doubleTransactionAlert = useDoubleTransactionAlert(); | ||
|
||
useEffect(() => { | ||
fetch(); | ||
}, []); | ||
|
||
const updateAllowancesAndGrants: AutoTopUpSettingProps["onSubmit"] = useCallback( | ||
async (action, next) => { | ||
const prev = { | ||
uaktFeeLimit, | ||
usdcFeeLimit, | ||
uaktDeploymentLimit, | ||
usdcDeploymentLimit, | ||
expiration | ||
}; | ||
|
||
const { revoke, grant } = autoTopUpMessageService.collectMessages({ | ||
granter: address, | ||
prev, | ||
next: action === "revoke-all" ? undefined : { ...next, expiration: new Date(next.expiration) } | ||
}); | ||
|
||
const closeAlert = revoke.length && grant.length && doubleTransactionAlert.show(); | ||
|
||
if (revoke.length) { | ||
await signAndBroadcastTx(revoke); | ||
} | ||
|
||
if (grant.length) { | ||
await signAndBroadcastTx(grant); | ||
} | ||
|
||
if (closeAlert) { | ||
closeAlert(); | ||
} | ||
|
||
await fetch(); | ||
}, | ||
[ | ||
address, | ||
autoTopUpMessageService, | ||
doubleTransactionAlert, | ||
expiration, | ||
fetch, | ||
signAndBroadcastTx, | ||
uaktDeploymentLimit, | ||
uaktFeeLimit, | ||
usdcDeploymentLimit, | ||
usdcFeeLimit | ||
] | ||
); | ||
|
||
return ( | ||
<AutoTopUpSetting | ||
onSubmit={updateAllowancesAndGrants} | ||
uaktFeeLimit={uaktFeeLimit} | ||
usdcFeeLimit={usdcFeeLimit} | ||
uaktDeploymentLimit={uaktDeploymentLimit} | ||
usdcDeploymentLimit={usdcDeploymentLimit} | ||
expiration={expiration} | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { useMemo } from "react"; | ||
import { AllowanceHttpService } from "@akashnetwork/http-sdk"; | ||
|
||
import { useSettings } from "@src/context/SettingsProvider"; | ||
|
||
export const useAllowanceService = () => { | ||
const { settings } = useSettings(); | ||
return useMemo(() => new AllowanceHttpService({ baseURL: settings.apiEndpoint }), [settings.apiEndpoint]); | ||
}; |
Oops, something went wrong.