diff --git a/src/common/contracts/core/pot/client.ts b/src/common/contracts/core/pot/client.ts index 866f8f4e9..12ca5ea29 100644 --- a/src/common/contracts/core/pot/client.ts +++ b/src/common/contracts/core/pot/client.ts @@ -1,9 +1,10 @@ import { calculateDepositByDataSize } from "@wpdas/naxios"; +import { Big } from "big.js"; import { parseNearAmount } from "near-api-js/lib/utils/format"; import { type ByPotId, PotId } from "@/common/api/indexer"; import { nearProtocolClient } from "@/common/blockchains/near-protocol"; -import { FULL_TGAS, ONE_HUNDREDTH_NEAR } from "@/common/constants"; +import { FULL_TGAS, ONE_HUNDREDTH_NEAR, ONE_TGAS } from "@/common/constants"; import type { AccountId } from "@/common/types"; import { @@ -18,6 +19,7 @@ import { PotDonationArgs, UpdatePotArgs, } from "./interfaces"; +import { calculateCallDeposit } from "./utils"; export const contractApi = (potId: string) => nearProtocolClient.naxiosInstance.contractApi({ @@ -107,6 +109,22 @@ export const get_payouts = async (args: { potId: string }) => // WRITE METHODS +export type ApplyArgs = { + message?: string; +}; + +export const apply = ({ + potId, + args, + callbackUrl = window.location.href, +}: ByPotId & { args: ApplyArgs; callbackUrl?: string }) => + contractApi(potId).call("apply", { + args, + deposit: calculateCallDeposit({ functionName: "apply", args }), + gas: ONE_TGAS.mul(100).toString(), + callbackUrl, + }); + export type ChallengePayoutsArgs = { reason: string; }; diff --git a/src/common/contracts/core/pot/index.ts b/src/common/contracts/core/pot/index.ts index 55a3979a9..427bf29e5 100644 --- a/src/common/contracts/core/pot/index.ts +++ b/src/common/contracts/core/pot/index.ts @@ -1,7 +1,8 @@ import * as potContractClient from "./client"; import * as potContractHooks from "./hooks"; +import * as potContractUtils from "./utils"; export type * from "./hooks"; export * from "./interfaces"; -export { potContractClient, potContractHooks }; +export { potContractClient, potContractHooks, potContractUtils }; diff --git a/src/common/contracts/core/pot/utils.ts b/src/common/contracts/core/pot/utils.ts new file mode 100644 index 000000000..a9565c1ec --- /dev/null +++ b/src/common/contracts/core/pot/utils.ts @@ -0,0 +1,30 @@ +import { calculateDepositByDataSize } from "@wpdas/naxios"; +import { Big } from "big.js"; + +import { ONE_HUNDREDTH_NEAR } from "@/common/constants"; +import { parseNearAmount } from "@/common/lib"; +import type { IndivisibleUnits } from "@/common/types"; + +import type { ApplyArgs } from "./client"; + +type CallDepositParams = + | { functionName: "apply"; args: ApplyArgs } + | { functionName: string; args: {} }; + +export const calculateCallDeposit = ({ + functionName, + args, +}: CallDepositParams): IndivisibleUnits => { + switch (functionName) { + case "apply": { + return ( + parseNearAmount(Big(0.01).times(calculateDepositByDataSize(args)).toString()) ?? + ONE_HUNDREDTH_NEAR + ); + } + + default: { + return "0"; + } + } +}; diff --git a/src/common/contracts/sputnikdao2/client.ts b/src/common/contracts/sputnikdao2/client.ts index 5716bb5c4..f08a9d9e2 100644 --- a/src/common/contracts/sputnikdao2/client.ts +++ b/src/common/contracts/sputnikdao2/client.ts @@ -1,7 +1,8 @@ import { nearProtocolClient } from "@/common/blockchains/near-protocol"; -import type { ByAccountId } from "@/common/types"; +import { FULL_TGAS } from "@/common/constants"; +import type { ByAccountId, IndivisibleUnits } from "@/common/types"; -import type { Policy, ProposalOutput } from "./types"; +import type { Policy, ProposalId, ProposalInput, ProposalOutput } from "./types"; export const get_policy = ({ accountId }: ByAccountId) => nearProtocolClient.naxiosInstance @@ -20,3 +21,24 @@ export const get_proposals = ({ accountId, args }: ByAccountId & { args: GetProp nearProtocolClient.naxiosInstance .contractApi({ contractId: accountId }) .view("get_proposals", { args }); + +export type AddProposalArgs = { + proposal: ProposalInput; +}; + +export const add_proposal = ({ + accountId, + proposalBond, + callbackUrl = window.location.href, + args, +}: ByAccountId & { proposalBond: IndivisibleUnits; callbackUrl?: string } & { + args: AddProposalArgs; +}) => + nearProtocolClient.naxiosInstance + .contractApi({ contractId: accountId }) + .call("add_proposal", { + args, + deposit: proposalBond, + gas: FULL_TGAS, + callbackUrl, + }); diff --git a/src/common/contracts/sputnikdao2/types.ts b/src/common/contracts/sputnikdao2/types.ts index 8abd71e55..601e34137 100644 --- a/src/common/contracts/sputnikdao2/types.ts +++ b/src/common/contracts/sputnikdao2/types.ts @@ -87,7 +87,7 @@ export type ActionCall = { args: string; deposit: IndivisibleUnits; - gas: number; + gas: IndivisibleUnits; }; export enum ProposalStatus { @@ -149,6 +149,11 @@ export enum Vote { Remove = 2, } +export type ProposalInput = { + description: string; + kind: ProposalKind; +}; + export type Proposal = { proposer: AccountId; description: string; diff --git a/src/common/lib/object.ts b/src/common/lib/object.ts index db51c763d..2ed041bc7 100644 --- a/src/common/lib/object.ts +++ b/src/common/lib/object.ts @@ -1,5 +1,7 @@ import { mapValues } from "remeda"; +import { utf8StringToBase64 } from "./string"; + type DeepPartial = T extends object ? { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; @@ -52,3 +54,6 @@ export const nullifyEmptyStrings = mapValues((value: string | unknown) => { return null; } else return value; }); + +export const objectToBase64Json = (obj: object | Record): string => + utf8StringToBase64(JSON.stringify(obj)); diff --git a/src/common/lib/string.ts b/src/common/lib/string.ts index d9a8b3993..b47d0dc38 100644 --- a/src/common/lib/string.ts +++ b/src/common/lib/string.ts @@ -35,3 +35,11 @@ export const isValidHttpUrl = (value: string) => { return false; } }; + +export const utf8StringToBase64 = (value: string): string => { + const encoder = new TextEncoder(); + const bytes = encoder.encode(value); + const binary = bytes.reduce((acc, byte) => acc + String.fromCharCode(byte), ""); + + return btoa(binary); +}; diff --git a/src/features/donation/components/user-entrypoints.tsx b/src/features/donation/components/user-entrypoints.tsx index 97d358bec..70531093b 100644 --- a/src/features/donation/components/user-entrypoints.tsx +++ b/src/features/donation/components/user-entrypoints.tsx @@ -72,7 +72,7 @@ export type DonateToListProjectsProps = ByListId & {}; export const DonateToListProjects: React.FC = ({ listId }) => { const { openDonationModal } = useDonationUserFlow({ listId }); - return ; + return ; }; export type DonationToCampaignProps = ByCampaignId & diff --git a/src/features/matching-pool-contribution/hooks/forms.ts b/src/features/matching-pool-contribution/hooks/forms.ts index fe96d9b60..e553d0c3d 100644 --- a/src/features/matching-pool-contribution/hooks/forms.ts +++ b/src/features/matching-pool-contribution/hooks/forms.ts @@ -8,6 +8,7 @@ import { Pot } from "@/common/api/indexer"; import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { FIFTY_TGAS, FULL_TGAS, MIN_PROPOSAL_DEPOSIT_FALLBACK, ONE_TGAS } from "@/common/constants"; import { sputnikDaoClient } from "@/common/contracts/sputnikdao2"; +import { objectToBase64Json } from "@/common/lib"; import { useWalletUserSession } from "@/common/wallet"; import { MatchingPoolContributionInputs, matchingPoolFundingSchema } from "../model/schemas"; @@ -37,7 +38,7 @@ export const useMatchingPoolContributionForm = ({ potDetail }: { potDetail: Pot method_name: "donate", gas: FIFTY_TGAS, deposit: parseNearAmount(formData.amountNEAR.toString()) || "0", - args: Buffer.from(JSON.stringify(args), "utf-8").toString("base64"), + args: objectToBase64Json(args), }; const daoTransactionArgs = { diff --git a/src/features/pot-application/components/PotApplicationModal.tsx b/src/features/pot-application/components/PotApplicationModal.tsx index 209f2e89a..5f34f8547 100644 --- a/src/features/pot-application/components/PotApplicationModal.tsx +++ b/src/features/pot-application/components/PotApplicationModal.tsx @@ -1,28 +1,25 @@ -// TODO!: Import from the UI kit instead and adjust the layout accordingly -import { Form } from "react-hook-form"; - -import { Pot } from "@/common/api/indexer"; -import type { AccountId } from "@/common/types"; +import { TextAreaField } from "@/common/ui/form/components"; import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, + Form, FormField, Spinner, - Textarea, } from "@/common/ui/layout/components"; import { useWalletUserSession } from "@/common/wallet"; -import { usePotApplicationForm } from "../hooks/forms"; +import { type PotApplicationFormParams, usePotApplicationForm } from "../hooks/forms"; -export type PotApplicationModalProps = { +export type PotApplicationModalProps = Pick< + PotApplicationFormParams, + "applicantAccountId" | "potConfig" | "onSuccess" | "onFailure" +> & { open?: boolean; onCloseClick?: () => void; - applicantAccountId: AccountId; daoMode?: boolean; - potDetail: Pot; }; export const PotApplicationModal: React.FC = ({ @@ -30,62 +27,59 @@ export const PotApplicationModal: React.FC = ({ onCloseClick, applicantAccountId, daoMode = false, - potDetail, + potConfig, + onSuccess, + onFailure, }) => { const walletUser = useWalletUserSession(); - // Form settings - const { form, errors, onSubmit, inProgress } = usePotApplicationForm({ - accountId: applicantAccountId, + const { form, onSubmit } = usePotApplicationForm({ + applicantAccountId, asDao: daoMode, - potDetail, + potConfig, + onSuccess, + onFailure, }); return ( - Apply to Pot + {`Apply to ${potConfig.name}`} -
-
- {/*NEAR Input */} -

- Application message * -

- - {/* Optional Message */} + + ( -