Skip to content

Commit

Permalink
Merge pull request #10344 from hicommonwealth/tim/referral-ce
Browse files Browse the repository at this point in the history
  • Loading branch information
timolegros authored Dec 20, 2024
2 parents 34e959e + 19e18ef commit 1c931b6
Show file tree
Hide file tree
Showing 45 changed files with 763 additions and 514 deletions.
19 changes: 19 additions & 0 deletions common_knowledge/Chain-Events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# EVM Chain Events

There are 2 methods of adding chain events sources. Parent contracts are contracts that we (Common) deploy and child
contracts are contracts deployed by users (e.g. Single Contests). Parent contract addresses are stable (almost never
change) and are therefore hardcoded in `chainConfig.ts`. On the other hand, we cannot know the address of a child
contract ahead of time since it is deployed at runtime. For this reason, child contract sources are stored in the
EvmEventSources table with a reference to the parent contract in the EventRegistry.

These instructions only describe how to ensure events are picked it up by EVM CE _not_ how these events are processed.

## Adding Parent Contract Events
1. Add the contract address in the `factoryContracts` object in `chainConfig.ts`.
2. Add the relevant event signatures in `eventSignatures.ts`.
3. Update the `EventRegistry` object in `eventRegistry.ts` to reference the new contract/event.

## Adding Child Contract Events
1. Add the relevant event signatures in `eventSignatures.ts`.
2. Update the `EventRegistry` object in `eventRegistry.ts` to reference the new events.
3. Ensure that the parent events pertaining to child contract launches create sources in the `EvmEventSources` table.
2 changes: 2 additions & 0 deletions libs/evm-protocols/src/common-protocol/chainConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type factoryContractsType = {
launchpad?: string;
lpBondingCurve?: string;
tokenCommunityManager?: string;
referralFeeManager?: string;
chainId: number;
};
};
Expand All @@ -50,6 +51,7 @@ export const factoryContracts = {
launchpad: '0xc6e7B0AdDf35AE4a5A65bb3bCb78D11Db6c8fB8F',
lpBondingCurve: '0x2ECc0af0e4794F0Ab4797549a5a8cf97688D7D21',
tokenCommunityManager: '0xC8fe1F23AbC4Eb55f4aa9E52dAFa3761111CF03a',
referralFeeManager: '0xdc07fEaf01666B7f5dED2F59D895543Ed3FAE1cA',
chainId: 84532,
},
[ValidChains.Blast]: {
Expand Down
15 changes: 15 additions & 0 deletions libs/evm-protocols/src/event-registry/eventRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ type ContractAddresses = {
? 'tokenCommunityManager' extends keyof (typeof factoryContracts)[key]
? (typeof factoryContracts)[key]['tokenCommunityManager']
: never
: never)
| (key extends keyof typeof factoryContracts
? 'referralFeeManager' extends keyof (typeof factoryContracts)[key]
? (typeof factoryContracts)[key]['referralFeeManager']
: never
: never);
};

Expand Down Expand Up @@ -103,6 +108,14 @@ const tokenCommunityManagerSource: ContractSource = {
eventSignatures: [],
} satisfies ContractSource;

const referralFeeManagerSource: ContractSource = {
abi: tokenCommunityManagerAbi,
eventSignatures: [
EvmEventSignatures.Referrals.ReferralSet,
EvmEventSignatures.Referrals.ReferralSet,
],
};

