From d04b75e6095a14794dfdbfb05b3bb9a4866abf39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20L=C3=A9ost?= Date: Thu, 15 May 2025 12:39:28 +0100 Subject: [PATCH 1/2] feat: add proposal for Torque Streams --- hooks/useGovernanceAssets.ts | 17 + package.json | 1 + .../Torque/CreateRecurringPayment.tsx | 352 ++++++++++++++++++ .../instructions/Torque/useTorque.ts | 203 ++++++++++ pages/dao/[symbol]/proposal/new.tsx | 4 +- public/img/torque.png | Bin 0 -> 820 bytes utils/uiTypes/proposalCreationTypes.ts | 31 +- yarn.lock | 18 +- 8 files changed, 622 insertions(+), 4 deletions(-) create mode 100644 pages/dao/[symbol]/proposal/components/instructions/Torque/CreateRecurringPayment.tsx create mode 100644 pages/dao/[symbol]/proposal/components/instructions/Torque/useTorque.ts create mode 100644 public/img/torque.png diff --git a/hooks/useGovernanceAssets.ts b/hooks/useGovernanceAssets.ts index 3e12c275f..63f19477f 100644 --- a/hooks/useGovernanceAssets.ts +++ b/hooks/useGovernanceAssets.ts @@ -216,6 +216,10 @@ export default function useGovernanceAssets() { currentPluginPk.toBase58(), ), }, + [PackageEnum.Torque]: { + name: 'Torque', + image: '/img/torque.png', + }, } // Alphabetical order, Packages then instructions @@ -869,6 +873,19 @@ export default function useGovernanceAssets() { isVisible: canUseAuthorityInstruction, packageId: PackageEnum.Distribution, }, + + /* + ████████ ██████ ██████ ██████ ██ ██ ███████ + ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██ ██ ██ ██████ ██ ██ ██ ██ █████ + ██ ██ ██ ██ ██ ██ ▄▄ ██ ██ ██ ██ + ██ ██████ ██ ██ ██████ ██████ ███████ + */ + + [Instructions.TorqueCreateRecurringPayment]: { + name: 'Create Recurring Payment', + packageId: PackageEnum.Torque, + }, } const availablePackages: PackageType[] = Object.entries(packages) diff --git a/package.json b/package.json index ce0086d70..ce4014524 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ "@tailwindcss/line-clamp": "0.4.2", "@tanstack/react-query": "4.14.3", "@tippyjs/react": "4.2.6", + "@torque-labs/sdk": "0.0.12", "@types/ramda": "0.28.15", "@urql/exchange-auth": "1.0.0", "@urql/exchange-graphcache": "5.0.1", diff --git a/pages/dao/[symbol]/proposal/components/instructions/Torque/CreateRecurringPayment.tsx b/pages/dao/[symbol]/proposal/components/instructions/Torque/CreateRecurringPayment.tsx new file mode 100644 index 000000000..a38a49a54 --- /dev/null +++ b/pages/dao/[symbol]/proposal/components/instructions/Torque/CreateRecurringPayment.tsx @@ -0,0 +1,352 @@ +import { Governance } from '@solana/spl-governance' +import { ProgramAccount } from '@solana/spl-governance' +import { + TorqueCreateRecurringPaymentForm, + TorqueDurationUnit, + TorqueFrequencyUnit, + TorqueStreamType, + UiInstruction, +} from '@utils/uiTypes/proposalCreationTypes' +import { NewProposalContext } from '../../../new' +import { useContext, useEffect, useMemo, useState } from 'react' +import * as yup from 'yup' +import { isFormValid, validatePubkey } from '@utils/formValidation' +import { InstructionInputType } from '../inputInstructionType' +import InstructionForm, { InstructionInput } from '../FormCreator' +import useGovernanceAssets from '@hooks/useGovernanceAssets' +import { parseMintNaturalAmountFromDecimal } from '@tools/sdk/units' +import useWalletOnePointOh from '@hooks/useWalletOnePointOh' +import { useTorque } from './useTorque' +import { StreamedRewardCadenceType } from '@torque-labs/sdk' + +interface CreateRecurringPaymentProps { + index: number + governance: ProgramAccount | null +} + +// Helper function to convert time units to days +const convertToSeconds = (value: number, unit: TorqueDurationUnit): number => { + switch (unit) { + case 'hours': + return value * 3600 + case 'days': + return value * 24 * 3600 + case 'weeks': + return value * 7 * 24 * 3600 + case 'months': + return value * 30 * 24 * 3600 // Approximate + case 'years': + return value * 365 * 24 * 3600 // Approximate + default: + return 0 + } +} + +const INTERVAL_OPTIONS: TorqueFrequencyUnit[] = [ + { name: 'Hours', value: 'hours' as TorqueDurationUnit }, + { name: 'Days', value: 'days' as TorqueDurationUnit }, + { name: 'Weeks', value: 'weeks' as TorqueDurationUnit }, + { name: 'Months', value: 'months' as TorqueDurationUnit }, + { name: 'Years', value: 'years' as TorqueDurationUnit }, +] + +const STREAM_TYPES: TorqueStreamType[] = [ + { + name: 'Monthly', + value: 'FIRST_OF_EVERY_MONTH' as StreamedRewardCadenceType, + description: 'Pays out at the first of every month.', + }, + { + name: 'Fixed Interval', + value: 'FIXED_INTERVAL' as StreamedRewardCadenceType, + description: 'Pays out at a fixed interval.', + }, +] + +function CreateRecurringPayment({ + index, + governance, +}: CreateRecurringPaymentProps) { + // State + const [formErrors, setFormErrors] = useState({}) + const [form, setForm] = useState({ + governedTokenAccount: undefined, + tokenAmount: 0, + streamType: STREAM_TYPES[0], + paymentFrequency: 0, + paymentFrequencyUnit: INTERVAL_OPTIONS[0], + paymentDuration: 0, + paymentDestination: '', + }) + + // Hooks + const { fetchDaoProject, createStreamOffer, createStreamDistributor } = + useTorque() + const wallet = useWalletOnePointOh() + const { governedSPLTokenAccounts, governedNativeAccounts } = + useGovernanceAssets() + + // Contexts + const { handleSetInstructions } = useContext(NewProposalContext) + + // Consts + const shouldBeGoverned = !!(index !== 0 && governance) + const schema = yup.object().shape({ + governedTokenAccount: yup.object().required('Token is required'), + tokenAmount: yup + .number() + .required('Token amount is required') + .moreThan(0, 'Token amount must be more than 0'), + streamType: yup.object().required('Stream type is required'), + paymentDestination: yup + .string() + .required('Payment destination is required') + .test('is-valid-address', 'Please enter a valid PublicKey', (value) => + value ? validatePubkey(value) : false, + ), + paymentDuration: yup.number().required('Payment duration is required'), + }) + + const { totalPayments, totalAmount } = useMemo(() => { + if (!form.tokenAmount) { + return { + totalPayments: 0, + totalAmount: 0, + } + } + + // Calculate how many payments will be made based on frequency and duration + const totalPayments = form.paymentDuration + + // Calculate total amount + return { + totalPayments, + totalAmount: form.tokenAmount * totalPayments, + } + }, [form.tokenAmount, form.paymentDuration]) + + const getInstruction = async () => { + // Validate form + const { isValid, validationErrors } = await isFormValid(schema, form) + setFormErrors(validationErrors) + + if (!isValid) return + if (!wallet || !wallet.publicKey) return + if (!form.governedTokenAccount) return + + let tokenMint: string | undefined + let tokenBalance: number | undefined + let tokenDecimals: number | undefined + let payer: string | undefined + const daoWallet = + form.governedTokenAccount.governance.account.realm.toBase58() + + if (form.governedTokenAccount.isToken) { + tokenMint = + form.governedTokenAccount.extensions.mint?.publicKey.toString() + tokenBalance = Number( + form.governedTokenAccount?.extensions.amount?.toString(), + ) + payer = + form.governedTokenAccount.extensions.token?.account.owner.toString() + tokenDecimals = + form.governedTokenAccount.extensions.mint?.account.decimals + } else if (form.governedTokenAccount.isSol) { + tokenBalance = Number( + form.governedTokenAccount.extensions.amount?.toString(), + ) + payer = form.governedTokenAccount.pubkey.toString() + tokenMint = 'So11111111111111111111111111111111111111112' + tokenDecimals = 9 + } + + // Check if the token details are valid + if (!tokenMint || !tokenBalance || !payer || !tokenDecimals) { + return setFormErrors({ + governedTokenAccount: 'Missing details from token account', + }) + } + + // Check if the dao has enough balance + if ( + tokenBalance < + parseMintNaturalAmountFromDecimal(totalAmount, tokenDecimals ?? 0) + ) { + return setFormErrors({ + tokenAmount: 'Insufficient balance', + }) + } + + // If it's a Monthly stream we need to make sure the payment frequency is greater than 1 + // if(form.streamType.value === 'FIRST_OF_EVERY_MONTH' && (!form.paymentFrequency || form.paymentFrequency <= 1)) { + // return setFormErrors({ + // paymentFrequency: 'Payment frequency must be greater than 1', + // }) + // } + + const interval = + form.streamType.value === 'FIXED_INTERVAL' + ? convertToSeconds( + form.paymentFrequency ?? 0, + form.paymentFrequencyUnit?.value ?? 'days', + ) + : 0 + + const project = await fetchDaoProject(daoWallet) + + if (!project) return + + const offer = await createStreamOffer(form, project.id) + + console.log('offer', offer) + const { serializedIx } = await createStreamDistributor({ + offerId: offer.id, + totalAmount: totalAmount, + amountPerPayment: form.tokenAmount, + token: tokenMint, + decimals: tokenDecimals, + numberOfPayments: form.paymentDuration, + streamType: form.streamType, + paymentInterval: interval, + startDate: new Date().toISOString(), + payer: payer, + }) + + const obj: UiInstruction = { + serializedInstruction: serializedIx, + isValid, + governance: form.governedTokenAccount.governance, + } + + return obj + } + + useEffect(() => { + // The part that integrates with the new proposal creation + handleSetInstructions( + { + governedAccount: form.governedTokenAccount?.governance, + getInstruction, + }, + index, + ) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [form]) + + const inputs: InstructionInput[] = useMemo(() => { + const inputs: InstructionInput[] = [ + { + label: 'Select Token', + initialValue: form.governedTokenAccount, + name: 'governedTokenAccount', + type: InstructionInputType.GOVERNED_ACCOUNT, + shouldBeGoverned: shouldBeGoverned, + governance: governance, + options: [...governedSPLTokenAccounts, ...governedNativeAccounts], + assetType: 'token' as const, + }, + { + label: 'Payment Amount', + subtitle: 'Amount to be paid per payment', + initialValue: form.tokenAmount, + type: InstructionInputType.INPUT, + inputType: 'number', + name: 'tokenAmount', + additionalComponent: totalAmount ? ( +

