diff --git a/libs/adapters/src/rabbitmq/configuration/rascalConfig.ts b/libs/adapters/src/rabbitmq/configuration/rascalConfig.ts
index 5dc68fc48c6..ebb5352cee8 100644
--- a/libs/adapters/src/rabbitmq/configuration/rascalConfig.ts
+++ b/libs/adapters/src/rabbitmq/configuration/rascalConfig.ts
@@ -234,7 +234,7 @@ export function getAllRascalConfigs(
source: RascalExchanges.MessageRelayer,
destination: RascalQueues.UserReferrals,
destinationType: 'queue',
- bindingKeys: [RascalRoutingKeys.UserReferralsCommunityJoined],
+ bindingKeys: [RascalRoutingKeys.UserReferralsCommunityCreated],
},
[RascalBindings.FarcasterWorkerPolicy]: {
source: RascalExchanges.MessageRelayer,
diff --git a/libs/adapters/src/rabbitmq/types.ts b/libs/adapters/src/rabbitmq/types.ts
index 668f3f31df5..7f3d4ac4676 100644
--- a/libs/adapters/src/rabbitmq/types.ts
+++ b/libs/adapters/src/rabbitmq/types.ts
@@ -92,7 +92,7 @@ export enum RascalRoutingKeys {
XpProjectionCommentUpvoted = EventNames.CommentUpvoted,
XpProjectionUserMentioned = EventNames.UserMentioned,
- UserReferralsCommunityJoined = EventNames.CommunityJoined,
+ UserReferralsCommunityCreated = EventNames.CommunityCreated,
FarcasterWorkerPolicyCastCreated = EventNames.FarcasterCastCreated,
FarcasterWorkerPolicyReplyCastCreated = EventNames.FarcasterReplyCastCreated,
diff --git a/libs/core/src/framework/command.ts b/libs/core/src/framework/command.ts
index 3608a64c012..1ed8c8cb31c 100644
--- a/libs/core/src/framework/command.ts
+++ b/libs/core/src/framework/command.ts
@@ -24,7 +24,7 @@ export const command = async <
try {
const context: Context = {
actor,
- payload: validate ? input.parse(payload) : payload,
+ payload: validate ? await input.parseAsync(payload) : payload,
};
for (const fn of auth) {
await fn(context);
diff --git a/libs/evm-protocols/src/abis/namespaceFactoryAbi.ts b/libs/evm-protocols/src/abis/namespaceFactoryAbi.ts
index cd73105ce57..57dd0bf78f8 100644
--- a/libs/evm-protocols/src/abis/namespaceFactoryAbi.ts
+++ b/libs/evm-protocols/src/abis/namespaceFactoryAbi.ts
@@ -46,6 +46,49 @@ export const namespaceFactoryAbi = [
name: 'DeployedNamespace',
type: 'event',
},
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: false,
+ internalType: 'address',
+ name: 'namespaceAdress',
+ type: 'address',
+ },
+ {
+ indexed: true,
+ internalType: 'address',
+ name: 'feeManager',
+ type: 'address',
+ },
+ {
+ indexed: true,
+ internalType: 'address',
+ name: 'referrer',
+ type: 'address',
+ },
+ {
+ indexed: false,
+ internalType: 'address',
+ name: 'referralFeeManager',
+ type: 'address',
+ },
+ {
+ indexed: false,
+ internalType: 'bytes',
+ name: 'signature',
+ type: 'bytes',
+ },
+ {
+ indexed: false,
+ internalType: 'address',
+ name: 'namespaceDeployer',
+ type: 'address',
+ },
+ ],
+ name: 'DeployedNamespaceWithReferral',
+ type: 'event',
+ },
{
anonymous: false,
inputs: [
diff --git a/libs/evm-protocols/src/event-registry/eventRegistry.ts b/libs/evm-protocols/src/event-registry/eventRegistry.ts
index fa73ac4f329..1c722a18f54 100644
--- a/libs/evm-protocols/src/event-registry/eventRegistry.ts
+++ b/libs/evm-protocols/src/event-registry/eventRegistry.ts
@@ -68,6 +68,7 @@ const namespaceFactorySource = {
eventSignatures: [
EvmEventSignatures.NamespaceFactory.ContestManagerDeployed,
EvmEventSignatures.NamespaceFactory.NamespaceDeployed,
+ EvmEventSignatures.NamespaceFactory.NamespaceDeployedWithReferral,
],
childContracts: {
[ChildContractNames.RecurringContest]: {
diff --git a/libs/evm-protocols/src/event-registry/eventSignatures.ts b/libs/evm-protocols/src/event-registry/eventSignatures.ts
index 74640b29a53..7ad074fb034 100644
--- a/libs/evm-protocols/src/event-registry/eventSignatures.ts
+++ b/libs/evm-protocols/src/event-registry/eventSignatures.ts
@@ -17,6 +17,8 @@ export const EvmEventSignatures = {
NamespaceFactory: {
NamespaceDeployed:
'0x8870ba2202802ce285ce6bead5ac915b6dc2d35c8a9d6f96fa56de9de12829d5',
+ NamespaceDeployedWithReferral:
+ '0x2f5d04158abd2b403eb3b099bf1257e7949197015ef7d19db38b2c45f9e0d164',
ContestManagerDeployed:
'0x990f533044dbc89b838acde9cd2c72c400999871cf8f792d731edcae15ead693',
CommunityNamespaceCreated:
diff --git a/libs/model/src/community/CreateCommunity.command.ts b/libs/model/src/community/CreateCommunity.command.ts
index f7f806f3f68..513a0416dce 100644
--- a/libs/model/src/community/CreateCommunity.command.ts
+++ b/libs/model/src/community/CreateCommunity.command.ts
@@ -106,6 +106,12 @@ export function CreateCommunity(): Command {
else if (base === ChainBase.CosmosSDK && !node.cosmos_chain_id)
throw new InvalidInput(CreateCommunityErrors.CosmosChainNameRequired);
+ const user = await models.User.findOne({
+ where: { id: actor.user.id },
+ attributes: ['id', 'referred_by_address'],
+ });
+ mustExist('User', user);
+
// == command transaction boundary ==
await models.sequelize.transaction(async (transaction) => {
await models.Community.create(
@@ -148,7 +154,7 @@ export function CreateCommunity(): Command {
const created = await models.Address.create(
{
- user_id: actor.user.id,
+ user_id: user.id,
address: admin_address.address,
community_id: id,
hex:
@@ -176,8 +182,8 @@ export function CreateCommunity(): Command {
event_name: schemas.EventNames.CommunityCreated,
event_payload: {
community_id: id,
- user_id: actor.user.id!,
- referrer_address: payload.referrer_address,
+ user_id: user.id!,
+ referrer_address: user.referred_by_address ?? undefined,
created_at: created.created_at!,
},
},
diff --git a/libs/model/src/community/GetMembers.query.ts b/libs/model/src/community/GetMembers.query.ts
index f2eb486dbb1..7cae9720536 100644
--- a/libs/model/src/community/GetMembers.query.ts
+++ b/libs/model/src/community/GetMembers.query.ts
@@ -4,10 +4,10 @@ import { QueryTypes } from 'sequelize';
import { z } from 'zod';
import { models } from '../database';
-const buildOrderBy = (
- by: 'name' | 'referrals' | 'earnings' | string,
- direction: 'ASC' | 'DESC',
-) => {
+type OrderBy = 'name' | 'last_active' | 'referrals' | 'earnings';
+type OrderDirection = 'ASC' | 'DESC';
+
+const buildOrderBy = (by: OrderBy, direction: OrderDirection) => {
switch (by) {
case 'name':
return `profile_name ${direction}`;
@@ -101,7 +101,8 @@ const buildFilteredQuery = (
};
function membersSqlWithoutSearch(
- orderBy: string,
+ by: OrderBy,
+ direction: OrderDirection,
limit: number,
offset: number,
) {
@@ -112,6 +113,15 @@ function membersSqlWithoutSearch(
U.profile->>'name' AS profile_name,
U.profile->>'avatar_url' AS avatar_url,
U.created_at,
+ (
+ SELECT JSON_BUILD_OBJECT(
+ 'user_id', RU.id,
+ 'profile_name', RU.profile->>'name',
+ 'avatar_url', RU.profile->>'avatar_url'
+ )
+ FROM "Addresses" RA JOIN "Users" RU ON RA.user_id = RU.id
+ WHERE RA.address = U.referred_by_address LIMIT 1
+ ) as referred_by,
COALESCE(U.referral_count, 0) AS referral_count,
COALESCE(U.referral_eth_earnings, 0) AS referral_eth_earnings,
MAX(COALESCE(A.last_active, U.created_at)) AS last_active,
@@ -120,15 +130,7 @@ function membersSqlWithoutSearch(
'address', A.address,
'community_id', A.community_id,
'role', A.role,
- 'stake_balance', 0, -- TODO: project stake balance here
- 'referred_by', (SELECT
- JSON_BUILD_OBJECT(
- 'user_id', RU.id,
- 'profile_name', RU.profile->>'name',
- 'avatar_url', RU.profile->>'avatar_url'
- )
- FROM "Addresses" RA JOIN "Users" RU on RA.user_id = RU.id
- WHERE RA.address = A.referred_by_address LIMIT 1)
+ 'stake_balance', 0 -- TODO: project stake balance here
)) AS addresses,
COALESCE(ARRAY_AGG(M.group_id) FILTER (WHERE M.group_id IS NOT NULL), '{}') AS group_ids,
T.total
@@ -138,15 +140,21 @@ function membersSqlWithoutSearch(
JOIN T ON TRUE
WHERE
A.community_id = :community_id
+ ${
+ by === 'referrals' || by === 'earnings'
+ ? 'AND COALESCE(U.referral_count, 0) + COALESCE(U.referral_eth_earnings, 0) > 0'
+ : ''
+ }
GROUP BY U.id, T.total
- ORDER BY ${orderBy}
+ ORDER BY ${buildOrderBy(by, direction)}
LIMIT ${limit} OFFSET ${offset};
`;
}
function membersSqlWithSearch(
cte: string,
- orderBy: string,
+ by: OrderBy,
+ direction: OrderDirection,
limit: number,
offset: number,
) {
@@ -157,6 +165,15 @@ function membersSqlWithSearch(
U.profile->>'name' AS profile_name,
U.profile->>'avatar_url' AS avatar_url,
U.created_at,
+ (
+ SELECT JSON_BUILD_OBJECT(
+ 'user_id', RU.id,
+ 'profile_name', RU.profile->>'name',
+ 'avatar_url', RU.profile->>'avatar_url'
+ )
+ FROM "Addresses" RA JOIN "Users" RU ON RA.user_id = RU.id
+ WHERE RA.address = U.referred_by_address LIMIT 1
+ ) AS referred_by,
COALESCE(U.referral_count, 0) AS referral_count,
COALESCE(U.referral_eth_earnings, 0) AS referral_eth_earnings,
MAX(COALESCE(A.last_active, U.created_at)) AS last_active,
@@ -165,15 +182,7 @@ function membersSqlWithSearch(
'address', A.address,
'community_id', A.community_id,
'role', A.role,
- 'stake_balance', 0, -- TODO: project stake balance here
- 'referred_by', (SELECT
- JSON_BUILD_OBJECT(
- 'user_id', RU.id,
- 'profile_name', RU.profile->>'name',
- 'avatar_url', RU.profile->>'avatar_url'
- )
- FROM "Addresses" RA JOIN "Users" RU on RA.user_id = RU.id
- WHERE RA.address = A.referred_by_address LIMIT 1)
+ 'stake_balance', 0 -- TODO: project stake balance here
)) AS addresses,
COALESCE(ARRAY_AGG(M.group_id) FILTER (WHERE M.group_id IS NOT NULL), '{}') AS group_ids,
T.total
@@ -182,8 +191,13 @@ function membersSqlWithSearch(
JOIN "Users" U ON A.user_id = U.id
LEFT JOIN "Memberships" M ON A.id = M.address_id AND M.reject_reason IS NULL
JOIN T ON TRUE
+ ${
+ by === 'referrals' || by === 'earnings'
+ ? 'WHERE COALESCE(U.referral_count, 0) + COALESCE(U.referral_eth_earnings, 0) > 0'
+ : ''
+ }
GROUP BY U.id, T.total
- ORDER BY ${orderBy}
+ ORDER BY ${buildOrderBy(by, direction)}
LIMIT ${limit} OFFSET ${offset};
`;
}
@@ -214,10 +228,8 @@ export function GetMembers(): Query {
addresses,
};
- const orderBy = buildOrderBy(
- order_by ?? 'name',
- order_direction ?? 'DESC',
- );
+ const by = order_by ?? 'name';
+ const direction = order_direction ?? 'DESC';
const sql =
search || memberships || addresses.length > 0
@@ -226,11 +238,12 @@ export function GetMembers(): Query {
search ?? '',
buildFilters(memberships ?? '', addresses),
),
- orderBy,
+ by,
+ direction,
limit,
offset,
)
- : membersSqlWithoutSearch(orderBy, limit, offset);
+ : membersSqlWithoutSearch(by, direction, limit, offset);
const members = await models.sequelize.query<
z.infer & { total?: number }
diff --git a/libs/model/src/community/JoinCommunity.command.ts b/libs/model/src/community/JoinCommunity.command.ts
index e8fdc583692..89a4f9fac47 100644
--- a/libs/model/src/community/JoinCommunity.command.ts
+++ b/libs/model/src/community/JoinCommunity.command.ts
@@ -98,7 +98,6 @@ export function JoinCommunity(): Command {
is_user_default: false,
ghost_address: false,
is_banned: false,
- referred_by_address: payload.referrer_address,
},
{ transaction },
);
@@ -115,7 +114,6 @@ export function JoinCommunity(): Command {
event_payload: {
community_id,
user_id: actor.user.id!,
- referrer_address: payload.referrer_address,
created_at: created.created_at!,
},
},
diff --git a/libs/model/src/models/address.ts b/libs/model/src/models/address.ts
index be524f684f7..aa8e273340c 100644
--- a/libs/model/src/models/address.ts
+++ b/libs/model/src/models/address.ts
@@ -46,7 +46,6 @@ export default (
created_at: { type: Sequelize.DATE, allowNull: false },
updated_at: { type: Sequelize.DATE, allowNull: false },
user_id: { type: Sequelize.INTEGER, allowNull: true },
- referred_by_address: { type: Sequelize.STRING, allowNull: true },
ghost_address: {
type: Sequelize.BOOLEAN,
allowNull: false,
diff --git a/libs/model/src/models/referral_fee.ts b/libs/model/src/models/referral_fee.ts
index 78d5c80382d..e89c9f26e72 100644
--- a/libs/model/src/models/referral_fee.ts
+++ b/libs/model/src/models/referral_fee.ts
@@ -36,6 +36,10 @@ export const ReferralFee = (
type: Sequelize.FLOAT,
allowNull: false,
},
+ referee_address: {
+ type: Sequelize.STRING,
+ allowNull: false,
+ },
transaction_timestamp: {
type: Sequelize.BIGINT,
allowNull: false,
diff --git a/libs/model/src/models/user.ts b/libs/model/src/models/user.ts
index 207eb1641de..9e07cd4a367 100644
--- a/libs/model/src/models/user.ts
+++ b/libs/model/src/models/user.ts
@@ -75,6 +75,7 @@ export default (sequelize: Sequelize.Sequelize): UserModelStatic =>
profile: { type: Sequelize.JSONB, allowNull: false },
xp_points: { type: Sequelize.INTEGER, defaultValue: 0, allowNull: true },
unsubscribe_uuid: { type: Sequelize.STRING, allowNull: true },
+ referred_by_address: { type: Sequelize.STRING, allowNull: true },
referral_count: {
type: Sequelize.INTEGER,
defaultValue: 0,
diff --git a/libs/model/src/policies/ChainEventCreated.policy.ts b/libs/model/src/policies/ChainEventCreated.policy.ts
index 5f06f296749..1703f67eba6 100644
--- a/libs/model/src/policies/ChainEventCreated.policy.ts
+++ b/libs/model/src/policies/ChainEventCreated.policy.ts
@@ -7,8 +7,8 @@ import { systemActor } from '../middleware';
import { CreateLaunchpadToken } from '../token/CreateToken.command';
import { handleCommunityStakeTrades } from './handlers/handleCommunityStakeTrades';
import { handleLaunchpadTrade } from './handlers/handleLaunchpadTrade';
+import { handleNamespaceDeployedWithReferral } from './handlers/handleNamespaceDeployedWithReferral';
import { handleReferralFeeDistributed } from './handlers/handleReferralFeeDistributed';
-import { handleReferralSet } from './handlers/handleReferralSet';
const log = logger(import.meta);
@@ -17,8 +17,12 @@ export const processChainEventCreated: EventHandler<
ZodUndefined
> = async ({ payload }) => {
switch (payload.eventSource.eventSignature) {
+ case EvmEventSignatures.NamespaceFactory.NamespaceDeployedWithReferral:
+ await handleNamespaceDeployedWithReferral(payload);
+ break;
+
case EvmEventSignatures.CommunityStake.Trade:
- await handleCommunityStakeTrades(models, payload);
+ await handleCommunityStakeTrades(payload);
break;
case EvmEventSignatures.Launchpad.TokenLaunched: {
@@ -43,7 +47,7 @@ export const processChainEventCreated: EventHandler<
break;
case EvmEventSignatures.Referrals.ReferralSet:
- await handleReferralSet(payload);
+ // await handleReferralSet(payload);
break;
case EvmEventSignatures.Referrals.FeeDistributed:
diff --git a/libs/model/src/policies/handlers/handleCommunityStakeTrades.ts b/libs/model/src/policies/handlers/handleCommunityStakeTrades.ts
index 0ff9736213a..5376a6e1908 100644
--- a/libs/model/src/policies/handlers/handleCommunityStakeTrades.ts
+++ b/libs/model/src/policies/handlers/handleCommunityStakeTrades.ts
@@ -3,13 +3,12 @@ import { getStakeTradeInfo } from '@hicommonwealth/evm-protocols';
import { chainEvents, events } from '@hicommonwealth/schemas';
import { BigNumber } from 'ethers';
import { z } from 'zod';
-import { DB } from '../../models';
+import { models } from '../../database';
import { chainNodeMustExist } from '../utils/utils';
const log = logger(import.meta);
export async function handleCommunityStakeTrades(
- models: DB,
event: z.infer,
) {
const {
diff --git a/libs/model/src/policies/handlers/handleNamespaceDeployedWithReferral.ts b/libs/model/src/policies/handlers/handleNamespaceDeployedWithReferral.ts
new file mode 100644
index 00000000000..c9ed1c88266
--- /dev/null
+++ b/libs/model/src/policies/handlers/handleNamespaceDeployedWithReferral.ts
@@ -0,0 +1,75 @@
+import { chainEvents, events } from '@hicommonwealth/schemas';
+import { z } from 'zod';
+import { models } from '../../database';
+
+async function setReferral(
+ timestamp: number,
+ eth_chain_id: number,
+ transaction_hash: string,
+ namespace_address: string,
+ referee_address: string,
+ referrer_address: string,
+ log_removed: boolean,
+) {
+ const existingReferral = await models.Referral.findOne({
+ where: { referee_address, referrer_address },
+ });
+ if (existingReferral) {
+ if (
+ existingReferral.transaction_hash === transaction_hash &&
+ existingReferral.eth_chain_id === eth_chain_id
+ ) {
+ // found with txn but removed from chain
+ if (log_removed)
+ await existingReferral.update({
+ eth_chain_id: null,
+ transaction_hash: null,
+ namespace_address: null,
+ created_on_chain_timestamp: null,
+ });
+ } else {
+ // found with partial or outdated chain details
+ await existingReferral.update({
+ eth_chain_id,
+ transaction_hash,
+ namespace_address,
+ created_on_chain_timestamp: Number(timestamp),
+ });
+ }
+ } else
+ await models.Referral.create({
+ eth_chain_id,
+ transaction_hash,
+ namespace_address,
+ referee_address,
+ referrer_address,
+ referrer_received_eth_amount: 0,
+ created_on_chain_timestamp: Number(timestamp),
+ });
+}
+
+export async function handleNamespaceDeployedWithReferral(
+ event: z.infer,
+) {
+ const {
+ 0: namespace_address,
+ // 1: fee_manager_address,
+ 2: referrer_address,
+ // 3: referral_fee_manager_contract_address,
+ // 4: signature,
+ 5: referee_address,
+ } = event.parsedArgs as z.infer<
+ typeof chainEvents.NamespaceDeployedWithReferral
+ >;
+
+ if (referrer_address)
+ await setReferral(
+ event.block.timestamp,
+ event.eventSource.ethChainId,
+ event.rawLog.transactionHash,
+ namespace_address,
+ referee_address,
+ referrer_address,
+ event.rawLog.removed,
+ );
+}
diff --git a/libs/model/src/policies/handlers/handleReferralFeeDistributed.ts b/libs/model/src/policies/handlers/handleReferralFeeDistributed.ts
index 873812a904d..71d443d13e7 100644
--- a/libs/model/src/policies/handlers/handleReferralFeeDistributed.ts
+++ b/libs/model/src/policies/handlers/handleReferralFeeDistributed.ts
@@ -8,11 +8,11 @@ export async function handleReferralFeeDistributed(
event: z.infer,
) {
const {
- 0: namespaceAddress,
- 1: tokenAddress,
- // 2: totalAmountDistributed,
- 3: referrerAddress,
- 4: referrerReceivedAmount,
+ 0: namespace_address,
+ 1: distributed_token_address,
+ // 2: total_amount_distributed,
+ 3: referrer_address,
+ 4: fee_amount,
} = event.parsedArgs as z.infer;
const existingFee = await models.ReferralFee.findOne({
@@ -21,53 +21,54 @@ export async function handleReferralFeeDistributed(
transaction_hash: event.rawLog.transactionHash,
},
});
-
- if (event.rawLog.removed && existingFee) {
- await existingFee.destroy();
+ if (existingFee) {
+ event.rawLog.removed && (await existingFee.destroy());
return;
- } else if (existingFee) return;
+ }
+
+ // find the referral (already mapped to a namespace)
+ const referral = await models.Referral.findOne({
+ where: {
+ referrer_address,
+ namespace_address,
+ },
+ });
+ if (!referral) return; // we must guarantee the order of chain events here
- const feeAmount =
- Number(BigNumber.from(referrerReceivedAmount).toBigInt()) / 1e18;
+ const referrer_received_amount =
+ Number(BigNumber.from(fee_amount).toBigInt()) / 1e18;
await models.sequelize.transaction(async (transaction) => {
await models.ReferralFee.create(
{
eth_chain_id: event.eventSource.ethChainId,
transaction_hash: event.rawLog.transactionHash,
- namespace_address: namespaceAddress,
- distributed_token_address: tokenAddress,
- referrer_recipient_address: referrerAddress,
- referrer_received_amount: feeAmount,
+ namespace_address,
+ distributed_token_address,
+ referrer_recipient_address: referrer_address,
+ referrer_received_amount,
+ referee_address: referral.referee_address,
transaction_timestamp: Number(event.block.timestamp),
},
{ transaction },
);
// if native token i.e. ETH
- if (tokenAddress === ZERO_ADDRESS) {
- const userAddress = await models.Address.findOne({
- where: {
- address: referrerAddress,
- },
+ if (distributed_token_address === ZERO_ADDRESS) {
+ const referrer = await models.Address.findOne({
+ where: { address: referrer_address },
transaction,
});
- if (userAddress) {
+ if (referrer) {
await models.User.increment('referral_eth_earnings', {
- by: feeAmount,
- where: {
- id: userAddress.user_id!,
- },
+ by: referrer_received_amount,
+ where: { id: referrer.user_id! },
transaction,
});
}
- await models.Referral.increment('referrer_received_eth_amount', {
- by: feeAmount,
- where: {
- referrer_address: referrerAddress,
- referee_address: event.rawLog.address,
- },
+ await referral.increment('referrer_received_eth_amount', {
+ by: referrer_received_amount,
transaction,
});
}
diff --git a/libs/model/src/policies/handlers/handleReferralSet.ts b/libs/model/src/policies/handlers/handleReferralSet.ts
index f554d3703bf..f5c1c985d03 100644
--- a/libs/model/src/policies/handlers/handleReferralSet.ts
+++ b/libs/model/src/policies/handlers/handleReferralSet.ts
@@ -2,6 +2,7 @@ import { chainEvents, events } from '@hicommonwealth/schemas';
import { z } from 'zod';
import { models } from '../../database';
+// TODO: remove this handler since it's redundant with handleNamespaceDeployed
export async function handleReferralSet(
event: z.infer,
) {
diff --git a/libs/model/src/user/GetUserProfile.query.ts b/libs/model/src/user/GetUserProfile.query.ts
index 94fbc19a539..1096f359c08 100644
--- a/libs/model/src/user/GetUserProfile.query.ts
+++ b/libs/model/src/user/GetUserProfile.query.ts
@@ -16,7 +16,13 @@ export function GetUserProfile(): Query {
const user = await models.User.findOne({
where: { id: user_id },
- attributes: ['profile', 'xp_points'],
+ attributes: [
+ 'profile',
+ 'referred_by_address',
+ 'referral_count',
+ 'referral_eth_earnings',
+ 'xp_points',
+ ],
});
mustExist('User', user);
@@ -105,6 +111,9 @@ export function GetUserProfile(): Query {
isOwner: actor.user?.id === user_id,
// ensure Tag is present in typed response
tags: profileTags.map((t) => ({ id: t.Tag!.id!, name: t.Tag!.name })),
+ referred_by_address: user!.referred_by_address,
+ referral_count: user!.referral_count ?? 0,
+ referral_eth_earnings: user!.referral_eth_earnings ?? 0,
xp_points: user!.xp_points ?? 0,
};
},
diff --git a/libs/model/src/user/GetUserReferralFees.query.ts b/libs/model/src/user/GetUserReferralFees.query.ts
index 5045bb92b73..9c55e5fe128 100644
--- a/libs/model/src/user/GetUserReferralFees.query.ts
+++ b/libs/model/src/user/GetUserReferralFees.query.ts
@@ -17,28 +17,34 @@ export function GetUserReferralFees(): Query<
>(
`
WITH
-referrer_addresses AS (
- SELECT DISTINCT address
- FROM "Addresses"
+R AS (
+ SELECT DISTINCT address FROM "Addresses"
WHERE user_id = :user_id AND address LIKE '0x%'
)
SELECT
- eth_chain_id,
- transaction_hash,
- namespace_address,
- distributed_token_address,
- referrer_recipient_address,
- referrer_received_amount,
- CAST(transaction_timestamp AS DOUBLE PRECISION) as transaction_timestamp
-FROM "ReferralFees"
-WHERE referrer_recipient_address IN (SELECT * FROM referrer_addresses);
+ F.eth_chain_id,
+ F.transaction_hash,
+ F.namespace_address,
+ F.distributed_token_address,
+ F.referrer_recipient_address,
+ F.referrer_received_amount,
+ CAST(F.transaction_timestamp AS DOUBLE PRECISION) as transaction_timestamp,
+ F.referee_address,
+ C.id AS community_id,
+ C.name AS community_name,
+ C.icon_url AS community_icon_url,
+ U.profile AS referee_profile
+FROM
+ "ReferralFees" F
+ JOIN R ON F.referrer_recipient_address = R.address
+ LEFT JOIN "Communities" C ON F.namespace_address = C.namespace_address
+ LEFT JOIN "Addresses" A ON A.community_id = C.id AND A.address = F.referee_address
+ LEFT JOIN "Users" U ON U.id = A.user_id;
`,
{
type: QueryTypes.SELECT,
raw: true,
- replacements: {
- user_id: actor.user.id,
- },
+ replacements: { user_id: actor.user.id },
},
);
},
diff --git a/libs/model/src/user/GetUserReferrals.query.ts b/libs/model/src/user/GetUserReferrals.query.ts
index 31b77c3cf84..274a478a2f1 100644
--- a/libs/model/src/user/GetUserReferrals.query.ts
+++ b/libs/model/src/user/GetUserReferrals.query.ts
@@ -43,10 +43,15 @@ referee_addresses AS (
SELECT
R.*,
U.id as referee_user_id,
- U.profile as referee_profile
+ U.profile as referee_profile,
+ C.id as community_id,
+ C.name as community_name,
+ C.icon_url as community_icon_url
FROM referrals R
JOIN referee_addresses RA ON RA.address = R.referee_address
- JOIN "Users" U ON U.id = RA.user_id;
+ JOIN "Users" U ON U.id = RA.user_id
+ LEFT JOIN "Communities" C ON C.namespace = R.namespace_address
+ ;
`,
{
type: QueryTypes.SELECT,
diff --git a/libs/model/src/user/SignIn.command.ts b/libs/model/src/user/SignIn.command.ts
index ef420b2c2e2..e43b6c8bb7c 100644
--- a/libs/model/src/user/SignIn.command.ts
+++ b/libs/model/src/user/SignIn.command.ts
@@ -99,7 +99,11 @@ export function SignIn(): Command {
});
if (!existing) {
const user = await models.User.create(
- { email: null, profile: {} },
+ {
+ email: null,
+ profile: {},
+ referred_by_address: referrer_address,
+ },
{ transaction },
);
if (!user) throw new Error('Failed to create user');
@@ -125,7 +129,6 @@ export function SignIn(): Command {
is_user_default: false,
ghost_address: false,
is_banned: false,
- referred_by_address: referrer_address,
},
transaction,
});
@@ -150,7 +153,6 @@ export function SignIn(): Command {
community_id,
user_id: addr.user_id!,
created_at: addr.created_at!,
- referrer_address,
},
});
new_user &&
diff --git a/libs/model/src/user/UserReferrals.projection.ts b/libs/model/src/user/UserReferrals.projection.ts
index 6e4607ba013..f17358480a4 100644
--- a/libs/model/src/user/UserReferrals.projection.ts
+++ b/libs/model/src/user/UserReferrals.projection.ts
@@ -3,64 +3,61 @@ import { events } from '@hicommonwealth/schemas';
import { models } from '../database';
const inputs = {
- CommunityJoined: events.CommunityJoined,
+ CommunityCreated: events.CommunityCreated,
};
+async function setReferral(
+ user_id: number,
+ community_id: string,
+ referrer_address: string,
+) {
+ const referee = await models.Address.findOne({
+ where: { user_id, community_id },
+ attributes: ['id', 'address'],
+ });
+ if (!referee) return;
+
+ await models.sequelize.transaction(async (transaction) => {
+ await models.Referral.findOrCreate({
+ where: { referee_address: referee.address, referrer_address },
+ defaults: {
+ referee_address: referee.address,
+ referrer_address,
+ referrer_received_eth_amount: 0,
+ },
+ transaction,
+ });
+
+ // increment the referral count of referrer in this community
+ const referrer = await models.User.findOne({
+ include: [
+ {
+ model: models.Address,
+ where: { address: referrer_address },
+ },
+ ],
+ transaction,
+ });
+ referrer &&
+ (await referrer.update(
+ {
+ referral_count: models.sequelize.literal(
+ 'coalesce(referral_count, 0) + 1',
+ ),
+ },
+ { transaction },
+ ));
+ });
+}
+
export function UserReferrals(): Projection {
return {
inputs,
body: {
- CommunityJoined: async ({ payload }) => {
- const { referrer_address } = payload;
+ CommunityCreated: async ({ payload }) => {
+ const { user_id, community_id, referrer_address } = payload;
if (!referrer_address) return;
-
- const refereeAddress = await models.Address.findOne({
- where: {
- user_id: payload.user_id,
- community_id: payload.community_id,
- },
- attributes: ['id', 'address'],
- });
- if (!refereeAddress) return;
-
- await models.sequelize.transaction(async (transaction) => {
- await models.Referral.findOrCreate({
- where: {
- referee_address: refereeAddress.address,
- referrer_address,
- },
- defaults: {
- referee_address: refereeAddress.address,
- referrer_address,
- referrer_received_eth_amount: 0,
- },
- transaction,
- });
-
- // increment the referral count of referrer in this community
- const referrerUser = await models.User.findOne({
- include: [
- {
- model: models.Address,
- attributes: ['id', 'address'],
- where: {
- address: referrer_address,
- community_id: payload.community_id,
- },
- },
- ],
- transaction,
- });
- if (referrerUser)
- await referrerUser.update(
- {
- referral_count: models.sequelize.literal(
- 'coalesce(referral_count, 0) + 1',
- ),
- },
- { transaction },
- );
- });
+ await setReferral(user_id, community_id, referrer_address);
},
},
};
diff --git a/libs/model/src/user/Xp.projection.ts b/libs/model/src/user/Xp.projection.ts
index 896f7cd0521..fa761577eef 100644
--- a/libs/model/src/user/Xp.projection.ts
+++ b/libs/model/src/user/Xp.projection.ts
@@ -224,8 +224,8 @@ export function Xp(): Projection {
const reward_amount = 20;
const creator_reward_weight = 0.2;
- const referee_address = await models.Address.findOne({
- where: { address: payload.address, user_id: payload.user_id },
+ const referee_address = await models.User.findOne({
+ where: { id: payload.user_id },
});
referee_address &&
referee_address.referred_by_address &&
@@ -257,12 +257,15 @@ export function Xp(): Projection {
payload,
'CommunityJoined',
);
+ const user = await models.User.findOne({
+ where: { id: payload.user_id },
+ });
if (action_metas.length > 0) {
await recordXpsForQuest(
payload.user_id,
payload.created_at!,
action_metas,
- payload.referrer_address,
+ user?.referred_by_address,
);
}
},
diff --git a/libs/model/test/referral/referral-lifecycle.spec.ts b/libs/model/test/referral/referral-lifecycle.spec.ts
index 65dbcf77add..55551e0d7c6 100644
--- a/libs/model/test/referral/referral-lifecycle.spec.ts
+++ b/libs/model/test/referral/referral-lifecycle.spec.ts
@@ -2,12 +2,13 @@ import { BigNumber } from '@ethersproject/bignumber';
import { Actor, command, dispose, query } from '@hicommonwealth/core';
import { EvmEventSignatures } from '@hicommonwealth/evm-protocols';
import * as schemas from '@hicommonwealth/schemas';
-import { ZERO_ADDRESS } from '@hicommonwealth/shared';
-import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import { ChainBase, ChainType, ZERO_ADDRESS } from '@hicommonwealth/shared';
+import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { z } from 'zod';
-import { JoinCommunity } from '../../src/community';
+import { CreateCommunity, UpdateCommunity } from '../../src/community';
import { models } from '../../src/database';
import { ChainEventPolicy } from '../../src/policies';
+import { commonProtocol } from '../../src/services';
import { seed } from '../../src/tester';
import { GetUserReferralFees, UserReferrals } from '../../src/user';
import { GetUserReferrals } from '../../src/user/GetUserReferrals.query';
@@ -58,23 +59,27 @@ function chainEvent(
describe('Referral lifecycle', () => {
let admin: Actor;
let nonMember: Actor;
- let community_id: string;
+ let nonMemberUser: z.infer | undefined;
+ let chain_node_id: number;
beforeAll(async () => {
const { actors, base, community } = await seedCommunity({
roles: ['admin', 'member'],
});
admin = actors.admin;
- const [nonMemberUser] = await seed('User', {
+ [nonMemberUser] = await seed('User', {
profile: {
name: 'non-member',
},
+ referred_by_address: admin.address, // referrer
isAdmin: false,
is_welcome_onboard_flow_complete: false,
});
const [nonMemberAddress] = await seed('Address', {
community_id: base!.id!,
user_id: nonMemberUser!.id!,
+ address: '0x0000000000000000000000000000000000001234',
+ verified: true, // must be verified to update community as admin
});
nonMember = {
user: {
@@ -84,25 +89,37 @@ describe('Referral lifecycle', () => {
},
address: nonMemberAddress!.address!,
};
- community_id = community!.id!;
+ chain_node_id = community!.chain_node_id!;
});
afterAll(async () => {
await dispose()();
});
- it('should create a referral when signing in with a referral link', async () => {
- // non-member joins with referral link
- await command(JoinCommunity(), {
+ it('should create referral/fees when referred user creates a community', async () => {
+ // non-member creates a community with a referral link from admin
+ const result = await command(CreateCommunity(), {
actor: nonMember,
payload: {
- community_id,
- referrer_address: admin.address,
+ id: 'referred-community',
+ name: 'Referred Community',
+ description: 'Referred Community Description',
+ default_symbol: 'RC',
+ base: ChainBase.Ethereum,
+ type: ChainType.Offchain,
+ chain_node_id,
+ directory_page_enabled: true,
+ social_links: [],
+ tags: [],
},
});
+ expect(result).toBeTruthy();
+ const community = result?.community;
+ expect(community).toBeTruthy();
+ const community_id = community!.id!;
// creates "partial" platform entries for referrals
- await drainOutbox(['CommunityJoined'], UserReferrals);
+ await drainOutbox(['CommunityCreated'], UserReferrals);
const expectedReferrals: z.infer[] = [
{
@@ -117,6 +134,9 @@ describe('Referral lifecycle', () => {
updated_at: expect.any(Date),
referee_user_id: nonMember.user.id!,
referee_profile: { name: 'non-member' },
+ community_id: null,
+ community_name: null,
+ community_icon_url: null,
},
];
@@ -132,27 +152,41 @@ describe('Referral lifecycle', () => {
});
expect(referrerUser?.referral_count).toBe(1);
- const refereeAddress = await models.Address.findOne({
- where: { user_id: nonMember.user.id, community_id },
- });
- expect(refereeAddress?.referred_by_address).toBe(admin.address);
-
- // simulate on-chain transactions that occur when referees
- // deploy a new namespace with a referral link (ReferralSet)
+ // simulate namespace creation on-chain (From the UI)
const namespaceAddress = '0x0000000000000000000000000000000000000001';
+ const transactionHash = '0x2';
const chainEvents1 = [
chainEvent(
- '0x2',
- nonMember.address!, // referee
- EvmEventSignatures.Referrals.ReferralSet,
+ transactionHash,
+ '0x0000000000000000000000000000000000000002',
+ EvmEventSignatures.NamespaceFactory.NamespaceDeployedWithReferral,
[
namespaceAddress,
+ '0x0000000000000000000000000000000000000004', // fee manager address
admin.address, // referrer
+ '0x0000000000000000000000000000000000000003', // referral fee contract
+ '0x0', // signature
+ nonMember.address!, // referee
],
),
];
await models.Outbox.bulkCreate(chainEvents1);
+ // simulate UI updating the namespace address
+ vi.spyOn(
+ commonProtocol.newNamespaceValidator,
+ 'validateNamespace',
+ ).mockResolvedValue(namespaceAddress);
+ await command(UpdateCommunity(), {
+ actor: nonMember,
+ payload: {
+ community_id,
+ transactionHash,
+ namespace: namespaceAddress,
+ },
+ });
+ vi.restoreAllMocks();
+
// syncs "partial" platform entries for referrals with on-chain transactions
await drainOutbox(['ChainEventCreated'], ChainEventPolicy);
@@ -161,6 +195,9 @@ describe('Referral lifecycle', () => {
expectedReferrals[0].namespace_address = namespaceAddress;
expectedReferrals[0].created_on_chain_timestamp =
chainEvents1[0].event_payload.block.timestamp;
+ expectedReferrals[0].community_id = community!.id;
+ expectedReferrals[0].community_name = community!.name;
+ expectedReferrals[0].community_icon_url = community!.icon_url;
// get referrals again with tx attributes
const referrals2 = await query(GetUserReferrals(), {
@@ -212,6 +249,14 @@ describe('Referral lifecycle', () => {
referrer_recipient_address: admin.address,
referrer_received_amount: fee,
transaction_timestamp: expect.any(Number),
+ referee_address: nonMember.address!,
+ referee_profile: {
+ name: nonMemberUser?.profile.name,
+ avatar_url: nonMemberUser?.profile.avatar_url,
+ },
+ community_id,
+ community_name: community!.name,
+ community_icon_url: community!.icon_url,
},
];
const referralFees = await query(GetUserReferralFees(), {
diff --git a/libs/schemas/src/commands/community.schemas.ts b/libs/schemas/src/commands/community.schemas.ts
index 5a69c15af30..782a908766a 100644
--- a/libs/schemas/src/commands/community.schemas.ts
+++ b/libs/schemas/src/commands/community.schemas.ts
@@ -48,7 +48,6 @@ export const CreateCommunity = {
// hidden optional params
token_name: z.string().optional(),
- referrer_address: z.string().optional(),
// deprecated params to be removed
default_symbol: z.string().max(9),
@@ -342,7 +341,6 @@ export const SelectCommunity = {
export const JoinCommunity = {
input: z.object({
community_id: z.string(),
- referrer_address: z.string().optional(),
}),
output: z.object({
community_id: z.string(),
diff --git a/libs/schemas/src/entities/referral.schemas.ts b/libs/schemas/src/entities/referral.schemas.ts
index aec1724202e..601e89ffd7b 100644
--- a/libs/schemas/src/entities/referral.schemas.ts
+++ b/libs/schemas/src/entities/referral.schemas.ts
@@ -57,6 +57,7 @@ export const ReferralFees = z.object({
referrer_received_amount: z
.number()
.describe('The amount of ETH received by the referrer'),
+ referee_address: z.string().describe('The address of the referee'),
transaction_timestamp: z
.number()
.describe('The timestamp when the referral fee was distributed'),
diff --git a/libs/schemas/src/entities/user.schemas.ts b/libs/schemas/src/entities/user.schemas.ts
index a03d35f79bf..36c6dea4ad6 100644
--- a/libs/schemas/src/entities/user.schemas.ts
+++ b/libs/schemas/src/entities/user.schemas.ts
@@ -52,12 +52,13 @@ export const User = z.object({
is_welcome_onboard_flow_complete: z.boolean().default(false).optional(),
profile: UserProfile,
- xp_points: PG_INT.default(0).nullish(),
unsubscribe_uuid: z.string().uuid().nullish(),
+ referred_by_address: z.string().max(255).nullish(),
referral_count: PG_INT.default(0)
.nullish()
.describe('Number of referrals that have earned ETH'),
referral_eth_earnings: z.number().optional(),
+ xp_points: PG_INT.default(0).nullish(),
ProfileTags: z.array(ProfileTags).optional(),
ApiKey: ApiKey.optional(),
@@ -75,7 +76,6 @@ export const Address = z.object({
verification_token_expires: z.date().nullish(),
verified: z.date().nullish(),
last_active: z.date().nullish(),
- referred_by_address: z.string().max(255).nullish(),
ghost_address: z.boolean().default(false),
wallet_id: z.nativeEnum(WalletId).nullish(),
block_info: z.string().max(255).nullish(),
@@ -111,17 +111,17 @@ export const CommunityMember = z.object({
address: z.string(),
stake_balance: z.number().nullish(),
role: z.enum(Roles),
- referred_by: z
- .object({
- user_id: PG_INT,
- profile_name: z.string().nullish(),
- avatar_url: z.string().nullish(),
- })
- .nullish(),
}),
),
group_ids: z.array(PG_INT),
last_active: z.any().nullish().describe('string or date'),
+ referred_by: z
+ .object({
+ user_id: PG_INT,
+ profile_name: z.string().nullish(),
+ avatar_url: z.string().nullish(),
+ })
+ .nullish(),
referral_count: PG_INT.default(0).nullish(),
referral_eth_earnings: z.number().nullish(),
});
diff --git a/libs/schemas/src/events/chain-event.schemas.ts b/libs/schemas/src/events/chain-event.schemas.ts
index edd5ddc6664..1c539756b86 100644
--- a/libs/schemas/src/events/chain-event.schemas.ts
+++ b/libs/schemas/src/events/chain-event.schemas.ts
@@ -17,11 +17,20 @@ export const CommunityStakeTrade = z.tuple([
export const NamespaceDeployed = z.tuple([
z.string().describe('name'),
- EVM_ADDRESS.describe('_feeManger'),
+ EVM_ADDRESS.describe('_feeManager'),
z.string().describe('_signature'),
EVM_ADDRESS.describe('_namespaceDeployer'),
]);
+export const NamespaceDeployedWithReferral = z.tuple([
+ EVM_ADDRESS.describe('Namespace address'),
+ EVM_ADDRESS.describe('Fee manager address of new namespace'),
+ EVM_ADDRESS.describe('Referrer address (receiving referral fees)'),
+ EVM_ADDRESS.describe('Referral fee manager contract address'),
+ z.string().describe('Optional signature for name reservation validation'),
+ EVM_ADDRESS.describe('Namespace deployer address (referee)'),
+]);
+
export const LaunchpadTokenCreated = z.tuple([
z.string().describe('tokenAddress'),
ETHERS_BIG_NUMBER.describe('totalSupply'),
@@ -45,13 +54,13 @@ export const ReferralSet = z.tuple([
]);
export const ReferralFeeDistributed = z.tuple([
- EVM_ADDRESS.describe('namespace address'),
- EVM_ADDRESS.describe('distributed token address'),
+ EVM_ADDRESS.describe('Namespace address'),
+ EVM_ADDRESS.describe('Distributed token address'),
ETHERS_BIG_NUMBER.describe(
- 'total amount of the token that is distributed (includes protocol fee, referral fee, etc)',
+ 'Total amount of the token that is distributed (includes protocol fee, referral fee, etc)',
),
- EVM_ADDRESS.describe("the referrer's address"),
+ EVM_ADDRESS.describe('Referrer address (recipient)'),
ETHERS_BIG_NUMBER.describe(
- 'the amount of the token that is distributed to the referrer',
+ 'The amount of the token distributed to the referrer',
),
]);
diff --git a/libs/schemas/src/events/events.schemas.ts b/libs/schemas/src/events/events.schemas.ts
index 9a167fccf3f..b7670a4eced 100644
--- a/libs/schemas/src/events/events.schemas.ts
+++ b/libs/schemas/src/events/events.schemas.ts
@@ -12,6 +12,7 @@ import {
LaunchpadTokenCreated,
LaunchpadTrade,
NamespaceDeployed,
+ NamespaceDeployedWithReferral,
ReferralFeeDistributed,
ReferralSet,
} from './chain-event.schemas';
@@ -88,7 +89,6 @@ export const CommunityCreated = z.object({
export const CommunityJoined = z.object({
community_id: z.string(),
user_id: z.number(),
- referrer_address: z.string().nullish(),
created_at: z.coerce.date(),
});
@@ -215,6 +215,14 @@ export const ChainEventCreated = z.union([
}),
parsedArgs: NamespaceDeployed,
}),
+ ChainEventCreatedBase.extend({
+ eventSource: ChainEventCreatedBase.shape.eventSource.extend({
+ eventSignature: z.literal(
+ EvmEventSignatures.NamespaceFactory.NamespaceDeployedWithReferral,
+ ),
+ }),
+ parsedArgs: NamespaceDeployedWithReferral,
+ }),
ChainEventCreatedBase.extend({
eventSource: ChainEventCreatedBase.shape.eventSource.extend({
eventSignature: z.literal(EvmEventSignatures.CommunityStake.Trade),
diff --git a/libs/schemas/src/queries/user.schemas.ts b/libs/schemas/src/queries/user.schemas.ts
index d90b908375e..73f10d817c9 100644
--- a/libs/schemas/src/queries/user.schemas.ts
+++ b/libs/schemas/src/queries/user.schemas.ts
@@ -33,7 +33,10 @@ export const UserProfileView = z.object({
commentThreads: z.array(ThreadView),
isOwner: z.boolean(),
tags: z.array(Tags.extend({ id: PG_INT })),
- xp_points: z.number().int(),
+ referred_by_address: z.string().nullish(),
+ referral_count: PG_INT.default(0),
+ referral_eth_earnings: z.number().optional(),
+ xp_points: PG_INT.default(0),
});
export const GetUserProfile = {
@@ -96,6 +99,9 @@ export const GetUserAddresses = {
export const ReferralView = Referral.extend({
referee_user_id: PG_INT,
referee_profile: UserProfile,
+ community_id: z.string().nullish(),
+ community_name: z.string().nullish(),
+ community_icon_url: z.string().nullish(),
});
export const GetUserReferrals = {
@@ -103,7 +109,12 @@ export const GetUserReferrals = {
output: z.array(ReferralView),
};
-export const ReferralFeesView = ReferralFees;
+export const ReferralFeesView = ReferralFees.extend({
+ referee_profile: UserProfile.nullish(),
+ community_id: z.string().nullish(),
+ community_name: z.string().nullish(),
+ community_icon_url: z.string().nullish(),
+});
export const GetUserReferralFees = {
input: z.object({}),
diff --git a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityStakeStep/SignStakeTransactions/SignStakeTransactions.tsx b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityStakeStep/SignStakeTransactions/SignStakeTransactions.tsx
index df8e85da3a6..54cf2393192 100644
--- a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityStakeStep/SignStakeTransactions/SignStakeTransactions.tsx
+++ b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityStakeStep/SignStakeTransactions/SignStakeTransactions.tsx
@@ -33,9 +33,7 @@ const SignStakeTransactions = ({
apiCallEnabled: user.isLoggedIn,
});
- const referrerAddress = profile?.addresses.find(
- (address) => address.address === selectedAddress.address,
- )?.referred_by_address;
+ const referrerAddress = profile?.referred_by_address;
const { handleReserveCommunityNamespace, reserveNamespaceData } =
useReserveCommunityNamespace({
diff --git a/packages/commonwealth/server/migrations/20250117120000-add-referee-to-referral-fees.js b/packages/commonwealth/server/migrations/20250117120000-add-referee-to-referral-fees.js
new file mode 100644
index 00000000000..d57657b8ab4
--- /dev/null
+++ b/packages/commonwealth/server/migrations/20250117120000-add-referee-to-referral-fees.js
@@ -0,0 +1,23 @@
+'use strict';
+
+/** @type {import('sequelize-cli').Migration} */
+module.exports = {
+ async up(queryInterface, Sequelize) {
+ await queryInterface.sequelize.transaction(async (transaction) => {
+ await queryInterface.addColumn(
+ 'ReferralFees',
+ 'referee_address',
+ {
+ type: Sequelize.STRING,
+ allowNull: false,
+ defaultValue: '',
+ },
+ { transaction },
+ );
+ });
+ },
+
+ async down(queryInterface) {
+ await queryInterface.removeColumn('ReferralFees', 'referee_address');
+ },
+};
diff --git a/packages/commonwealth/server/migrations/20250121090000-update-referral-stats2.js b/packages/commonwealth/server/migrations/20250121090000-update-referral-stats2.js
new file mode 100644
index 00000000000..a035e9482ce
--- /dev/null
+++ b/packages/commonwealth/server/migrations/20250121090000-update-referral-stats2.js
@@ -0,0 +1,38 @@
+'use strict';
+
+/** @type {import('sequelize-cli').Migration} */
+module.exports = {
+ async up(queryInterface, Sequelize) {
+ await queryInterface.sequelize.transaction(async (transaction) => {
+ await queryInterface.addColumn(
+ 'Users',
+ 'referred_by_address',
+ {
+ type: Sequelize.STRING,
+ allowNull: true,
+ },
+ { transaction },
+ );
+ await queryInterface.removeColumn('Addresses', 'referred_by_address', {
+ transaction,
+ });
+ });
+ },
+
+ async down(queryInterface) {
+ await queryInterface.sequelize.transaction(async (transaction) => {
+ await queryInterface.removeColumn('Users', 'referred_by_address', {
+ transaction,
+ });
+ await queryInterface.addColumn(
+ 'Addresses',
+ 'referred_by_address',
+ {
+ type: Sequelize.STRING,
+ allowNull: true,
+ },
+ { transaction },
+ );
+ });
+ },
+};