/**
* Note that this object does not contain details for contracts deployed by users
* at runtime. Those contracts remain in the EvmEventSources table.
Expand All @@ -121,6 +134,8 @@ export const EventRegistry = {
lpBondingCurveSource,
[factoryContracts[ValidChains.SepoliaBase].tokenCommunityManager]:
tokenCommunityManagerSource,
[factoryContracts[ValidChains.SepoliaBase].referralFeeManager]:
referralFeeManagerSource,
},
[ValidChains.Sepolia]: {
[factoryContracts[ValidChains.Sepolia].factory]: namespaceFactorySource,
Expand Down
6 changes: 6 additions & 0 deletions libs/evm-protocols/src/event-registry/eventSignatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export const EvmEventSignatures = {
TokenRegistered:
'0xc2fe88a1a3c1957424571593960b97f158a519d0aa4cef9e13a247c64f1f4c35',
},
Referrals: {
ReferralSet:
'0xdf63218877cb126f6c003f2b7f77327674cd6a0b53ad51deac392548ec12b0ed',
FeeDistributed:
'0xadecf9f6e10f953395058158f0e6e399835cf1d045bbed7ecfa82947ecc0a368',
},
} as const;

type Values<T> = T[keyof T];
Expand Down
2 changes: 1 addition & 1 deletion libs/model/src/community/CreateCommunity.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export function CreateCommunity(): Command<typeof schemas.CreateCommunity> {
event_payload: {
community_id: id,
user_id: actor.user.id!,
referral_link: payload.referral_link,
referrer_address: payload.referrer_address,
created_at: created.created_at!,
},
},
Expand Down
2 changes: 1 addition & 1 deletion libs/model/src/community/JoinCommunity.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function JoinCommunity(): Command<typeof schemas.JoinCommunity> {
event_payload: {
community_id,
user_id: actor.user.id!,
referral_link: payload.referral_link,
referrer_address: payload.referrer_address,
created_at: created.created_at!,
},
},
Expand Down
12 changes: 0 additions & 12 deletions libs/model/src/models/associations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,6 @@ export const buildAssociations = (db: DB) => {
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
})
.withMany(db.Referral, {
foreignKey: 'referrer_id',
asOne: 'referrer',
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
})
.withMany(db.Referral, {
foreignKey: 'referee_id',
asOne: 'referee',
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
})
.withMany(db.XpLog, {
foreignKey: 'user_id',
onDelete: 'CASCADE',
Expand Down
2 changes: 2 additions & 0 deletions libs/model/src/models/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import ProfileTags from './profile_tags';
import { Quest, QuestAction, QuestActionMeta } from './quest';
import Reaction from './reaction';
import { Referral } from './referral';
import { ReferralFee } from './referral_fee';
import SsoToken from './sso_token';
import StakeTransaction from './stake_transaction';
import StarredCommunity from './starred_community';
Expand Down Expand Up @@ -78,6 +79,7 @@ export const Factories = {
QuestActionMeta,
Reaction,
Referral,
ReferralFee,
SsoToken,
StakeTransaction,
StarredCommunity,
Expand Down
2 changes: 2 additions & 0 deletions libs/model/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export * from './outbox';
export * from './poll';
export * from './profile_tags';
export * from './reaction';
export * from './referral';
export * from './referral_fee';
export * from './role';
export * from './role_assignment';
export * from './sso_token';
Expand Down
53 changes: 43 additions & 10 deletions libs/model/src/models/referral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,62 @@ export const Referral = (
sequelize.define<ReferralInstance>(
'Referral',
{
referrer_id: {
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
referee_id: {
eth_chain_id: {
type: Sequelize.INTEGER,
allowNull: true,
},
transaction_hash: {
type: Sequelize.STRING,
allowNull: true,
},
namespace_address: {
type: Sequelize.STRING,
allowNull: true,
},
referee_address: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
},
event_name: {
referrer_address: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
},
event_payload: { type: Sequelize.JSONB, allowNull: false },
created_at: { type: Sequelize.DATE, allowNull: true, primaryKey: true },
referrer_received_eth_amount: {
type: Sequelize.FLOAT,
allowNull: false,
defaultValue: 0,
},
created_on_chain_timestamp: {
type: Sequelize.INTEGER,
allowNull: true,
},
created_off_chain_at: {
type: Sequelize.DATE,
allowNull: false,
},
updated_at: {
type: Sequelize.DATE,
allowNull: false,
},
},
{
timestamps: true,
createdAt: 'created_at',
updatedAt: false,
createdAt: 'created_off_chain_at',
updatedAt: 'updated_at',
underscored: true,
tableName: 'Referrals',
indexes: [
{ fields: ['referee_address'] },
{ fields: ['referrer_address'] },
{
fields: ['eth_chain_id', 'transaction_hash'],
unique: true,
},
],
},
);
49 changes: 49 additions & 0 deletions libs/model/src/models/referral_fee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as schemas from '@hicommonwealth/schemas';
import Sequelize from 'sequelize';
import { z } from 'zod';
import type { ModelInstance } from './types';

export type ReferralFeesAttributes = z.infer<typeof schemas.ReferralFees>;
export type ReferralFeesInstance = ModelInstance<ReferralFeesAttributes>;

export const ReferralFee = (
sequelize: Sequelize.Sequelize,
): Sequelize.ModelStatic<ReferralFeesInstance> =>
sequelize.define<ReferralFeesInstance>(
'ReferralFee',
{
eth_chain_id: {
type: Sequelize.INTEGER,
primaryKey: true,
},
transaction_hash: {
type: Sequelize.STRING,
primaryKey: true,
},
namespace_address: {
type: Sequelize.STRING,
allowNull: false,
},
distributed_token_address: {
type: Sequelize.STRING,
allowNull: false,
},
referrer_recipient_address: {
type: Sequelize.STRING,
allowNull: false,
},
referrer_received_amount: {
type: Sequelize.FLOAT,
allowNull: false,
},
transaction_timestamp: {
type: Sequelize.INTEGER,
allowNull: false,
},
},
{
timestamps: false,
underscored: true,
tableName: 'ReferralFees',
},
);
6 changes: 5 additions & 1 deletion libs/model/src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ export default (sequelize: Sequelize.Sequelize): UserModelStatic =>
selected_community_id: { type: Sequelize.STRING, allowNull: true },
profile: { type: Sequelize.JSONB, allowNull: false },
xp_points: { type: Sequelize.INTEGER, defaultValue: 0, allowNull: true },
referral_link: { type: Sequelize.STRING, allowNull: true },
referral_eth_earnings: {
type: Sequelize.FLOAT,
allowNull: false,
defaultValue: 0,
},
},
{
timestamps: true,
Expand Down
12 changes: 12 additions & 0 deletions libs/model/src/policies/chainEventCreatedPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { systemActor } from '../middleware';
import { CreateLaunchpadToken } from '../token/CreateToken.command';
import { handleCommunityStakeTrades } from './handleCommunityStakeTrades';
import { handleLaunchpadTrade } from './handleLaunchpadTrade';
import { handleReferralFeeDistributed } from './handleReferralFeeDistributed';
import { handleReferralSet } from './handleReferralSet';

const log = logger(import.meta);

Expand Down Expand Up @@ -45,6 +47,16 @@ export const processChainEventCreated: EventHandler<
payload.eventSource.eventSignature === EvmEventSignatures.Launchpad.Trade
) {
await handleLaunchpadTrade(payload);
} else if (
payload.eventSource.eventSignature ===
EvmEventSignatures.Referrals.ReferralSet
) {
await handleReferralSet(payload);
} else if (
payload.eventSource.eventSignature ===
EvmEventSignatures.Referrals.FeeDistributed
) {
await handleReferralFeeDistributed(payload);
} else {
log.error('Attempted to process an unsupported chain-event', undefined, {
event: payload,
Expand Down
13 changes: 2 additions & 11 deletions libs/model/src/policies/handleCommunityStakeTrades.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BigNumber } from 'ethers';
import Web3 from 'web3';
import { z } from 'zod';
import { DB } from '../models';
import { chainNodeMustExist } from './utils';

const log = logger(import.meta);

Expand Down Expand Up @@ -41,17 +42,7 @@ export async function handleCommunityStakeTrades(
return;
}

const chainNode = await models.ChainNode.scope('withPrivateData').findOne({
where: {
eth_chain_id: event.eventSource.ethChainId,
},
});
if (!chainNode) {
log.error('ChainNode associated to chain event not found!', undefined, {
event,
});
return;
}
const chainNode = await chainNodeMustExist(event.eventSource.ethChainId);

if (!chainNode.private_url) {
log.error('ChainNode is missing a private url', undefined, {
Expand Down
12 changes: 2 additions & 10 deletions libs/model/src/policies/handleLaunchpadTrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Web3 from 'web3';
import { z } from 'zod';
import { models } from '../database';
import { commonProtocol } from '../services';
import { chainNodeMustExist } from './utils';

const log = logger(import.meta);

Expand All @@ -32,16 +33,7 @@ export async function handleLaunchpadTrade(
throw new Error('Token not found');
}

const chainNode = await models.ChainNode.scope('withPrivateData').findOne({
where: {
eth_chain_id: event.eventSource.ethChainId,
},
});

if (!chainNode) {
// TODO: throw custom error with no retries -> straight to deadletter
throw new Error('Unsupported chain');
}
const chainNode = await chainNodeMustExist(event.eventSource.ethChainId);

const trade = await models.LaunchpadTrade.findOne({
where: {
Expand Down
Loading

0 comments on commit 1c931b6

Please sign in to comment.