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/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..cc9e7171f 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.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 new file mode 100644 index 000000000..b750d074b --- /dev/null +++ b/pages/dao/[symbol]/proposal/components/instructions/Torque/CreateRecurringPayment.tsx @@ -0,0 +1,344 @@ +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', + }) + } + + 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) + + 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..40e7da81f --- /dev/null +++ b/pages/dao/[symbol]/proposal/components/instructions/Torque/useTorque.ts @@ -0,0 +1,183 @@ +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' + +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: process.env.NEXT_PUBLIC_TORQUE_API_URL || 'https://server.torque.so', + rpcUrl, + daoWallet: realmsInfo?.realmId?.toBase58() + }) + 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) { + + 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 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'] = [ + { + 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(), + 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: args.paymentInterval ?? 3600, + 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 + }; + + const distributor = await torqueSdk.offers.addDistributor(args.offerId, distributionInput) + const {instruction: distributorIx} = await torqueSdk.offers.distributorInstructions(args.offerId, distributor.id, args.payer, true ) + + 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 000000000..754615570 Binary files /dev/null and b/public/img/torque.png differ diff --git a/utils/uiTypes/proposalCreationTypes.ts b/utils/uiTypes/proposalCreationTypes.ts index cb3df118d..ea14a142c 100644 --- a/utils/uiTypes/proposalCreationTypes.ts +++ b/utils/uiTypes/proposalCreationTypes.ts @@ -10,6 +10,7 @@ import { AssetAccount, StakeAccount } from '@utils/uiTypes/assets' import { RealmInfo } from '@models/registry/api' import * as PaymentStreaming from '@mean-dao/payment-streaming' import { DasNftObject } from '@hooks/queries/digitalAssets' +import { StreamedRewardCadenceType } from '@torque-labs/sdk' // Alphabetical order export enum PackageEnum { @@ -30,7 +31,8 @@ export enum PackageEnum { Squads, Switchboard, VsrPlugin, - Raydium + Raydium, + Torque, } export interface UiInstruction { @@ -311,6 +313,30 @@ export interface WithdrawDAOForm { amount?: number } +export type TorqueDurationUnit = 'hours' | 'days' | 'weeks' | 'months' | 'years' + +export type TorqueFrequencyUnit = { + name: string + value: TorqueDurationUnit +} + +export type TorqueStreamType = { + name: string + value: StreamedRewardCadenceType + description: string +} + +export interface TorqueCreateRecurringPaymentForm { + governedTokenAccount?: AssetAccount + tokenAmount: number + streamType: TorqueStreamType + paymentDuration : number + paymentFrequency?: number + paymentFrequencyUnit?: TorqueFrequencyUnit + paymentDestination: string +} + + export enum Instructions { Base64, Burn, @@ -424,7 +450,8 @@ export enum Instructions { SymmetryWithdraw, TokenWithdrawFees, CollectPoolFees, - CollectVestedTokens + CollectVestedTokens, + TorqueCreateRecurringPayment, } export interface ComponentInstructionData { diff --git a/yarn.lock b/yarn.lock index 4686f3afb..810a287d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6915,7 +6915,7 @@ "@solana/wallet-standard-core" "^1.1.2" "@solana/wallet-standard-wallet-adapter" "^1.1.4" -"@solana/web3.js@1.56.0", "@solana/web3.js@1.78.8", "@solana/web3.js@=1.92.3", "@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.22.0", "@solana/web3.js@^1.30.2", "@solana/web3.js@^1.31.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.35.1", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.43.4", "@solana/web3.js@^1.44.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.50.1", "@solana/web3.js@^1.53.0", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.59.1", "@solana/web3.js@^1.63.0", "@solana/web3.js@^1.63.1", "@solana/web3.js@^1.66.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.70.3", "@solana/web3.js@^1.73.0", "@solana/web3.js@^1.73.2", "@solana/web3.js@^1.77.3", "@solana/web3.js@^1.87.5", "@solana/web3.js@^1.87.6", "@solana/web3.js@^1.89.1", "@solana/web3.js@^1.90.0", "@solana/web3.js@^1.91.8", "@solana/web3.js@^1.95.1", "@solana/web3.js@^1.95.2", "@solana/web3.js@^1.95.3", "@solana/web3.js@^1.95.8", "@solana/web3.js@~1.77.3": +"@solana/web3.js@1.56.0", "@solana/web3.js@1.78.8", "@solana/web3.js@=1.92.3", "@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.22.0", "@solana/web3.js@^1.30.2", "@solana/web3.js@^1.31.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.35.1", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.43.4", "@solana/web3.js@^1.44.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.50.1", "@solana/web3.js@^1.53.0", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.59.1", "@solana/web3.js@^1.63.0", "@solana/web3.js@^1.63.1", "@solana/web3.js@^1.66.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.70.3", "@solana/web3.js@^1.73.0", "@solana/web3.js@^1.73.2", "@solana/web3.js@^1.77.3", "@solana/web3.js@^1.87.5", "@solana/web3.js@^1.87.6", "@solana/web3.js@^1.89.1", "@solana/web3.js@^1.90.0", "@solana/web3.js@^1.91.8", "@solana/web3.js@^1.95.1", "@solana/web3.js@^1.95.2", "@solana/web3.js@^1.95.3", "@solana/web3.js@^1.95.8", "@solana/web3.js@^1.98.0", "@solana/web3.js@~1.77.3": version "1.78.8" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.78.8.tgz#d635bcccaac9e36270c6b16340bfeb5502737831" integrity sha512-y6kMa0ohRjamBGtxIGX4TkdAzL8Cs2bzM4JDPCyYLFPdo7kWk0Cx+BkbhX8hEV4IfvCONF92KIniV7hDvHuq8A== @@ -7385,6 +7385,17 @@ 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.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" + "@solana/web3.js" "^1.98.0" + tweetnacl "^1.0.3" + zod "^3.24.1" + "@toruslabs/base-controllers@^2.8.0": version "2.9.0" resolved "https://registry.yarnpkg.com/@toruslabs/base-controllers/-/base-controllers-2.9.0.tgz#e23f4228b5a90bf94ba9b0b27451f3024bd1acc4" @@ -20168,6 +20179,11 @@ zod@^3.20.2, zod@^3.22.2: resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee" integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A== +zod@^3.24.1: + version "3.24.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" + integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== + zstddec@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.0.2.tgz#57e2f28dd1ff56b750e07d158a43f0611ad9eeb4"