+ Total amount to be paid out: {totalAmount} over {totalPayments}{' '} + payments. +

+ ) : null, + }, + { + label: 'Payment Destination', + initialValue: form.paymentDestination, + type: InstructionInputType.INPUT, + inputType: 'text', + name: 'paymentDestination', + }, + { + label: 'Stream Type', + subtitle: STREAM_TYPES.find( + (type) => type.value === form.streamType.value, + )?.description, + initialValue: form.streamType, + type: InstructionInputType.SELECT, + name: 'streamType', + options: STREAM_TYPES, + }, + ] + + switch (form.streamType.value) { + case 'FIRST_OF_EVERY_MONTH': + inputs.push({ + label: 'Payment Duration', + subtitle: 'How many months to pay out for.', + initialValue: form.paymentDuration ?? 0, + type: InstructionInputType.INPUT, + inputType: 'number', + name: 'paymentDuration', + additionalComponent: null, + }) + break + case 'FIXED_INTERVAL': + inputs.push({ + label: 'Payment Duration', + subtitle: 'How many payments do you want to make?', + initialValue: form.paymentDuration, + type: InstructionInputType.INPUT, + inputType: 'number', + name: 'paymentDuration', + additionalComponent: null, + }) + inputs.push({ + label: 'Payment Interval', + subtitle: 'How often payments should occur', + initialValue: form.paymentFrequency, + type: InstructionInputType.INPUT, + inputType: 'number', + name: 'paymentFrequency', + additionalComponent: null, + }) + inputs.push({ + label: 'Payment Interval Unit', + subtitle: 'Unit of time between payments', + initialValue: form.paymentFrequencyUnit, + type: InstructionInputType.SELECT, + name: 'paymentFrequencyUnit', + options: INTERVAL_OPTIONS, + }) + break + default: + break + } + + return inputs + }, [ + form.governedTokenAccount, + form.tokenAmount, + form.paymentDestination, + form.streamType, + form.paymentDuration, + form.paymentFrequency, + form.paymentFrequencyUnit, + shouldBeGoverned, + governance, + governedSPLTokenAccounts, + governedNativeAccounts, + totalAmount, + totalPayments, + ]) + return ( + + ) +} + +export default CreateRecurringPayment diff --git a/pages/dao/[symbol]/proposal/components/instructions/Torque/useTorque.ts b/pages/dao/[symbol]/proposal/components/instructions/Torque/useTorque.ts new file mode 100644 index 000000000..a823a3720 --- /dev/null +++ b/pages/dao/[symbol]/proposal/components/instructions/Torque/useTorque.ts @@ -0,0 +1,203 @@ +import { useSelectedRealmInfo } from '@hooks/selectedRealm/useSelectedRealmRegistryEntry' +import useRpcContext from '@hooks/useRpcContext' +import useWalletOnePointOh from '@hooks/useWalletOnePointOh' +import { serializeInstructionToBase64 } from '@solana/spl-governance' +import { TransactionInstruction } from '@solana/web3.js' +import { DistributionFunctionInputType, DistributorInput, EventType, OfferInput, ProjectInput, ProjectResponse, RewardActivationType, TorqueSDK } from '@torque-labs/sdk' +import { TorqueCreateRecurringPaymentForm, TorqueStreamType } from '@utils/uiTypes/proposalCreationTypes' +import { useRef } from 'react' +import { StreamedRewardCadenceType } from '@torque-labs/sdk' + +// Helper function to convert time units to seconds +const convertToSeconds = (value: number, unit: string): number => { + switch (unit) { + case 'days': + return value * 86400 + case 'weeks': + return value * 604800 + case 'months': + return value * 2592000 // Approximate + case 'years': + return value * 31536000 // Approximate + default: + return 0 + } +} + +interface CreateStreamDistributorArgs { + offerId: string, + totalAmount: number, + amountPerPayment: number, + token: string, + decimals: number, + numberOfPayments: number, + streamType: TorqueStreamType, + paymentInterval?: number, + startDate: string, + payer: string +} + +export function useTorque() { + const { getRpcContext } = useRpcContext() + const realmsInfo = useSelectedRealmInfo() + const wallet = useWalletOnePointOh() + const sdkRef = useRef(null) + + async function getSdk() { + if (!sdkRef.current) { + if (!wallet || !wallet.publicKey) { + throw new Error('Wallet not found') + } + + const rpcUrl = getRpcContext()?.endpoint + if (!rpcUrl) { + throw new Error('RPC URL not found') + } + + sdkRef.current = new TorqueSDK({ + apiUrl: 'https://server-devnet.torque.so', + rpcUrl, + }) + await sdkRef.current.authenticate(wallet) + } + return sdkRef.current + } + + async function fetchDaoProject(daoWallet: string) { + const torqueSdk = await getSdk() + let project: ProjectResponse | null = null + + try { + const existingProjects = await torqueSdk.projects.getProjects() + if(existingProjects.length === 0) { + throw new Error('No projects found') + } + project = existingProjects[0]?.id ? existingProjects[0] : null + } catch (error) { + console.log("error", error) + const projectInput: ProjectInput = { + name: `Realms DAO - ${daoWallet}`, + description: `Project to handle all Torque related activities for ${daoWallet}`, + ownerId: daoWallet, + } + + project = await torqueSdk.projects.createProject(projectInput) + } + return project + } + + async function createStreamOffer(form: TorqueCreateRecurringPaymentForm, projectId: string) { + const torqueSdk = await getSdk() + + const startDate = form.streamType.value === 'FIRST_OF_EVERY_MONTH' ? new Date() : new Date(form.startDate) + const endDate = form.streamType.value === 'FIRST_OF_EVERY_MONTH' ? new Date(startDate.setMonth(new Date().getMonth() + form.paymentDuration)) : new Date(startDate.setDate(new Date().getSeconds() + form.paymentDuration)) + + const requirements: OfferInput['requirements'] = [ + { + type: EventType.CLAIM, + config: { + claim: { + type: "boolean", + exact: true, + }, + }, + oracle: 'CUSTOM_EVENT_PROVIDER', + } + ] + + const metadata: OfferInput['metadata'] = { + title: `Payout to ${form.paymentDestination}`, + description: `${realmsInfo?.displayName ?? 'Dao'} will pay out ${form.governedTokenAccount?.extensions.mint?.publicKey.toBase58()} to ${form.paymentDestination}.`, + } + + const audience: OfferInput['audience'] = { + name: `Payout to ${form.paymentDestination}`, + type: 'ALLOWLIST', + addresses: [form.paymentDestination], + } + + const offerInput: OfferInput = { + projectId: projectId, + requirements: requirements, + metadata: metadata, + audience: audience, + startTime: new Date(form.startDate), + endTime: endDate, + } + + const offer = await torqueSdk.offers.createOffer(offerInput); + + return offer; + } + + async function createStreamDistributor(args: CreateStreamDistributorArgs) { + + const emissionType = args.token === 'So11111111111111111111111111111111111111112' ? 'SOL' : 'TOKENS'; + + if(args.streamType.value === 'FIXED_INTERVAL' && (!args.paymentInterval || !args.startDate)) { + throw new Error('Payment interval and start date are required for fixed interval streams') + } + + const streamed: DistributorInput['crankGuard']['streamed'] = args.streamType.value === 'FIXED_INTERVAL' ? { + type: StreamedRewardCadenceType.FIXED_INTERVAL, + maxStreams: args.numberOfPayments, + requireClaim: true, + cadence: { + seconds: convertToSeconds(args.paymentInterval || 0, 'days'), + startDate: new Date(args.startDate) + } + } : { + type: StreamedRewardCadenceType.FIRST_OF_EVERY_MONTH, + maxStreams: args.numberOfPayments, + requireClaim: true + } + + + const torqueSdk = await getSdk() + const distributionInput: DistributorInput = { + type: 'CONVERSION', + emissionType, + tokenAddress: emissionType === 'TOKENS' ? args.token : undefined, + tokenDecimals: args.decimals, + totalFundAmount: args.totalAmount, + crankGuard: { + recipient: "USER", + activation: { type: RewardActivationType.OFFER_START }, + distributionFunctionInput: { + type: DistributionFunctionInputType.CONVERSION_INDEX, + }, + availability: { + maxConversionsPerRecipient: 1, + }, + streamed + }, + distributionFunction: { + type: "CONSTANT", + yIntercept: args.amountPerPayment + }, + closeAuthority: args.payer + }; + + console.log("distributionInput", distributionInput) + + const distributor = await torqueSdk.offers.addDistributor(args.offerId, distributionInput) + const {instruction: distributorIx} = await torqueSdk.offers.distributorInstructions(args.offerId, distributor.id, args.payer, true ) + + console.log("distributorIx", distributorIx) + + const serializedIx = serializeInstructionToBase64(distributorIx as TransactionInstruction) + + return { + rawIx: distributorIx, + serializedIx, + distributor + } + } + + + return { + fetchDaoProject, + createStreamOffer, + createStreamDistributor + } +} diff --git a/pages/dao/[symbol]/proposal/new.tsx b/pages/dao/[symbol]/proposal/new.tsx index 18b7a8459..bd4b83243 100644 --- a/pages/dao/[symbol]/proposal/new.tsx +++ b/pages/dao/[symbol]/proposal/new.tsx @@ -156,6 +156,7 @@ import WithdrawFees from './components/instructions/Token2022/WithdrawFees' import SquadsV4RemoveMember from './components/instructions/Squads/SquadsV4RemoveMember' import CollectPoolFees from './components/instructions/Raydium/CollectPoolFees' import CollectVestedTokens from './components/instructions/Raydium/CollectVestedTokens' +import CreateRecurringPayment from './components/instructions/Torque/CreateRecurringPayment' const TITLE_LENGTH_LIMIT = 130 // the true length limit is either at the tx size level, and maybe also the total account size level (I can't remember) @@ -635,7 +636,8 @@ const New = () => { [Instructions.SymmetryDeposit]: SymmetryDeposit, [Instructions.SymmetryWithdraw]: SymmetryWithdraw, [Instructions.CollectPoolFees]: CollectPoolFees , - [Instructions.CollectVestedTokens]: CollectVestedTokens + [Instructions.CollectVestedTokens]: CollectVestedTokens, + [Instructions.TorqueCreateRecurringPayment]: CreateRecurringPayment }), [governance?.pubkey?.toBase58()], ) diff --git a/public/img/torque.png b/public/img/torque.png new file mode 100644 index 0000000000000000000000000000000000000000..7546155706b1d636fdad3462315dcbb54d2c9715 GIT binary patch literal 820 zcmV-41Izr0P)R1_h_(z4nX@14_|>F3hgg{CdmrA3c{tz$U9&O3qC%gw?4((?!zBr zNW?70p_&L%Yn+i7B>2vv&hYwg=mF$dk{IH^N#{-Dyn@I(hK;lh#2p@|^dGNJ{uz@) zN03K|i~lZYj@E+E3kyCikZ8k%TU_5Qi&{VrAjisaq=&KDEQkYn!Z)Y#9&X!97L?OC z=z%?Zi8y?oekI5~J?*9+=oNX7mJSbVH;!@?bOd|)hR(IeO9z7ZsWT8yQill6EEJp} zu%}B7oOA9K#DVN$g95qmk=258))br}5VvmdbR~!bIRm@RR}n(GU%-Gg0CtXoITieeL5?QZM-Nlo2V=G`~ z&a^Erv@37$x9(!kLUK7j3A!e?!~4Lkf_H`y=7hCk4{@Mwp&PQ)HgJxE`kkVSJ$Dsk zTP93OhZ=+h zCM*M<19<~6L4^H_m-1L}Ho@1bwrK!E?`2D6Jv8K9Y=eg}@T(N^U9+I|I7z$@8meU! zoaK8|+H>i&O&>0mI5EKnF!(BsUYe_y7@*{Nn{;e15M%kFf;LU4=Zb#)pc-?8FCPv& yT&gdj0nx7?d%N~aRQ?$T0D0~q(yM=<4W0pBt$Wb=wse000000 Date: Thu, 15 May 2025 17:21:00 +0100 Subject: [PATCH 2/2] chore: cleanup comments --- .env.sample | 1 + package.json | 2 +- .../Torque/CreateRecurringPayment.tsx | 8 ----- .../instructions/Torque/useTorque.ts | 32 ++++--------------- yarn.lock | 8 ++--- 5 files changed, 12 insertions(+), 39 deletions(-) diff --git a/.env.sample b/.env.sample index 289d0cd4b..2734c66fb 100644 --- a/.env.sample +++ b/.env.sample @@ -11,3 +11,4 @@ NEXT_PUBLIC_DISCORD_APPLICATION_CLIENT_ID=1042836142560645130 NEXT_PUBLIC_DISCORD_MATCHDAY_CLIENT_ID=1044361939322683442 NEXT_PUBLIC_HELIUS_MAINNET_RPC= NEXT_PUBLIC_HELIUS_DEVNET_RPC= +NEXT_PUBLIC_TORQUE_API=https://server.torque.so \ No newline at end of file diff --git a/package.json b/package.json index ce4014524..cc9e7171f 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "@tailwindcss/line-clamp": "0.4.2", "@tanstack/react-query": "4.14.3", "@tippyjs/react": "4.2.6", - "@torque-labs/sdk": "0.0.12", + "@torque-labs/sdk": "0.0.13", "@types/ramda": "0.28.15", "@urql/exchange-auth": "1.0.0", "@urql/exchange-graphcache": "5.0.1", diff --git a/pages/dao/[symbol]/proposal/components/instructions/Torque/CreateRecurringPayment.tsx b/pages/dao/[symbol]/proposal/components/instructions/Torque/CreateRecurringPayment.tsx index a38a49a54..b750d074b 100644 --- a/pages/dao/[symbol]/proposal/components/instructions/Torque/CreateRecurringPayment.tsx +++ b/pages/dao/[symbol]/proposal/components/instructions/Torque/CreateRecurringPayment.tsx @@ -177,13 +177,6 @@ function CreateRecurringPayment({ }) } - // If it's a Monthly stream we need to make sure the payment frequency is greater than 1 - // if(form.streamType.value === 'FIRST_OF_EVERY_MONTH' && (!form.paymentFrequency || form.paymentFrequency <= 1)) { - // return setFormErrors({ - // paymentFrequency: 'Payment frequency must be greater than 1', - // }) - // } - const interval = form.streamType.value === 'FIXED_INTERVAL' ? convertToSeconds( @@ -198,7 +191,6 @@ function CreateRecurringPayment({ const offer = await createStreamOffer(form, project.id) - console.log('offer', offer) const { serializedIx } = await createStreamDistributor({ offerId: offer.id, totalAmount: totalAmount, diff --git a/pages/dao/[symbol]/proposal/components/instructions/Torque/useTorque.ts b/pages/dao/[symbol]/proposal/components/instructions/Torque/useTorque.ts index a823a3720..40e7da81f 100644 --- a/pages/dao/[symbol]/proposal/components/instructions/Torque/useTorque.ts +++ b/pages/dao/[symbol]/proposal/components/instructions/Torque/useTorque.ts @@ -8,22 +8,6 @@ import { TorqueCreateRecurringPaymentForm, TorqueStreamType } from '@utils/uiTyp import { useRef } from 'react' import { StreamedRewardCadenceType } from '@torque-labs/sdk' -// Helper function to convert time units to seconds -const convertToSeconds = (value: number, unit: string): number => { - switch (unit) { - case 'days': - return value * 86400 - case 'weeks': - return value * 604800 - case 'months': - return value * 2592000 // Approximate - case 'years': - return value * 31536000 // Approximate - default: - return 0 - } -} - interface CreateStreamDistributorArgs { offerId: string, totalAmount: number, @@ -55,8 +39,9 @@ export function useTorque() { } sdkRef.current = new TorqueSDK({ - apiUrl: 'https://server-devnet.torque.so', + apiUrl: process.env.NEXT_PUBLIC_TORQUE_API_URL || 'https://server.torque.so', rpcUrl, + daoWallet: realmsInfo?.realmId?.toBase58() }) await sdkRef.current.authenticate(wallet) } @@ -74,7 +59,7 @@ export function useTorque() { } project = existingProjects[0]?.id ? existingProjects[0] : null } catch (error) { - console.log("error", error) + const projectInput: ProjectInput = { name: `Realms DAO - ${daoWallet}`, description: `Project to handle all Torque related activities for ${daoWallet}`, @@ -89,8 +74,7 @@ export function useTorque() { async function createStreamOffer(form: TorqueCreateRecurringPaymentForm, projectId: string) { const torqueSdk = await getSdk() - const startDate = form.streamType.value === 'FIRST_OF_EVERY_MONTH' ? new Date() : new Date(form.startDate) - const endDate = form.streamType.value === 'FIRST_OF_EVERY_MONTH' ? new Date(startDate.setMonth(new Date().getMonth() + form.paymentDuration)) : new Date(startDate.setDate(new Date().getSeconds() + form.paymentDuration)) + const endDate = form.streamType.value === 'FIRST_OF_EVERY_MONTH' ? new Date(new Date().setMonth(new Date().getMonth() + form.paymentDuration)) : new Date(new Date().setDate(new Date().getSeconds() + form.paymentDuration)) const requirements: OfferInput['requirements'] = [ { @@ -121,7 +105,7 @@ export function useTorque() { requirements: requirements, metadata: metadata, audience: audience, - startTime: new Date(form.startDate), + startTime: new Date(), endTime: endDate, } @@ -143,7 +127,7 @@ export function useTorque() { maxStreams: args.numberOfPayments, requireClaim: true, cadence: { - seconds: convertToSeconds(args.paymentInterval || 0, 'days'), + seconds: args.paymentInterval ?? 3600, startDate: new Date(args.startDate) } } : { @@ -177,14 +161,10 @@ export function useTorque() { }, closeAuthority: args.payer }; - - console.log("distributionInput", distributionInput) const distributor = await torqueSdk.offers.addDistributor(args.offerId, distributionInput) const {instruction: distributorIx} = await torqueSdk.offers.distributorInstructions(args.offerId, distributor.id, args.payer, true ) - console.log("distributorIx", distributorIx) - const serializedIx = serializeInstructionToBase64(distributorIx as TransactionInstruction) return { diff --git a/yarn.lock b/yarn.lock index 965d91f96..810a287d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7385,10 +7385,10 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== -"@torque-labs/sdk@0.0.12": - version "0.0.12" - resolved "https://registry.yarnpkg.com/@torque-labs/sdk/-/sdk-0.0.12.tgz#1e5bfc455c4174de523c908675c0fc241dfc03cb" - integrity sha512-umDm7mVRjXeZp6gU8BIrDClzbpPVFupFTUbQfwPNyYRci+s5vtTDbeH/15Bxk6FJrMW3CLWXadzt+11i+XM7rw== +"@torque-labs/sdk@0.0.13": + version "0.0.13" + resolved "https://registry.yarnpkg.com/@torque-labs/sdk/-/sdk-0.0.13.tgz#aa084558c7285ac5964b4a7c671f49a2f072dd5f" + integrity sha512-GLtJbc7RnKX60a0vFT+Ge6erWFglKC5DT1SVaRB11GgJvQAElXwGRG5BWLaRBqZ/8wPMbtaqKKFWSSY4HoD5OQ== dependencies: "@solana/wallet-adapter-base" "^0.9.23" "@solana/wallet-standard-features" "^1.2.0"