-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2355 from get10101/feat/referrals
feat: Add referral feature
- Loading branch information
Showing
62 changed files
with
1,770 additions
and
333 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
coordinator/migrations/2024-04-03-035827_add_matching_fee_to_trade_params/down.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
-- This file should undo anything in `up.sql` | ||
ALTER TABLE trade_params | ||
DROP COLUMN matching_fee; | ||
|
3 changes: 3 additions & 0 deletions
3
coordinator/migrations/2024-04-03-035827_add_matching_fee_to_trade_params/up.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
-- Your SQL goes here | ||
ALTER TABLE trade_params | ||
ADD COLUMN matching_fee BIGINT NOT NULL DEFAULT 0; |
10 changes: 10 additions & 0 deletions
10
coordinator/migrations/2024-04-03-065859_add_referral_code_to_user_table/down.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
DROP VIEW IF EXISTS user_referral_summary_view; | ||
|
||
ALTER TABLE users | ||
DROP COLUMN referral_code; | ||
ALTER TABLE users | ||
DROP COLUMN used_referral_code; | ||
|
||
DROP TABLE IF EXISTS bonus_tiers; | ||
DROP TYPE IF EXISTS "BonusStatus_Type"; | ||
|
38 changes: 38 additions & 0 deletions
38
coordinator/migrations/2024-04-03-065859_add_referral_code_to_user_table/up.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
ALTER TABLE users | ||
ADD COLUMN referral_code TEXT NOT NULL GENERATED ALWAYS AS ( | ||
UPPER(RIGHT(pubkey, 6)) | ||
) STORED UNIQUE; | ||
ALTER TABLE users | ||
ADD COLUMN used_referral_code TEXT; | ||
|
||
CREATE TYPE "BonusStatus_Type" AS ENUM ('Referral', 'Referent'); | ||
|
||
CREATE TABLE bonus_tiers ( | ||
id SERIAL PRIMARY KEY, | ||
tier_level INTEGER NOT NULL, | ||
min_users_to_refer INTEGER NOT NULL, | ||
fee_rebate REAL NOT NULL, | ||
bonus_tier_type "BonusStatus_Type" NOT NULL, | ||
active BOOLEAN NOT NULL | ||
); | ||
|
||
INSERT INTO bonus_tiers (tier_level, min_users_to_refer, fee_rebate, bonus_tier_type, active) | ||
VALUES (0, 0, 0.0, 'Referral', true), | ||
(1, 3, 0.20, 'Referral', true), | ||
(2, 5, 0.35, 'Referral', true), | ||
(3, 10, 0.50, 'Referral', true), | ||
(4, 0, 0.1, 'Referent', true); | ||
|
||
CREATE VIEW user_referral_summary_view AS | ||
SELECT u2.pubkey AS referring_user, | ||
u1.used_referral_code as referring_user_referral_code, | ||
u1.pubkey AS referred_user, | ||
u1.referral_code AS referred_user_referral_code, | ||
u1.contact, | ||
u1.nickname, | ||
u1.timestamp, | ||
COALESCE(SUM(t.quantity), 0) AS referred_user_total_quantity | ||
FROM users u1 | ||
JOIN users u2 ON u1.used_referral_code = u2.referral_code | ||
LEFT JOIN trades t ON t.trader_pubkey = u1.pubkey | ||
GROUP BY u1.pubkey, u1.referral_code, u2.pubkey, u1.used_referral_code, u1.contact, u1.nickname, u1.timestamp; |
2 changes: 2 additions & 0 deletions
2
coordinator/migrations/2024-04-03-073229_add_matching_fee_to_matches/down.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
-- This file should undo anything in `up.sql` | ||
alter table matches drop column matching_fee_sats; |
4 changes: 4 additions & 0 deletions
4
coordinator/migrations/2024-04-03-073229_add_matching_fee_to_matches/up.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
-- Your SQL goes here | ||
alter table matches add column matching_fee_sats BIGINT NOT NULL DEFAULT 0; | ||
|
||
update matches set matching_fee_sats=((quantity/execution_price) * 100000000)*0.003; |
2 changes: 2 additions & 0 deletions
2
coordinator/migrations/2024-04-04-005617_add_bonus_status_table/down.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
-- This file should undo anything in `up.sql` | ||
drop table if exists bonus_status; |
11 changes: 11 additions & 0 deletions
11
coordinator/migrations/2024-04-04-005617_add_bonus_status_table/up.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
-- Your SQL goes here | ||
|
||
CREATE TABLE IF NOT EXISTS bonus_status ( | ||
id SERIAL PRIMARY KEY, | ||
trader_pubkey TEXT NOT NULL, | ||
tier_level INTEGER NOT NULL, | ||
fee_rebate REAL NOT NULL DEFAULT 0.0, | ||
bonus_type "BonusStatus_Type" NOT NULL, | ||
activation_timestamp TIMESTAMP WITH TIME ZONE NOT NULL, | ||
deactivation_timestamp TIMESTAMP WITH TIME ZONE NOT NULL | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
use crate::db::bonus_tiers; | ||
use crate::schema::bonus_status; | ||
use crate::schema::sql_types::BonusStatusType; | ||
use bitcoin::secp256k1::PublicKey; | ||
use diesel::AsExpression; | ||
use diesel::ExpressionMethods; | ||
use diesel::FromSqlRow; | ||
use diesel::Insertable; | ||
use diesel::PgConnection; | ||
use diesel::QueryDsl; | ||
use diesel::QueryResult; | ||
use diesel::Queryable; | ||
use diesel::RunQueryDsl; | ||
use time::OffsetDateTime; | ||
|
||
/// A user's referral bonus status may be active for this much days at max | ||
const MAX_DAYS_FOR_ACTIVE_REFERRAL_STATUS: i64 = 30; | ||
|
||
#[derive(Debug, Clone, Copy, PartialEq, FromSqlRow, AsExpression)] | ||
#[diesel(sql_type = BonusStatusType)] | ||
pub enum BonusType { | ||
/// The bonus is because he referred so many users | ||
Referral, | ||
/// The user has been referred and gets a bonus | ||
Referent, | ||
} | ||
|
||
#[allow(dead_code)] | ||
// this is needed because the fields needs to be here to satisfy diesel | ||
#[derive(Queryable, Debug, Clone)] | ||
#[diesel(table_name = bonus_status)] | ||
pub(crate) struct BonusStatus { | ||
pub(crate) id: i32, | ||
pub(crate) trader_pubkey: String, | ||
pub(crate) tier_level: i32, | ||
pub(crate) fee_rebate: f32, | ||
pub(crate) bonus_type: BonusType, | ||
pub(crate) activation_timestamp: OffsetDateTime, | ||
pub(crate) deactivation_timestamp: OffsetDateTime, | ||
} | ||
|
||
impl From<BonusType> for commons::BonusStatusType { | ||
fn from(value: BonusType) -> Self { | ||
match value { | ||
BonusType::Referral => commons::BonusStatusType::Referral, | ||
BonusType::Referent => commons::BonusStatusType::Referent, | ||
} | ||
} | ||
} | ||
|
||
#[derive(Insertable, Debug, Clone)] | ||
#[diesel(table_name = bonus_status)] | ||
pub(crate) struct NewBonusStatus { | ||
pub(crate) trader_pubkey: String, | ||
pub(crate) tier_level: i32, | ||
pub(crate) fee_rebate: f32, | ||
pub(crate) bonus_type: BonusType, | ||
pub(crate) activation_timestamp: OffsetDateTime, | ||
pub(crate) deactivation_timestamp: OffsetDateTime, | ||
} | ||
|
||
/// This function might return multiple status for a single user | ||
/// | ||
/// Because he might have moved up into the next level without the old level being expired. The | ||
/// caller is responsible in picking the most suitable status | ||
pub(crate) fn active_status_for_user( | ||
conn: &mut PgConnection, | ||
trader_pubkey: &PublicKey, | ||
) -> QueryResult<Vec<BonusStatus>> { | ||
bonus_status::table | ||
.filter(bonus_status::trader_pubkey.eq(trader_pubkey.to_string())) | ||
.filter(bonus_status::deactivation_timestamp.gt(OffsetDateTime::now_utc())) | ||
.load(conn) | ||
} | ||
|
||
pub(crate) fn insert( | ||
conn: &mut PgConnection, | ||
trader_pk: &PublicKey, | ||
tier_level: i32, | ||
bonus_type: BonusType, | ||
) -> QueryResult<BonusStatus> { | ||
let existing_status_for_user = active_status_for_user(conn, trader_pk)?; | ||
let bonus_tier = bonus_tiers::tier_by_tier_level(conn, tier_level)?; | ||
|
||
if let Some(status) = existing_status_for_user | ||
.into_iter() | ||
.find(|status| status.tier_level == tier_level) | ||
{ | ||
tracing::debug!( | ||
trader_pubkey = trader_pk.to_string(), | ||
tier_level, | ||
"User has already gained bonus status" | ||
); | ||
return Ok(status); | ||
} | ||
|
||
let bonus_status = diesel::insert_into(bonus_status::table) | ||
.values(NewBonusStatus { | ||
trader_pubkey: trader_pk.to_string(), | ||
tier_level, | ||
fee_rebate: bonus_tier.fee_rebate, | ||
bonus_type, | ||
activation_timestamp: OffsetDateTime::now_utc(), | ||
deactivation_timestamp: OffsetDateTime::now_utc() | ||
+ time::Duration::days(MAX_DAYS_FOR_ACTIVE_REFERRAL_STATUS), | ||
}) | ||
.get_result(conn)?; | ||
|
||
Ok(bonus_status) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
use crate::db::bonus_status::BonusType; | ||
use crate::schema::bonus_tiers; | ||
use bitcoin::secp256k1::PublicKey; | ||
use diesel::pg::sql_types::Timestamptz; | ||
use diesel::prelude::*; | ||
use diesel::sql_query; | ||
use diesel::sql_types::Float; | ||
use diesel::sql_types::Text; | ||
use diesel::PgConnection; | ||
use diesel::QueryResult; | ||
use diesel::Queryable; | ||
use rust_decimal::Decimal; | ||
use time::OffsetDateTime; | ||
|
||
pub struct Referral { | ||
pub volume: Decimal, | ||
} | ||
|
||
#[derive(Queryable, Debug, Clone)] | ||
#[diesel(table_name = bonus_tiers)] | ||
// this is needed because some fields are unused but need to be here for diesel | ||
#[allow(dead_code)] | ||
pub(crate) struct BonusTier { | ||
pub(crate) id: i32, | ||
pub(crate) tier_level: i32, | ||
pub(crate) min_users_to_refer: i32, | ||
pub(crate) fee_rebate: f32, | ||
pub(crate) bonus_tier_type: BonusType, | ||
pub(crate) active: bool, | ||
} | ||
|
||
/// Returns all active bonus tiers for given types | ||
pub(crate) fn all_active_by_type( | ||
conn: &mut PgConnection, | ||
types: Vec<BonusType>, | ||
) -> QueryResult<Vec<BonusTier>> { | ||
bonus_tiers::table | ||
.filter(bonus_tiers::active.eq(true)) | ||
.filter(bonus_tiers::bonus_tier_type.eq_any(types)) | ||
.load::<BonusTier>(conn) | ||
} | ||
|
||
pub(crate) fn tier_by_tier_level( | ||
conn: &mut PgConnection, | ||
tier_level: i32, | ||
) -> QueryResult<BonusTier> { | ||
bonus_tiers::table | ||
.filter(bonus_tiers::tier_level.eq(tier_level)) | ||
.first(conn) | ||
} | ||
|
||
#[derive(Debug, QueryableByName, Clone)] | ||
pub struct UserReferralSummaryView { | ||
#[diesel(sql_type = Text)] | ||
pub referring_user: String, | ||
#[diesel(sql_type = Text)] | ||
pub referring_user_referral_code: String, | ||
#[diesel(sql_type = Text)] | ||
pub referred_user: String, | ||
#[diesel(sql_type = Text)] | ||
pub referred_user_referral_code: String, | ||
#[diesel(sql_type = Timestamptz)] | ||
pub timestamp: OffsetDateTime, | ||
#[diesel(sql_type = Float)] | ||
pub referred_user_total_quantity: f32, | ||
} | ||
|
||
/// Returns all referred users for by referrer with trading volume > 0 | ||
pub(crate) fn all_referrals_by_referring_user( | ||
conn: &mut PgConnection, | ||
trader_pubkey: &PublicKey, | ||
) -> QueryResult<Vec<UserReferralSummaryView>> { | ||
// we have to do this manually because diesel does not support views. If you make a change to | ||
// below, make sure to test this against a life db as errors will only be thrown at runtime | ||
let query = "SELECT referring_user, referring_user_referral_code, \ | ||
referred_user, \ | ||
referred_user_referral_code, \ | ||
timestamp, \ | ||
referred_user_total_quantity \ | ||
FROM user_referral_summary_view where referring_user = $1 \ | ||
and referred_user_total_quantity > 0"; | ||
let summaries: Vec<UserReferralSummaryView> = sql_query(query) | ||
.bind::<Text, _>(trader_pubkey.to_string()) | ||
.load(conn)?; | ||
|
||
Ok(summaries) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.