Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recurring donations setup on Base #4889

Merged
merged 5 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/apollo/gql/gqlProjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const PROJECT_CARD_FIELDS = gql`
anchorContracts {
address
isActive
networkId
}
}
`;
Expand Down Expand Up @@ -196,6 +197,7 @@ export const FETCH_PROJECT_BY_SLUG_DONATION = gql`
anchorContracts {
address
isActive
networkId
}
}
}
Expand Down Expand Up @@ -294,6 +296,7 @@ export const FETCH_PROJECT_BY_SLUG_SINGLE_PROJECT = gql`
anchorContracts {
address
isActive
networkId
}
}
}
Expand Down Expand Up @@ -332,6 +335,7 @@ export const FETCH_PROJECT_BY_ID = gql`
anchorContracts {
address
isActive
networkId
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/apollo/gql/gqlSuperfluid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,14 @@ export const CREATE_ANCHOR_CONTRACT_ADDRESS_QUERY = gql`
$projectId: Int!
$networkId: Int!
$address: String!
$recipientAddress: String
$txHash: String!
) {
addAnchorContractAddress(
projectId: $projectId
networkId: $networkId
address: $address
recipientAddress: $recipientAddress
txHash: $txHash
) {
id
Expand Down
1 change: 1 addition & 0 deletions src/apollo/gql/gqlUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export const FETCH_USER_RECURRING_DONATIONS = gql`
anchorContracts {
address
isActive
networkId
}
}
finished
Expand Down
1 change: 1 addition & 0 deletions src/apollo/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface IEstimatedMatching {
export interface IAnchorContractData {
address: Address;
isActive: boolean;
networkId: number;
}

export interface IProject {
Expand Down
90 changes: 81 additions & 9 deletions src/components/views/create/AddressInterface.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { useIntl } from 'react-intl';
import { useAccount, useSwitchChain } from 'wagmi';
import styled, { css } from 'styled-components';
import {
B,
Expand All @@ -23,13 +25,16 @@ import { getChainName } from '@/lib/network';
import { IChainType } from '@/types/config';
import { findAddressByChain } from '@/lib/helpers';
import { useGeneralWallet } from '@/providers/generalWalletProvider';
import { IAnchorContractData } from '@/apollo/types/types';
import { IAnchorContractData, IProject } from '@/apollo/types/types';
import { IconWithTooltip } from '@/components/IconWithToolTip';
import { EInputs } from './types';
import links from '@/lib/constants/links';
import { STOP_RECURRING_SETUP_ON_CREATION } from './CreateProject';
import { saveAnchorContract } from './AlloProtocol/AlloProtocolModal';

interface IAddressInterfaceProps extends IChainType {
networkId: number;
project?: IProject;
onButtonClick?: () => void;
anchorContractData?: IAnchorContractData;
isEditMode?: boolean;
Expand All @@ -41,32 +46,54 @@ interface IconContainerProps {

const AddressInterface = ({
networkId,
project,
onButtonClick,
chainType,
anchorContractData,
isEditMode,
}: IAddressInterfaceProps) => {
const { chain } = useAccount();
const { switchChain } = useSwitchChain();
const { setValue, watch } = useFormContext();
const { formatMessage } = useIntl();
const { isOnEVM } = useGeneralWallet();

const [hasAnchorContract, setHasAnchorContract] = useState(
anchorContractData?.isActive || false,
);

const isOnOptimism = chain
? chain.id === config.OPTIMISM_NETWORK_NUMBER
: false;
const isOnBase = chain ? chain.id === config.BASE_NETWORK_NUMBER : false;

const inputName = EInputs.addresses;
const alloProtocolRegistry = watch(EInputs.alloProtocolRegistry) as boolean;

const value = watch(inputName);

const isOptimism = networkId === config.OPTIMISM_NETWORK_NUMBER;
const isBase = networkId === config.BASE_NETWORK_NUMBER;

const addressObj = findAddressByChain(value, networkId, chainType);
const walletAddress = addressObj?.address;

const hasAddress = !!walletAddress;
const hasAnchorContract = !!anchorContractData?.isActive;

const hasOptimismAddress = !!findAddressByChain(
value,
config.OPTIMISM_NETWORK_NUMBER,
chainType,
);
const hasBaseAddress = !!findAddressByChain(
value,
config.BASE_NETWORK_NUMBER,
chainType,
);
const isRecurringOnOptimismReady = isOptimism && hasOptimismAddress;
const isRecurringOnBaseReady = isBase && hasBaseAddress;
const isRecurringDonationsReady =
isRecurringOnBaseReady || isRecurringOnOptimismReady;

return (
<Container>
Expand Down Expand Up @@ -117,7 +144,7 @@ const AddressInterface = ({
{hasAddress ? walletAddress : 'No address added yet!'}
</AddressContainer>
{hasAddress &&
(hasAnchorContract && isOptimism ? (
(hasAnchorContract && (isOptimism || isBase) ? (
<IconWithTooltip
direction='top'
icon={
Expand Down Expand Up @@ -153,10 +180,10 @@ const AddressInterface = ({
</IconContainer>
))}
</Flex>
{isOptimism && isOnEVM && (
// Render this section only on Optimism
{(isOptimism || isBase) && isOnEVM && (
// Render this section only on Optimism and Base
<AlloProtocolContainer>
<Flex>
<Flex $flexDirection='column' $alignItems='end'>
<div>
<B>
{hasAnchorContract && isEditMode
Expand Down Expand Up @@ -192,18 +219,58 @@ const AddressInterface = ({
<IconCheckContainer>
<IconCheck16 color={brandColors.giv[100]} />
</IconCheckContainer>
) : STOP_RECURRING_SETUP_ON_CREATION ? (
<EnableBtn>
<Button
buttonType={
isRecurringDonationsReady
? 'secondary'
: 'texty-secondary'
}
label={'Enable'}
disabled={!isRecurringDonationsReady}
onClick={async () => {
if (!project) return;
if (
isRecurringOnOptimismReady &&
!isOnOptimism
) {
switchChain?.({
chainId:
config.OPTIMISM_NETWORK_NUMBER,
});
} else if (
isRecurringOnBaseReady &&
!isOnBase
) {
switchChain?.({
chainId:
config.BASE_NETWORK_NUMBER,
});
}

await saveAnchorContract({
addedProjectState: project,
chainId: networkId,
recipientAddress:
walletAddress || value,
});
setHasAnchorContract(true);
}}
Comment on lines +232 to +259
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure chain switching completes before proceeding to save the anchor contract

The switchChain function is asynchronous and returns a Promise. You should await this function to ensure the chain switch has completed before calling saveAnchorContract. This prevents potential issues where the contract might be saved on the wrong network.

Apply this diff to fix the issue:

if (
  isRecurringOnOptimismReady &&
  !isOnOptimism
) {
- switchChain?.({
+ await switchChain?.({
    chainId:
      config.OPTIMISM_NETWORK_NUMBER,
  });
} else if (
  isRecurringOnBaseReady &&
  !isOnBase
) {
- switchChain?.({
+ await switchChain?.({
    chainId:
      config.BASE_NETWORK_NUMBER,
  });
}

await saveAnchorContract({
  addedProjectState: project,
  chainId: networkId,
  recipientAddress:
    walletAddress || value,
});

Additionally, consider adding error handling around the switchChain and saveAnchorContract calls to manage any potential errors gracefully.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onClick={async () => {
if (!project) return;
if (
isRecurringOnOptimismReady &&
!isOnOptimism
) {
switchChain?.({
chainId:
config.OPTIMISM_NETWORK_NUMBER,
});
} else if (
isRecurringOnBaseReady &&
!isOnBase
) {
switchChain?.({
chainId:
config.BASE_NETWORK_NUMBER,
});
}
await saveAnchorContract({
addedProjectState: project,
chainId: networkId,
recipientAddress:
walletAddress || value,
});
setHasAnchorContract(true);
}}
onClick={async () => {
if (!project) return;
if (
isRecurringOnOptimismReady &&
!isOnOptimism
) {
await switchChain?.({
chainId:
config.OPTIMISM_NETWORK_NUMBER,
});
} else if (
isRecurringOnBaseReady &&
!isOnBase
) {
await switchChain?.({
chainId:
config.BASE_NETWORK_NUMBER,
});
}
await saveAnchorContract({
addedProjectState: project,
chainId: networkId,
recipientAddress:
walletAddress || value,
});
setHasAnchorContract(true);
}}

/>
</EnableBtn>
) : (
<ToggleSwitch
isOn={alloProtocolRegistry}
toggleOnOff={() => {
if (!hasOptimismAddress) return;
if (!isRecurringDonationsReady) return;
setValue(
EInputs.alloProtocolRegistry,
!alloProtocolRegistry,
);
}}
label=''
disabled={!hasOptimismAddress}
disabled={!isRecurringDonationsReady}
/>
)}
</Flex>
Expand Down Expand Up @@ -233,7 +300,7 @@ const TopContainer = styled.div`
`;

const MiddleContainer = styled.div`
padding: 24px 0;
padding: 24px 0 0 0;
`;

const AddressContainer = styled.div<{ $hasAddress: boolean }>`
Expand Down Expand Up @@ -286,4 +353,9 @@ const CustomLink = styled.a`
cursor: pointer;
`;

const EnableBtn = styled.div`
width: 100px;
margin: 12px 0 0 0;
`;

export default AddressInterface;
56 changes: 56 additions & 0 deletions src/components/views/create/AlloProtocol/AlloProtocolModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,62 @@ interface IAlloProtocolModal extends IModal {
addedProjectState: IProject;
}

export const saveAnchorContract = async ({
addedProjectState,
chainId,
recipientAddress,
}: {
addedProjectState: IProject;
chainId: number;
recipientAddress?: string;
}) => {
try {
const isOptimism = chainId === config.OPTIMISM_NETWORK_NUMBER;
const hash = await writeContract(wagmiConfig, {
address: isOptimism
? config.OPTIMISM_CONFIG.anchorRegistryAddress
: config.BASE_CONFIG.anchorRegistryAddress,
functionName: 'createProfile',
abi: createProfileABI.abi,
chainId,
args: [
generateRandomNonce(), //nonce
addedProjectState?.id!,
{
protocol: 1,
pointer: '',
},
addedProjectState?.adminUser?.walletAddress, //admin user wallet address
[],
],
});
if (hash) {
const data = await waitForTransactionReceipt(wagmiConfig, {
hash: hash,
chainId,
});

const contractAddress = extractContractAddressFromString(
data.logs[0].data,
);

//Call backend to update project
await client.mutate({
mutation: CREATE_ANCHOR_CONTRACT_ADDRESS_QUERY,
variables: {
projectId: Number(addedProjectState.id),
networkId: chainId,
address: contractAddress,
recipientAddress,
txHash: hash,
},
});
}
} catch (error) {
console.error('Error Contract', error);
}
};

const AlloProtocolModal: FC<IAlloProtocolModal> = ({
setShowModal,
addedProjectState,
Expand Down
22 changes: 16 additions & 6 deletions src/components/views/create/CreateProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '@/apollo/gql/gqlProjects';
import {
EProjectSocialMediaType,
IAnchorContractData,
IProject,
IProjectCreation,
IProjectEdition,
Expand Down Expand Up @@ -59,6 +60,7 @@ import SocialMedias from './SocialMediaBox/SocialMedias';
import { CreateHeader } from './CreateHeader';

const ALL_CHAINS = config.CHAINS;
export const STOP_RECURRING_SETUP_ON_CREATION = true;

interface ICreateProjectProps {
project?: IProjectEdition;
Expand Down Expand Up @@ -405,7 +407,12 @@ const CreateProject: FC<ICreateProjectProps> = ({ project }) => {
if (addedProject) {
// Success

if (watchAlloProtocolRegistry && hasOptimismAddress && !draft) {
if (
!STOP_RECURRING_SETUP_ON_CREATION &&
watchAlloProtocolRegistry &&
hasOptimismAddress &&
!draft
) {
setShowAlloProtocolModal(true);
localStorage.removeItem(StorageLabel.CREATE_PROJECT_FORM);
} else {
Expand Down Expand Up @@ -449,7 +456,6 @@ const CreateProject: FC<ICreateProjectProps> = ({ project }) => {
localStorage.removeItem(StorageLabel.CREATE_PROJECT_FORM);
}
};

return (
<>
<CreateHeader
Expand Down Expand Up @@ -530,6 +536,7 @@ const CreateProject: FC<ICreateProjectProps> = ({ project }) => {
{ALL_CHAINS.map(chain => (
<AddressInterface
key={chain.id}
project={project as IProject}
networkId={chain.id}
chainType={
(chain as NonEVMChain).chainType
Expand All @@ -545,10 +552,13 @@ const CreateProject: FC<ICreateProjectProps> = ({ project }) => {
}}
isEditMode={isEditMode}
anchorContractData={
(project?.anchorContracts &&
project
?.anchorContracts[0]) ??
undefined
project?.anchorContracts?.find(
(
contract: IAnchorContractData,
) =>
contract.networkId ===
chain.id,
) ?? undefined
}
/>
))}
Expand Down
1 change: 1 addition & 0 deletions src/config/development.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ const config: EnvConfig = {
gasPreference: {
// Keep it empty for automatic configuration
},
anchorRegistryAddress: '0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3',
chainLogo: (logoSize?: number) => <IconBase size={logoSize} />,
},

Expand Down
1 change: 1 addition & 0 deletions src/config/production.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ const config: EnvConfig = {
gasPreference: {
// Keep it empty for automatic configuration
},
anchorRegistryAddress: '0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3',
subgraphAddress: '',
coingeckoChainName: 'base',
chainLogo: (logoSize = 24) => <IconBase size={logoSize} />,
Expand Down
Loading
Loading