diff --git a/automation/fluidity/website/mainnet.yml b/automation/fluidity/website/mainnet.yml index f5be2dd74..c4ae27dce 100644 --- a/automation/fluidity/website/mainnet.yml +++ b/automation/fluidity/website/mainnet.yml @@ -18,6 +18,7 @@ SERVICES: NEXT_PUBLIC_FLU_SPLIT_BROWSER_KEY: s7ovhvrpv77htn8aqlf2irirnll9ofvr07be NEXT_PUBLIC_FLU_GTAG_ID: G-EF68MNJRJ7 NEXT_PUBLIC_FLU_GTM_ID: GTM-W7QJGR2 + NEXT_PUBLIC_FLU_HASURA_URL: https://fluidity.hasura.app/v1/graphql DOCKER_ARGS: SECRET_FLU_SENTRY_DSN: /fluidity/frontend/sentryURL diff --git a/cmd/connector-common-lootboxes-timescale/main.go b/cmd/connector-common-lootboxes-timescale/main.go index ab05975c4..b7f0d2e10 100644 --- a/cmd/connector-common-lootboxes-timescale/main.go +++ b/cmd/connector-common-lootboxes-timescale/main.go @@ -6,7 +6,5 @@ import ( ) func main() { - queue.LootboxesAll(func(lootbox lootboxes.Lootbox) { - lootboxes.InsertLootbox(lootbox) - }) + queue.LootboxesAll(lootboxes.InsertLootbox) } diff --git a/cmd/connector-common-winners-timescale/README.md b/cmd/connector-common-winners-timescale/README.md index 455a15be3..58683806b 100644 --- a/cmd/connector-common-winners-timescale/README.md +++ b/cmd/connector-common-winners-timescale/README.md @@ -1,7 +1,7 @@ # Winners to Timescale connector -Write winners received via AMQP to Timescale. +Write winners received via AMQP to Timescale. Also tracks epochs. ## Environment variables diff --git a/cmd/connector-common-winners-timescale/main.go b/cmd/connector-common-winners-timescale/main.go index 0e389ab18..cce0f97e1 100644 --- a/cmd/connector-common-winners-timescale/main.go +++ b/cmd/connector-common-winners-timescale/main.go @@ -5,17 +5,85 @@ package main import ( + "math" + "math/big" + "github.com/fluidity-money/fluidity-app/lib/databases/postgres/solana" + "github.com/fluidity-money/fluidity-app/lib/databases/timescale/lootboxes" database "github.com/fluidity-money/fluidity-app/lib/databases/timescale/winners" + "github.com/fluidity-money/fluidity-app/lib/log" queue "github.com/fluidity-money/fluidity-app/lib/queues/winners" + "github.com/fluidity-money/fluidity-app/lib/types/network" ) +// lootboxUpdateTrackedRewardAmounts by destructuring the winner, and +// upserting it into the database. queries the current epoch to get what +// should be used. +func lootboxUpdateTrackedRewardAmounts(winner queue.Winner) { + var ( + network_ = winner.Network + tokenDetails = winner.TokenDetails + application = winner.Application + amountWon = &winner.WinningAmount.Int + ) + + _, lootboxHasBegun, lootboxCurrentEpoch, _ := lootboxes.GetLootboxConfig() + + if !lootboxHasBegun { + log.Debug(func(k *log.Log) { + k.Message = "Lootbox not running! Ignoring a winner to be inserted in the lootbox tracked rewards!" + }) + + return + } + + var ( + tokenShortName = tokenDetails.TokenShortName + tokenDecimals = tokenDetails.TokenDecimals + ) + + winnerAddress := "" + + // need to set it to the owner of the ATA if we're on solana! + + switch network_ { + case network.NetworkSolana: + winnerAddress = winner.SolanaWinnerOwnerAddress + + default: + winnerAddress = winner.WinnerAddress + } + + // we can normalise this and store it in the database to avoid an + // extra check, as we can afford some loss in this calculation. + + decimals := math.Pow10(tokenDecimals) + + amountNormal := new(big.Rat).SetInt(amountWon) + amountNormal.Quo(amountNormal, new(big.Rat).SetInt64(int64(decimals))) + + amountNormalFloat, _ := amountNormal.Float64() + + lootboxes.UpdateOrInsertAmountsRewarded( + network_, + lootboxCurrentEpoch, + tokenShortName, + amountNormalFloat, // amount normal lossy + winnerAddress, + application, + ) +} + func main() { - go queue.WinnersEthereum(database.InsertWinner) + go queue.WinnersEthereum(func(winner queue.Winner) { + lootboxUpdateTrackedRewardAmounts(winner) + database.InsertWinner(winner) + }) queue.WinnersSolana(func(winner queue.Winner) { winningSignature := solana.GetIntermediateWinner(winner.TransactionHash) winner.SendTransactionHash = winningSignature + lootboxUpdateTrackedRewardAmounts(winner) database.InsertWinner(winner) }) } diff --git a/cmd/microservice-ethereum-create-transaction-lootboxes/main.go b/cmd/microservice-ethereum-create-transaction-lootboxes/main.go index fb1973511..a523578a7 100644 --- a/cmd/microservice-ethereum-create-transaction-lootboxes/main.go +++ b/cmd/microservice-ethereum-create-transaction-lootboxes/main.go @@ -10,29 +10,31 @@ import ( "math/big" "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - libEthereum "github.com/fluidity-money/fluidity-app/common/ethereum" - ethereumApps "github.com/fluidity-money/fluidity-app/common/ethereum/applications" database "github.com/fluidity-money/fluidity-app/lib/databases/timescale/lootboxes" user_actions "github.com/fluidity-money/fluidity-app/lib/databases/timescale/user-actions" "github.com/fluidity-money/fluidity-app/lib/log" "github.com/fluidity-money/fluidity-app/lib/queue" lootboxes_queue "github.com/fluidity-money/fluidity-app/lib/queues/lootboxes" winners_queue "github.com/fluidity-money/fluidity-app/lib/queues/winners" - "github.com/fluidity-money/fluidity-app/lib/types/applications" "github.com/fluidity-money/fluidity-app/lib/types/ethereum" "github.com/fluidity-money/fluidity-app/lib/types/lootboxes" "github.com/fluidity-money/fluidity-app/lib/types/misc" "github.com/fluidity-money/fluidity-app/lib/types/worker" "github.com/fluidity-money/fluidity-app/lib/util" + + libEthereum "github.com/fluidity-money/fluidity-app/common/ethereum" + "github.com/fluidity-money/fluidity-app/common/ethereum/applications" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" ) const ( // EnvTokensList to relate the received token names to a contract address // of the form ADDR1:TOKEN1:DECIMALS1,ADDR2:TOKEN2:DECIMALS2,... EnvTokensList = "FLU_ETHEREUM_TOKENS_LIST" + // EnvGethHttpUrl to use when performing RPC requests EnvGethHttpUrl = `FLU_ETHEREUM_HTTP_URL` ) @@ -85,6 +87,32 @@ func main() { tokenShortName = tokenDetails.TokenShortName ) + programFound, hasBegun, currentEpoch, _ := database.GetLootboxConfig() + + // check that the lootbox is enabled, and that it's also + // running. logs separately if either fail. if the + // campaign has begun but isn't in progress, then we + // should die! that means that something was processed + // weirdly. + + switch false { + case programFound: + log.App(func(k *log.Log) { + k.Message = "No lootbox epoch found, skipping a request to track a winner!" + }) + + return + + case hasBegun: + log.Fatal(func(k *log.Log) { + k.Message = "Lootbox epoch that was found is not running! Terminating on request to track a winner!" + }) + + return + } + + log.Debugf("Lootbox current epoch running is %v!", currentEpoch) + // don't track fluidification if winner.RewardType != "send" { log.Debug(func(k *log.Log) { @@ -114,7 +142,11 @@ func main() { // fetch parameters to call GetApplicationFee // wait for transaction to be mined before fetching receipt - transaction, isPending, err := ethClient.TransactionByHash(context.Background(), common.HexToHash(transactionHash)) + + transaction, isPending, err := ethClient.TransactionByHash( + context.Background(), + common.HexToHash(transactionHash), + ) if err != nil { log.Fatal(func(k *log.Log) { @@ -172,7 +204,14 @@ func main() { inputData := transaction.Data() - feeData, _, _, err := ethereumApps.GetApplicationFee(applicationTransfer, ethClient, fluidTokenContract, tokenDecimals, receipt, inputData) + feeData, _, _, err := applications.GetApplicationFee( + applicationTransfer, + ethClient, + fluidTokenContract, + tokenDecimals, + receipt, + inputData, + ) if err != nil { log.Fatal(func(k *log.Log) { @@ -288,22 +327,8 @@ func volumeLiquidityMultiplier(volume *big.Rat, tokenDecimals int, address strin } func protocolMultiplier(application applications.Application) *big.Rat { - switch application.String() { - case "uniswap_v2": - fallthrough - case "uniswap_v3": - fallthrough - case "saddle": - fallthrough - case "curve": - fallthrough - case "camelot": - fallthrough - case "chronos": - fallthrough - case "kyber_classic": - fallthrough - case "sushiswap": + switch application { + case applications.ApplicationJumper, applications.ApplicationUniswapV3, applications.ApplicationTraderJoe, applications.ApplicationCamelot, applications.ApplicationSushiswap, applications.ApplicationRamses: return big.NewRat(2, 100) } diff --git a/cmd/microservice-ethereum-track-winners/main.go b/cmd/microservice-ethereum-track-winners/main.go index 0bf358ce0..302042be9 100644 --- a/cmd/microservice-ethereum-track-winners/main.go +++ b/cmd/microservice-ethereum-track-winners/main.go @@ -11,13 +11,14 @@ import ( "strconv" "strings" - "github.com/fluidity-money/fluidity-app/common/ethereum/fluidity" logging "github.com/fluidity-money/fluidity-app/lib/log" "github.com/fluidity-money/fluidity-app/lib/queues/ethereum" "github.com/fluidity-money/fluidity-app/lib/queues/winners" "github.com/fluidity-money/fluidity-app/lib/types/network" "github.com/fluidity-money/fluidity-app/lib/types/token-details" "github.com/fluidity-money/fluidity-app/lib/util" + + "github.com/fluidity-money/fluidity-app/common/ethereum/fluidity" ) const ( @@ -81,7 +82,6 @@ func main() { } ethereum.Logs(func(log ethereum.Log) { - var ( logTopics = log.Topics transactionHash = log.TxHash diff --git a/cmd/microservice-ethereum-track-winners/processing.go b/cmd/microservice-ethereum-track-winners/processing.go index 4d2a80158..d976b96af 100644 --- a/cmd/microservice-ethereum-track-winners/processing.go +++ b/cmd/microservice-ethereum-track-winners/processing.go @@ -3,7 +3,6 @@ package main import ( "time" - "github.com/fluidity-money/fluidity-app/common/ethereum/fluidity" winnersDb "github.com/fluidity-money/fluidity-app/lib/databases/timescale/winners" "github.com/fluidity-money/fluidity-app/lib/log" "github.com/fluidity-money/fluidity-app/lib/queue" @@ -12,17 +11,19 @@ import ( "github.com/fluidity-money/fluidity-app/lib/types/network" token_details "github.com/fluidity-money/fluidity-app/lib/types/token-details" "github.com/fluidity-money/fluidity-app/lib/types/winners" + + "github.com/fluidity-money/fluidity-app/common/ethereum/fluidity" ) func processReward(contractAddress ethereum.Address, transactionHash ethereum.Hash, data fluidity.RewardData, tokenDetails token_details.TokenDetails, network network.BlockchainNetwork) { var ( - winnerString = data.Winner.String() - winnerAddress = ethereum.AddressFromString(winnerString) - startBlock = *data.StartBlock - endBlock = *data.EndBlock + winnerString = data.Winner.String() + startBlock = *data.StartBlock + endBlock = *data.EndBlock + blocked = data.Blocked ) - blocked := data.Blocked + winnerAddress := ethereum.AddressFromString(winnerString) if blocked { blockedWinner := convertBlockedWinner( @@ -61,8 +62,9 @@ func processReward(contractAddress ethereum.Address, transactionHash ethereum.Ha } func processUnblockedReward(transactionHash ethereum.Hash, data fluidity.UnblockedRewardData, tokenDetails token_details.TokenDetails, network network.BlockchainNetwork) { + rewardData := data.RewardData + var ( - rewardData = data.RewardData winnerString = rewardData.Winner.String() startBlock = *rewardData.StartBlock endBlock = *rewardData.EndBlock diff --git a/cmd/microservice-lootbox-referral-activator/main.go b/cmd/microservice-lootbox-referral-activator/main.go index a065abc4b..5b0c5dd28 100644 --- a/cmd/microservice-lootbox-referral-activator/main.go +++ b/cmd/microservice-lootbox-referral-activator/main.go @@ -26,9 +26,7 @@ const ( ) func main() { - var ( - lootboxReferralAmount_ = util.GetEnvOrFatal(EnvLootboxReferralAmount) - ) + lootboxReferralAmount_ := util.GetEnvOrFatal(EnvLootboxReferralAmount) lootboxReferralAmount, err := strconv.ParseUint(lootboxReferralAmount_, 10, 64) @@ -45,6 +43,7 @@ func main() { transactionHash = lootbox.TransactionHash address = lootbox.Address lootboxCount = lootbox.LootboxCount + epoch = lootbox.Epoch ) // don't track non-transaction lootboxes @@ -102,6 +101,7 @@ func main() { RewardTier: 1, LootboxCount: 5, Application: applications.ApplicationNone, + Epoch: epoch, } queue.SendMessage(lootboxes_queue.TopicLootboxes, referralLootbox) diff --git a/cmd/microservice-lootbox-referral-distributor/main.go b/cmd/microservice-lootbox-referral-distributor/main.go index d960725a3..80abdecd8 100644 --- a/cmd/microservice-lootbox-referral-distributor/main.go +++ b/cmd/microservice-lootbox-referral-distributor/main.go @@ -5,7 +5,6 @@ package main import ( - "github.com/fluidity-money/fluidity-app/common/ethereum/applications" "github.com/fluidity-money/fluidity-app/lib/databases/timescale/referrals" "github.com/fluidity-money/fluidity-app/lib/log" "github.com/fluidity-money/fluidity-app/lib/queue" @@ -13,6 +12,8 @@ import ( "github.com/fluidity-money/fluidity-app/lib/types/ethereum" "github.com/fluidity-money/fluidity-app/lib/types/lootboxes" "github.com/fluidity-money/fluidity-app/lib/types/misc" + + "github.com/fluidity-money/fluidity-app/common/ethereum/applications" ) func main() { @@ -23,6 +24,7 @@ func main() { address = lootbox.Address lootboxCount = lootbox.LootboxCount awardedTime = lootbox.AwardedTime + epoch = lootbox.Epoch ) // don't track non-transaction lootboxes @@ -62,9 +64,10 @@ func main() { RewardTier: 1, LootboxCount: referralLootboxCount, Application: applications.ApplicationNone, + Epoch: epoch, } - go queue.SendMessage(lootboxes_queue.TopicLootboxes, referralLootbox) + queue.SendMessage(lootboxes_queue.TopicLootboxes, referralLootbox) } }) } diff --git a/cmd/microservice-lootbox-reward-top-winners/main.go b/cmd/microservice-lootbox-reward-top-winners/main.go index 12119feeb..eabe0fa9d 100644 --- a/cmd/microservice-lootbox-reward-top-winners/main.go +++ b/cmd/microservice-lootbox-reward-top-winners/main.go @@ -32,8 +32,48 @@ func main() { // endTime is the beginning of the day after startTime (the start of the current date) endTime := currentTime - // fetch and log the top 10 users - topUsers := lootboxes.GetTopApplicationUsersByLootboxCount(startTime, endTime, applications.ApplicationKyberClassic) + programFound, hasBegun, currentEpoch, currentApplication := lootboxes.GetLootboxConfig() + + // if the lootbox isn't enabled, or it isn't running, then we skip. we + // treat the cases separately for logging reasons. + + switch false { + case programFound: + log.App(func(k *log.Log) { + k.Message = "No lootbox epoch found! Skipping running." + }) + + return + + case hasBegun: + log.App(func(k *log.Log) { + k.Message = "Current lootbox epoch has not yet started! Skipping running." + }) + + return + } + + var topUsers []lootboxes.UserLootboxCount + + // if there's a current application focus, then we want to reward only specific winners + + switch currentApplication { + case applications.ApplicationNone: + topUsers = lootboxes.GetTopUsersByLootboxCount( + currentEpoch, + startTime, + endTime, + ) + default: + // fetch and log the top 10 users for a specific application + topUsers = lootboxes.GetTopApplicationUsersByLootboxCount( + currentEpoch, + startTime, + endTime, + currentApplication, + ) + } + for i, user := range topUsers { log.App(func(k *log.Log) { k.Format( @@ -47,5 +87,5 @@ func main() { } // reward the top 10 users - lootboxes.InsertTopUserReward(startTime, topUsers) + lootboxes.InsertTopUserReward(currentEpoch, startTime, topUsers) } diff --git a/cmd/microservice-redeem-testnet-lootboxes/README.md b/cmd/microservice-redeem-testnet-lootboxes/README.md index 988ac37b6..8c9a2f45b 100644 --- a/cmd/microservice-redeem-testnet-lootboxes/README.md +++ b/cmd/microservice-redeem-testnet-lootboxes/README.md @@ -1,7 +1,11 @@ # microservice-redeem-testnet-lootboxes -Watch for events indicating ownership of a testnet address and reward the owner. +Watch for events indicating ownership of a testnet address and reward +the owner. + +If an epoch is not currently running, then this should break as +someone is using the contract improperly. ## Environment variables diff --git a/cmd/microservice-redeem-testnet-lootboxes/main.go b/cmd/microservice-redeem-testnet-lootboxes/main.go index 9ddf11e58..90ed02578 100644 --- a/cmd/microservice-redeem-testnet-lootboxes/main.go +++ b/cmd/microservice-redeem-testnet-lootboxes/main.go @@ -34,6 +34,27 @@ func main() { ) logs.Logs(func(l logs.Log) { + programFound, hasBegun, currentEpoch, _ := lootboxes.GetLootboxConfig() + + // if the lootbox isn't enabled, or it isn't running, then we alarm + // because someone has called the contract to manually reward themselves + // whem presumably the UI isn't enabled. treated separately for logging + // reasons. + + switch false { + case programFound: + log.Fatal(func(k *log.Log) { + k.Message = "No lootbox program found, but a log was received for a testnet redemption!" + }) + + case hasBegun: + log.Fatal(func(k *log.Log) { + k.Message = "Lootbox program that was found is not running!" + }) + } + + log.Debugf("Lootbox current epoch running is %v!", currentEpoch) + if l.Address != addressConfirmerContractAddress { log.Debug(func(k *log.Log) { k.Format( @@ -98,6 +119,7 @@ func main() { AwardedTime: currentTime, LootboxCount: LootboxCountCommon, RewardTier: 1, + Epoch: currentEpoch, }, { Address: testnetOwnerString, @@ -105,6 +127,7 @@ func main() { AwardedTime: currentTime, LootboxCount: LootboxCountUncommon, RewardTier: 2, + Epoch: currentEpoch, }, { Address: testnetOwnerString, @@ -112,6 +135,7 @@ func main() { AwardedTime: currentTime, LootboxCount: LootboxCountRare, RewardTier: 3, + Epoch: currentEpoch, }, { Address: testnetOwnerString, @@ -119,6 +143,7 @@ func main() { AwardedTime: currentTime, LootboxCount: LootboxCountUltraRare, RewardTier: 4, + Epoch: currentEpoch, }, } diff --git a/common/ethereum/applications/applications.go b/common/ethereum/applications/applications.go index fac0e3ec9..eaf96096e 100644 --- a/common/ethereum/applications/applications.go +++ b/common/ethereum/applications/applications.go @@ -39,6 +39,8 @@ import ( "github.com/ethereum/go-ethereum/ethclient" ) +type Application = libApps.Application + const ( // ApplicationNone is the nil value representing a transfer. ApplicationNone libApps.Application = iota @@ -64,8 +66,15 @@ const ( ApplicationWombat ApplicationSeawaterAmm ApplicationTraderJoe + ApplicationRamses + ApplicationJumper ) +// ParseApplicationName shadows the lib types definition +func ParseApplicationName(name string) (Application, error) { + return libApps.ParseApplicationName(name) +} + // GetApplicationFee to find the fee (in USD) paid by a user for the application interaction // returns (feeData wiht Fee set to nil, ni) in the case where the application event is legitimate, but doesn't involve // the fluid asset we're tracking, e.g. in a multi-token pool where two other tokens are swapped @@ -383,7 +392,6 @@ func GetApplicationTransferParties(transaction ethereum.Transaction, transfer wo // Gave the majority payout to the swap-maker (i.e. transaction sender) // and rest to pool return transaction.From, logAddress, nil - default: return nilAddress, nilAddress, fmt.Errorf( "Transfer #%v did not contain an application", diff --git a/common/ethereum/applications/jumper/init.go b/common/ethereum/applications/jumper/init.go new file mode 100644 index 000000000..872132a0c --- /dev/null +++ b/common/ethereum/applications/jumper/init.go @@ -0,0 +1,7 @@ +// Copyright 2022 Fluidity Money. All rights reserved. Use of this +// source code is governed by a GPL-style license that can be found in the +// LICENSE.md file. + +package jumper + +func init() {} diff --git a/database/102-up-timescale/20230323164105-airdrop_leaderboard.sql b/database/102-up-timescale/20230323164105-airdrop_leaderboard.sql index fcf1e86ce..750fa4f29 100644 --- a/database/102-up-timescale/20230323164105-airdrop_leaderboard.sql +++ b/database/102-up-timescale/20230323164105-airdrop_leaderboard.sql @@ -14,18 +14,18 @@ LANGUAGE SQL STABLE AS $$ -SELECT +SELECT address, -- placeholder ROW_NUMBER() OVER () AS rank, - COUNT(DISTINCT referee) AS referral_count, - SUM(lootbox_count) AS total_lootboxes, - MAX(reward_tier) AS highest_reward_tier, - COALESCE(liquidity.result, 1) AS liquidity_multiplier -FROM lootbox - LEFT JOIN lootbox_referrals - ON lootbox.address = lootbox_referrals.referrer, - LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity + COUNT(DISTINCT referee) AS referral_count, + SUM(lootbox_count) AS total_lootboxes, + MAX(reward_tier) AS highest_reward_tier, + COALESCE(liquidity.result, 1) AS liquidity_multiplier +FROM lootbox + LEFT JOIN lootbox_referrals + ON lootbox.address = lootbox_referrals.referrer, + LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity GROUP BY address, liquidity_multiplier $$; diff --git a/database/102-up-timescale/20230613063527_airdrop_leaderboard_add_24_hrs_application.sql b/database/102-up-timescale/20230613063527_airdrop_leaderboard_add_24_hrs_application.sql index c9ed1a47a..c1c77eae7 100644 --- a/database/102-up-timescale/20230613063527_airdrop_leaderboard_add_24_hrs_application.sql +++ b/database/102-up-timescale/20230613063527_airdrop_leaderboard_add_24_hrs_application.sql @@ -5,25 +5,25 @@ LANGUAGE SQL STABLE AS $$ -SELECT +SELECT address, -- placeholder ROW_NUMBER() OVER () AS rank, - COUNT(DISTINCT referee) AS referral_count, - lb_24_application.total_box_count, + COUNT(DISTINCT referee) AS referral_count, + lb_24_application.total_box_count, lb_24_application.highest_reward_tier, - COALESCE(liquidity.result, 1) AS liquidity_multiplier + COALESCE(liquidity.result, 1) AS liquidity_multiplier FROM ( -- subquery to avoid re-summing lootbox_count for every referee SELECT address, SUM(lootbox_count) as total_box_count, MAX(reward_tier) as highest_reward_tier - FROM lootbox + FROM lootbox WHERE awarded_time > now() - interval '1 day' - AND application = application_ + AND application = application_ GROUP BY address ) lb_24_application - LEFT JOIN lootbox_referrals - ON lb_24_application.address = lootbox_referrals.referrer, - LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity + LEFT JOIN lootbox_referrals + ON lb_24_application.address = lootbox_referrals.referrer, + LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity GROUP BY address, liquidity_multiplier, total_box_count, highest_reward_tier $$; diff --git a/database/102-up-timescale/20231219091930-lootbox_add_epoch.sql b/database/102-up-timescale/20231219091930-lootbox_add_epoch.sql new file mode 100644 index 000000000..ba3523d0c --- /dev/null +++ b/database/102-up-timescale/20231219091930-lootbox_add_epoch.sql @@ -0,0 +1,102 @@ + +-- migrate:up + +CREATE TYPE lootbox_epoch AS ENUM ( + 'epoch_1', + 'epoch_2', + 'epoch_testing' +); + +ALTER TABLE lootbox + ADD COLUMN epoch lootbox_epoch NOT NULL DEFAULT 'epoch_1'; + +ALTER TABLE lootbox_referral_codes + ADD COLUMN epoch lootbox_epoch NOT NULL DEFAULT 'epoch_1'; + +ALTER TABLE lootbox_referrals + ADD COLUMN epoch lootbox_epoch NOT NULL DEFAULT 'epoch_1'; + +DROP FUNCTION lootbox_referral_lootbottle_counts; + +CREATE FUNCTION lootbox_referral_lootbottle_counts(epoch_ lootbox_epoch) +RETURNS SETOF lootbox_counts_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + COALESCE(tier1, 0), + COALESCE(tier2, 0), + COALESCE(tier3, 0), + COALESCE(tier4, 0), + COALESCE(tier5, 0) +FROM crosstab( + format( + 'SELECT + address, + reward_tier, + SUM(lootbox_count) + FROM lootbox + WHERE source=''referral'' AND epoch = %s + GROUP BY + address, + reward_tier + ORDER BY 1', + quote_literal(epoch_) + ), + 'VALUES (1),(2),(3),(4),(5)' +) AS ct( + address VARCHAR, + tier1 NUMERIC, + tier2 NUMERIC, + tier3 NUMERIC, + tier4 NUMERIC, + tier5 NUMERIC +); +$$; + +-- migrate:down + +ALTER TABLE lootbox DROP COLUMN epoch; + +ALTER TABLE lootbox_referral_codes DROP COLUMN epoch; + +ALTER TABLE lootbox_referrals DROP COLUMN epoch; + +DROP FUNCTION lootbox_referral_lootbottle_counts; + +CREATE FUNCTION lootbox_referral_lootbottle_counts() +RETURNS SETOF lootbox_counts_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + COALESCE(tier1, 0), + COALESCE(tier2, 0), + COALESCE(tier3, 0), + COALESCE(tier4, 0), + COALESCE(tier5, 0) +FROM crosstab( + 'SELECT + address, + reward_tier, + SUM(lootbox_count) + FROM lootbox + WHERE source=''referral'' + GROUP BY + address, + reward_tier + ORDER BY 1', + 'VALUES (1),(2),(3),(4),(5)' +) AS ct( + address VARCHAR, + tier1 NUMERIC, + tier2 NUMERIC, + tier3 NUMERIC, + tier4 NUMERIC, + tier5 NUMERIC +); +$$; diff --git a/database/102-up-timescale/20231219094456-lootbox_config_add_prev_epoch.sql b/database/102-up-timescale/20231219094456-lootbox_config_add_prev_epoch.sql new file mode 100644 index 000000000..fa75fd5a6 --- /dev/null +++ b/database/102-up-timescale/20231219094456-lootbox_config_add_prev_epoch.sql @@ -0,0 +1,39 @@ + +-- migrate:up + +CREATE TABLE lootbox_config ( + -- is_current_program if enabled, it's assumed that this is + -- the current epoch routine, and config is derived from this + is_current_program BOOLEAN NOT NULL DEFAULT FALSE, + + -- program_begin_date to begin counting lootbottles from + -- this date onwards, in UTC + program_begin TIMESTAMP WITHOUT TIME ZONE NOT NULL, + + -- program_end to use as the final date for the epoch, and + -- when to stop counting (also in UTC) + program_end TIMESTAMP WITHOUT TIME ZONE NOT NULL, + + -- epoch_identifier to use as the identifier for the epoch with + -- the enum + epoch_identifier lootbox_epoch NOT NULL UNIQUE, + + -- ethereum_application that should be the current focus of the lootbox + -- mini leaderboard competitions (and should be displayed in the UI + -- with the right query) + ethereum_application ethereum_application NOT NULL DEFAULT 'none' +); + +INSERT INTO lootbox_config ( + program_begin, + program_end, + epoch_identifier +) VALUES ( + '2023-05-01 12:00:00+02', + '2023-06-28 12:00:00+02', + 'epoch_1' +); + +-- migrate:down + +DROP TABLE lootbox_config; diff --git a/database/102-up-timescale/20231228172908-lootbox_amounts_earned_create.sql b/database/102-up-timescale/20231228172908-lootbox_amounts_earned_create.sql new file mode 100644 index 000000000..839cf1e2a --- /dev/null +++ b/database/102-up-timescale/20231228172908-lootbox_amounts_earned_create.sql @@ -0,0 +1,34 @@ +-- migrate:up + +-- lootbox_amounts_rewarded is an accumulation table that increases +-- itself over time on a per-epoch basis. it does so so we can track +-- cleanly in the UI the amounts earned for the lootbox presentation + +-- the winner field is converted from the solana winner owner +-- address implicitly in the go code currently! + +-- we're okay with DECIMAL as we can store the floating amount with a +-- level of loss since this is user-facing. + +CREATE TABLE lootbox_amounts_rewarded ( + network network_blockchain NOT NULL, + epoch lootbox_epoch NOT NULL, + token_short_name VARCHAR NOT NULL, + + -- the amount earned, normalised according to the underlying + -- decimals. can be lossy + amount_earned NUMERIC NOT NULL, + + -- winner of the winning (the recipient of the winning payout) + winner VARCHAR NOT NULL, + + application VARCHAR NOT NULL, + last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + -- id to prevent duplication during the upsert for updating amounts + CONSTRAINT id PRIMARY KEY(network, epoch, token_short_name, winner, application) +); + +-- migrate:down + +DROP TABLE lootbox_amounts_rewarded; diff --git a/database/102-up-timescale/20240102213320-airdrop_leaderboard_remove_chronos.sql b/database/102-up-timescale/20240102213320-airdrop_leaderboard_remove_chronos.sql new file mode 100644 index 000000000..c74f30ea6 --- /dev/null +++ b/database/102-up-timescale/20240102213320-airdrop_leaderboard_remove_chronos.sql @@ -0,0 +1,31 @@ +-- migrate:up +DROP FUNCTION airdrop_leaderboard_24_hours_chronos; + +-- migrate:down +CREATE OR REPLACE FUNCTION airdrop_leaderboard_24_hours_chronos() +RETURNS SETOF airdrop_leaderboard_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + -- placeholder + ROW_NUMBER() OVER () AS rank, + COUNT(DISTINCT referee) AS referral_count, + lb_24_chronos.total_box_count, + lb_24_chronos.highest_reward_tier, + COALESCE(liquidity.result, 1) AS liquidity_multiplier +FROM ( + -- subquery to avoid re-summing lootbox_count for every referee + SELECT address, SUM(lootbox_count) as total_box_count, MAX(reward_tier) as highest_reward_tier + FROM lootbox + WHERE awarded_time > now() - interval '1 day' + AND application = 'chronos' + GROUP BY address +) lb_24_chronos + LEFT JOIN lootbox_referrals + ON lb_24_chronos.address = lootbox_referrals.referrer, + LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity +GROUP BY address, liquidity_multiplier, total_box_count, highest_reward_tier +$$; \ No newline at end of file diff --git a/database/102-up-timescale/20240102213501-airdrop_leaderboard_24_hours_add_epoch_and_arb_fusdc.sql b/database/102-up-timescale/20240102213501-airdrop_leaderboard_24_hours_add_epoch_and_arb_fusdc.sql new file mode 100644 index 000000000..6f8286e07 --- /dev/null +++ b/database/102-up-timescale/20240102213501-airdrop_leaderboard_24_hours_add_epoch_and_arb_fusdc.sql @@ -0,0 +1,89 @@ +-- migrate:up + +-- doesn't take the underlying network into account! + +DROP FUNCTION airdrop_leaderboard_24_hours; + +ALTER TABLE airdrop_leaderboard_return + ADD COLUMN fusdc_earned NUMERIC NOT NULL, + ADD COLUMN arb_earned NUMERIC NOT NULL; + +CREATE FUNCTION airdrop_leaderboard_24_hours(epoch_ lootbox_epoch) +RETURNS SETOF airdrop_leaderboard_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + -- placeholder + ROW_NUMBER() OVER () AS rank, + COUNT(DISTINCT referee) AS referral_count, + lb.total_box_count, + lb.highest_reward_tier, + COALESCE(liquidity.result, 1) AS liquidity_multiplier, + COALESCE(lootbox_amounts_rewarded_fusdc.amount_earned, 0), + COALESCE(lootbox_amounts_rewarded_arb.amount_earned, 0) +FROM ( + -- subquery to avoid re-summing lootbox_count for every referee + SELECT address, SUM(lootbox_count) as total_box_count, MAX(reward_tier) as highest_reward_tier + FROM lootbox + WHERE awarded_time > now() - interval '1 day' AND epoch = epoch_ + GROUP BY address +) lb + LEFT JOIN ( + SELECT amount_earned, winner + FROM lootbox_amounts_rewarded + WHERE token_short_name = 'USDC' + ) AS lootbox_amounts_rewarded_fusdc ON lb.address = lootbox_amounts_rewarded_fusdc.winner + LEFT JOIN ( + SELECT amount_earned, winner + FROM lootbox_amounts_rewarded + WHERE token_short_name = 'ARB' + ) AS lootbox_amounts_rewarded_arb ON lb.address = lootbox_amounts_rewarded_arb.winner + LEFT JOIN lootbox_referrals + ON lb.address = lootbox_referrals.referrer, + LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity +GROUP BY + address, + liquidity_multiplier, + total_box_count, + highest_reward_tier, + lootbox_amounts_rewarded_fusdc.amount_earned, + lootbox_amounts_rewarded_arb.amount_earned +$$; + +-- migrate:down + +DROP FUNCTION airdrop_leaderboard_24_hours; + +ALTER TABLE airdrop_leaderboard_return + DROP COLUMN fusdc_earned, + DROP COLUMN arb_earned; + +CREATE OR REPLACE FUNCTION airdrop_leaderboard_24_hours() +RETURNS SETOF airdrop_leaderboard_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + -- placeholder + ROW_NUMBER() OVER () AS rank, + COUNT(DISTINCT referee) AS referral_count, + lb.total_box_count, + lb.highest_reward_tier, + COALESCE(liquidity.result, 1) AS liquidity_multiplier +FROM ( + -- subquery to avoid re-summing lootbox_count for every referee + SELECT address, SUM(lootbox_count) as total_box_count, MAX(reward_tier) as highest_reward_tier + FROM lootbox + WHERE awarded_time > now() - interval '1 day' + GROUP BY address +) lb + LEFT JOIN lootbox_referrals + ON lb.address = lootbox_referrals.referrer, + LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity +GROUP BY address, liquidity_multiplier, total_box_count, highest_reward_tier +$$; diff --git a/database/102-up-timescale/20240103103945-airdrop_leaderboard_add_epoch_and_arb_fusdc.sql b/database/102-up-timescale/20240103103945-airdrop_leaderboard_add_epoch_and_arb_fusdc.sql new file mode 100644 index 000000000..0da4d791a --- /dev/null +++ b/database/102-up-timescale/20240103103945-airdrop_leaderboard_add_epoch_and_arb_fusdc.sql @@ -0,0 +1,79 @@ +-- migrate:up + +DROP FUNCTION airdrop_leaderboard_24_hours_by_application; + +CREATE FUNCTION airdrop_leaderboard_24_hours_by_application(epoch_ lootbox_epoch, application_ ethereum_application) +RETURNS SETOF airdrop_leaderboard_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + -- placeholder + ROW_NUMBER() OVER () AS rank, + COUNT(DISTINCT referee) AS referral_count, + lb_24_application.total_box_count, + lb_24_application.highest_reward_tier, + COALESCE(liquidity.result, 1) AS liquidity_multiplier, + COALESCE(lootbox_amounts_rewarded_fusdc.amount_earned, 0), + COALESCE(lootbox_amounts_rewarded_arb.amount_earned, 0) +FROM ( + -- subquery to avoid re-summing lootbox_count for every referee + SELECT address, SUM(lootbox_count) as total_box_count, MAX(reward_tier) as highest_reward_tier + FROM lootbox + WHERE awarded_time > now() - interval '1 day' AND epoch = epoch_ + AND application = application_ + GROUP BY address +) lb_24_application + LEFT JOIN ( + SELECT amount_earned, winner + FROM lootbox_amounts_rewarded + WHERE token_short_name = 'USDC' + ) AS lootbox_amounts_rewarded_fusdc ON lb_24_application.address = lootbox_amounts_rewarded_fusdc.winner + LEFT JOIN ( + SELECT amount_earned, winner + FROM lootbox_amounts_rewarded + WHERE token_short_name = 'ARB' + ) AS lootbox_amounts_rewarded_arb ON lb_24_application.address = lootbox_amounts_rewarded_arb.winner + LEFT JOIN lootbox_referrals + ON lb_24_application.address = lootbox_referrals.referrer, + LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity +GROUP BY + address, + liquidity_multiplier, + total_box_count, + highest_reward_tier, + lootbox_amounts_rewarded_fusdc.amount_earned, + lootbox_amounts_rewarded_arb.amount_earned +$$; + +-- migrate:down + +CREATE OR REPLACE FUNCTION airdrop_leaderboard_24_hours_by_application(application_ ethereum_application) +RETURNS SETOF airdrop_leaderboard_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + -- placeholder + ROW_NUMBER() OVER () AS rank, + COUNT(DISTINCT referee) AS referral_count, + lb_24_application.total_box_count, + lb_24_application.highest_reward_tier, + COALESCE(liquidity.result, 1) AS liquidity_multiplier +FROM ( + -- subquery to avoid re-summing lootbox_count for every referee + SELECT address, SUM(lootbox_count) as total_box_count, MAX(reward_tier) as highest_reward_tier + FROM lootbox + WHERE awarded_time > now() - interval '1 day' + AND application = application_ + GROUP BY address +) lb_24_application + LEFT JOIN lootbox_referrals + ON lb_24_application.address = lootbox_referrals.referrer, + LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity +GROUP BY address, liquidity_multiplier, total_box_count, highest_reward_tier +$$; \ No newline at end of file diff --git a/database/102-up-timescale/20240104203505-lootbox_counts_add_epoch.sql b/database/102-up-timescale/20240104203505-lootbox_counts_add_epoch.sql new file mode 100644 index 000000000..2d68b232c --- /dev/null +++ b/database/102-up-timescale/20240104203505-lootbox_counts_add_epoch.sql @@ -0,0 +1,81 @@ +--migrate:up + +DROP FUNCTION lootbox_counts; + +-- epoch should be sanitised as an argument here + +CREATE FUNCTION lootbox_counts(epoch_ lootbox_epoch) +RETURNS SETOF lootbox_counts_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + COALESCE(tier1, 0), + COALESCE(tier2, 0), + COALESCE(tier3, 0), + COALESCE(tier4, 0), + COALESCE(tier5, 0) +FROM crosstab( + format( + 'SELECT + address, + reward_tier, + SUM(lootbox_count) + FROM lootbox + WHERE epoch = %s + GROUP BY + address, + reward_tier + ORDER BY 1', + quote_literal(epoch_) + ), + 'VALUES (1),(2),(3),(4),(5)' +) AS ct( + address VARCHAR, + tier1 NUMERIC, + tier2 NUMERIC, + tier3 NUMERIC, + tier4 NUMERIC, + tier5 NUMERIC +); +$$; + +--migrate:down + +DROP FUNCTION lootbox_counts; + +CREATE FUNCTION lootbox_counts() +RETURNS SETOF lootbox_counts_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + COALESCE(tier1, 0), + COALESCE(tier2, 0), + COALESCE(tier3, 0), + COALESCE(tier4, 0), + COALESCE(tier5, 0) +FROM crosstab( + 'SELECT + address, + reward_tier, + SUM(lootbox_count) + FROM lootbox + GROUP BY + address, + reward_tier + ORDER BY 1', + 'VALUES (1),(2),(3),(4),(5)' +) AS ct( + address VARCHAR, + tier1 NUMERIC, + tier2 NUMERIC, + tier3 NUMERIC, + tier4 NUMERIC, + tier5 NUMERIC +); +$$; diff --git a/database/102-up-timescale/20240105003308-airdrop_leaderboard_add_epoch.sql b/database/102-up-timescale/20240105003308-airdrop_leaderboard_add_epoch.sql new file mode 100644 index 000000000..2b1e21463 --- /dev/null +++ b/database/102-up-timescale/20240105003308-airdrop_leaderboard_add_epoch.sql @@ -0,0 +1,72 @@ +-- migrate:up + +DROP FUNCTION airdrop_leaderboard; + +CREATE FUNCTION airdrop_leaderboard(epoch_ lootbox_epoch) +RETURNS SETOF airdrop_leaderboard_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + -- placeholder + ROW_NUMBER() OVER () AS rank, + COUNT(DISTINCT referee) AS referral_count, + lb.total_box_count, + lb.highest_reward_tier, + COALESCE(liquidity.result, 1) AS liquidity_multiplier, + COALESCE(lootbox_amounts_rewarded_fusdc.amount_earned, 0), + COALESCE(lootbox_amounts_rewarded_arb.amount_earned, 0) +FROM ( + -- subquery to avoid re-summing lootbox_count for every referee + SELECT address, SUM(lootbox_count) as total_box_count, MAX(reward_tier) as highest_reward_tier + FROM lootbox + WHERE epoch = epoch_ + GROUP BY address +) lb + LEFT JOIN ( + SELECT amount_earned, winner + FROM lootbox_amounts_rewarded + WHERE token_short_name = 'USDC' + ) AS lootbox_amounts_rewarded_fusdc ON lb.address = lootbox_amounts_rewarded_fusdc.winner + LEFT JOIN ( + SELECT amount_earned, winner + FROM lootbox_amounts_rewarded + WHERE token_short_name = 'ARB' + ) AS lootbox_amounts_rewarded_arb ON lb.address = lootbox_amounts_rewarded_arb.winner + LEFT JOIN lootbox_referrals + ON lb.address = lootbox_referrals.referrer, + LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity +GROUP BY address, liquidity_multiplier, total_box_count, highest_reward_tier, lootbox_amounts_rewarded_fusdc.amount_earned, lootbox_amounts_rewarded_arb.amount_earned +$$; + +-- migrate:down + +DROP FUNCTION airdrop_leaderboard; + +CREATE FUNCTION airdrop_leaderboard() +RETURNS SETOF airdrop_leaderboard_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + address, + -- placeholder + ROW_NUMBER() OVER () AS rank, + COUNT(DISTINCT referee) AS referral_count, + lb.total_box_count, + lb.highest_reward_tier, + COALESCE(liquidity.result, 1) AS liquidity_multiplier +FROM ( + -- subquery to avoid re-summing lootbox_count for every referee + SELECT address, SUM(lootbox_count) as total_box_count, MAX(reward_tier) as highest_reward_tier + FROM lootbox + GROUP BY address +) lb + LEFT JOIN lootbox_referrals + ON lb.address = lootbox_referrals.referrer, + LATERAL calculate_a_y(address, now()::TIMESTAMP) AS liquidity +GROUP BY address, liquidity_multiplier, total_box_count, highest_reward_tier +$$; \ No newline at end of file diff --git a/database/102-up-timescale/20240108102606-user_actions_add_user_actions_lootbottles.sql b/database/102-up-timescale/20240108102606-user_actions_add_user_actions_lootbottles.sql new file mode 100644 index 000000000..e60430912 --- /dev/null +++ b/database/102-up-timescale/20240108102606-user_actions_add_user_actions_lootbottles.sql @@ -0,0 +1,61 @@ +-- migrate:up + +-- this is a copy of aggregated_user_transactions + +CREATE TABLE aggregated_user_transactions_lootbottles_return ( + token_short_name VARCHAR NOT NULL, + network network_blockchain NOT NULL, + time TIMESTAMP WITHOUT TIME ZONE NOT NULL, + transaction_hash VARCHAR NOT NULL, + sender_address VARCHAR NOT NULL, + recipient_address VARCHAR NOT NULL, + amount DOUBLE PRECISION NOT NULL, + application VARCHAR NOT NULL, + winning_amount DOUBLE PRECISION NOT NULL, + winning_address VARCHAR NOT NULL, + reward_hash VARCHAR NOT NULL, + type user_action NOT NULL, + swap_in BOOLEAN NOT NULL, + utility_amount DOUBLE PRECISION NOT NULL, + utility_name VARCHAR NOT NULL, + + lootbox_count DECIMAL, + reward_tier INTEGER +); + +CREATE FUNCTION aggregated_user_transactions_lootbottles() +RETURNS SETOF aggregated_user_transactions_lootbottles_return +LANGUAGE SQL +STABLE +AS +$$ +SELECT + token_short_name, + network, + time, + transaction_hash, + sender_address, + recipient_address, + amount, + application, + winning_amount, + winning_address, + reward_hash, + type user_action, + swap_in, + utility_amount, + utility_name, + lootbox_count, + reward_tier +FROM + aggregated_user_transactions +LEFT JOIN ( + SELECT reward_tier, lootbox_count FROM lootbox +) AS lootbox_user_actions ON transaction_hash = aggregated_user_transactions.transaction_hash +$$; + +-- migrate:down + +DROP TABLE user_actions_lootbottles_return; + +DROP FUNCTION user_actions_lootbottles; diff --git a/lib/databases/timescale/lootboxes/config.go b/lib/databases/timescale/lootboxes/config.go new file mode 100644 index 000000000..5f8e1f12e --- /dev/null +++ b/lib/databases/timescale/lootboxes/config.go @@ -0,0 +1,101 @@ +// Copyright 2023 Fluidity Money. All rights reserved. Use of this +// source code is governed by a GPL-style license that can be found in the +// LICENSE.md file. + +package lootboxes + +import ( + "database/sql" + "fmt" + + "github.com/fluidity-money/fluidity-app/common/ethereum/applications" + "github.com/fluidity-money/fluidity-app/lib/log" + "github.com/fluidity-money/fluidity-app/lib/timescale" +) + +const ( + Epoch1 = "epoch_1" + Epoch2 = "epoch_2" + EpochTesting = "epoch_testing" +) + +// GetConfig that's currently marked as enabled, also getting whether the +// program has currently begun or not. Fatals if the number of rows returned +// exceeds 1. +func GetLootboxConfig() (programFound bool, hasBegun bool, curEpoch string, curApplication applications.Application) { + timescaleClient := timescale.Client() + + statementText := fmt.Sprintf( + `WITH t AS ( + SELECT CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS timestamp + ) + SELECT + program_begin < (select timestamp from t) AND + program_end > (select timestamp from t), + epoch_identifier, + current_application + FROM %s + WHERE is_current_program;`, + + TableLootboxConfig, + ) + + rows, err := timescaleClient.Query(statementText) + + switch err { + case sql.ErrNoRows: + return false, false, "", applications.ApplicationNone + + case nil: + // do nothing + + default: + log.Fatal(func(k *log.Log) { + k.Context = Context + k.Message = "Failed to query for lootbox config!" + k.Payload = err + }) + } + + defer rows.Close() + + if !rows.Next() { + return false, false, "", applications.ApplicationNone + } + + var applicationString string + + err = rows.Scan(&hasBegun, &curEpoch, &applicationString) + + if err != nil { + log.Fatal(func(k *log.Log) { + k.Context = Context + k.Message = "Failed to query for lootbox config!" + k.Payload = err + }) + } + + curApplication, err = applications.ParseApplicationName(applicationString) + + if err != nil { + log.Fatal(func(k *log.Log) { + k.Context = Context + + k.Format( + "Failed to parse the Ethereum application name %#v!", + applicationString, + ) + + k.Payload = err + }) + } + + if rows.Next() { + log.Fatal(func(k *log.Log) { + k.Context = Context + k.Message = "Duplicate enabled row for the lootbox_config found!" + }) + } + + return true, hasBegun, curEpoch, curApplication +} diff --git a/lib/databases/timescale/lootboxes/lootboxes.go b/lib/databases/timescale/lootboxes/lootboxes.go index 471c89e20..55e46c516 100644 --- a/lib/databases/timescale/lootboxes/lootboxes.go +++ b/lib/databases/timescale/lootboxes/lootboxes.go @@ -1,3 +1,7 @@ +// Copyright 2023 Fluidity Money. All rights reserved. Use of this +// source code is governed by a GPL-style license that can be found in the +// LICENSE.md file. + package lootboxes import ( @@ -17,6 +21,14 @@ const ( // TableLootboxes to use when inserting // derived lootboxes to database TableLootboxes = `lootbox` + + // TableLootboxConfig to use for setting configuration + // for the currently running epoch + TableLootboxConfig = `lootbox_config` + + // TableLootboxAmountsRewarded to use for tracking cumulative + // amounts earned by users during a lootbox campaign + TableLootboxAmountsRewarded = `lootbox_amounts_rewarded` ) type Lootbox = types.Lootbox @@ -34,7 +46,8 @@ func InsertLootbox(lootbox Lootbox) { volume, reward_tier, lootbox_count, - application + application, + epoch ) VALUES ( @@ -45,7 +58,8 @@ func InsertLootbox(lootbox Lootbox) { $5, $6, $7, - $8 + $8, + $9 )`, TableLootboxes, @@ -61,6 +75,7 @@ func InsertLootbox(lootbox Lootbox) { lootbox.RewardTier, lootbox.LootboxCount, lootbox.Application.String(), + lootbox.Epoch, ) if err != nil { @@ -73,7 +88,7 @@ func InsertLootbox(lootbox Lootbox) { } // GetLootboxes gets all Lootboxes earned by address, limited by a number -func GetLootboxes(address ethereum.Address, limit int) []Lootbox { +func GetLootboxes(currentEpoch string, address ethereum.Address, limit int) []Lootbox { timescaleClient := timescale.Client() statementText := fmt.Sprintf( @@ -87,9 +102,9 @@ func GetLootboxes(address ethereum.Address, limit int) []Lootbox { lootbox_count, application - FROM %s - WHERE address = $1 - LIMIT $2 + FROM %s + WHERE address = $1 AND epoch = $2 + LIMIT $3 `, TableLootboxes, @@ -98,6 +113,7 @@ func GetLootboxes(address ethereum.Address, limit int) []Lootbox { rows, err := timescaleClient.Query( statementText, address, + currentEpoch, limit, ) @@ -143,6 +159,8 @@ func GetLootboxes(address ethereum.Address, limit int) []Lootbox { }) } + lootbox.Epoch = currentEpoch + application, err := applications.ParseApplicationName(application_) if err != nil { diff --git a/lib/databases/timescale/lootboxes/rewards.go b/lib/databases/timescale/lootboxes/rewards.go index 430b7abd4..a0df54a3b 100644 --- a/lib/databases/timescale/lootboxes/rewards.go +++ b/lib/databases/timescale/lootboxes/rewards.go @@ -13,6 +13,7 @@ import ( "github.com/fluidity-money/fluidity-app/lib/log" "github.com/fluidity-money/fluidity-app/lib/timescale" "github.com/fluidity-money/fluidity-app/lib/types/applications" + "github.com/fluidity-money/fluidity-app/lib/types/network" ) const ExpectedTopUsers = 10 @@ -33,32 +34,32 @@ func buildUserRewardValuesString(userCount int) (string, error) { } rewardStrings := []string{ - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none')`, + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2)`, - `($3, '', 'leaderboard_prize', $1, 0, 1, 20, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 2, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 3, 2, 'none')`, + `($4, '', 'leaderboard_prize', $1, 0, 1, 20, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 2, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 3, 2, 'none', $2)`, - `($4, '', 'leaderboard_prize', $1, 0, 1, 15, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 2, 3, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 3, 1, 'none')`, + `($5, '', 'leaderboard_prize', $1, 0, 1, 15, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 2, 3, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 3, 1, 'none', $2)`, - `($5, '', 'leaderboard_prize', $1, 0, 1, 12, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 2, 1, 'none')`, + `($6, '', 'leaderboard_prize', $1, 0, 1, 12, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 2, 1, 'none', $2)`, - "($6, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')", + "($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)", - "($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')", + "($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)", - "($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')", + "($9, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)", - "($9, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')", + "($10, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)", - "($10, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')", + "($11, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)", - "($11, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')", + "($12, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)", } var output strings.Builder @@ -100,7 +101,7 @@ func buildUserRewardValuesString(userCount int) (string, error) { // InsertTopUserReward to insert lootboxes for the given users for their activity during the airdrop. // Expects 10 users to reward -func InsertTopUserReward(currentTime time.Time, users []UserLootboxCount) { +func InsertTopUserReward(currentEpoch string, currentTime time.Time, users []UserLootboxCount) { valuesString, err := buildUserRewardValuesString(len(users)) if err != nil { @@ -114,14 +115,15 @@ func InsertTopUserReward(currentTime time.Time, users []UserLootboxCount) { statementText := fmt.Sprintf( `INSERT INTO %s ( - address, - transaction_hash, - source, - awarded_time, - volume, - reward_tier, - lootbox_count, - application + address, + transaction_hash, + source, + awarded_time, + volume, + reward_tier, + lootbox_count, + application, + epoch ) VALUES %s`, TableLootboxes, @@ -129,8 +131,7 @@ func InsertTopUserReward(currentTime time.Time, users []UserLootboxCount) { ) // build variadic arguments [time, users...] - var args []interface{} - args = append(args, currentTime) + args := []interface{}{currentTime, currentEpoch} for _, user := range users { args = append(args, user.Address) @@ -151,21 +152,22 @@ func InsertTopUserReward(currentTime time.Time, users []UserLootboxCount) { } // fetch the 10 addresses with the highest lootboxes earned during the given period -func GetTopUsersByLootboxCount(startTime, endTime time.Time) []UserLootboxCount { +func GetTopUsersByLootboxCount(currentEpoch string, startTime, endTime time.Time) []UserLootboxCount { timescaleClient := timescale.Client() statementText := fmt.Sprintf( - `SELECT - address, - SUM(lootbox_count) AS lootbox_count + `SELECT + address, + SUM(lootbox_count) AS lootbox_count FROM %s - WHERE + WHERE awarded_time >= $1 AT TIME ZONE $3 AND - awarded_time < $2 AT TIME ZONE $4 AND - source != 'leaderboard_prize' - GROUP BY address + awarded_time < $2 AT TIME ZONE $4 AND + source != 'leaderboard_prize' AND + epoch = $5 + GROUP BY address ORDER BY lootbox_count DESC - LIMIT $5`, + LIMIT $6`, TableLootboxes, ) @@ -176,6 +178,7 @@ func GetTopUsersByLootboxCount(startTime, endTime time.Time) []UserLootboxCount endTime, LootboxRewardTimezone, LootboxRewardTimezone, + currentEpoch, 10, ) @@ -193,10 +196,8 @@ func GetTopUsersByLootboxCount(startTime, endTime time.Time) []UserLootboxCount for rows.Next() { var user UserLootboxCount - err := rows.Scan( - &user.Address, - &user.LootboxCount, - ) + + err := rows.Scan(&user.Address, &user.LootboxCount) if err != nil { log.Fatal(func(k *log.Log) { @@ -215,20 +216,21 @@ func GetTopUsersByLootboxCount(startTime, endTime time.Time) []UserLootboxCount const LootboxRewardTimezone = "Australia/Adelaide" // fetch the 10 addresses with the highest lootboxes earned during the given period on the given application -func GetTopApplicationUsersByLootboxCount(startTime, endTime time.Time, application applications.Application) []UserLootboxCount { +func GetTopApplicationUsersByLootboxCount(currentEpoch string, startTime, endTime time.Time, application applications.Application) []UserLootboxCount { timescaleClient := timescale.Client() statementText := fmt.Sprintf( - `SELECT - address, - SUM(lootbox_count) AS lootbox_count + `SELECT + address, + SUM(lootbox_count) AS lootbox_count FROM %s - WHERE + WHERE awarded_time >= $1 AT TIME ZONE $3 AND - awarded_time < $2 AT TIME ZONE $4 AND + awarded_time < $2 AT TIME ZONE $4 AND source != 'leaderboard_prize' AND - application = $6 - GROUP BY address + application = $6 AND + epoch = $7 + GROUP BY address ORDER BY lootbox_count DESC LIMIT $5`, @@ -243,6 +245,7 @@ func GetTopApplicationUsersByLootboxCount(startTime, endTime time.Time, applicat LootboxRewardTimezone, 10, application.String(), + currentEpoch, ) if err != nil { @@ -259,10 +262,8 @@ func GetTopApplicationUsersByLootboxCount(startTime, endTime time.Time, applicat for rows.Next() { var user UserLootboxCount - err := rows.Scan( - &user.Address, - &user.LootboxCount, - ) + + err := rows.Scan(&user.Address, &user.LootboxCount) if err != nil { log.Fatal(func(k *log.Log) { @@ -277,3 +278,54 @@ func GetTopApplicationUsersByLootboxCount(startTime, endTime time.Time, applicat return topUsers } + +// UpdateOrInsertAmountsRewarded for the current user in the specific +// epoch that's taking place. +func UpdateOrInsertAmountsRewarded(network_ network.BlockchainNetwork, lootboxCurrentEpoch, tokenShortName string, amountNormalLossy float64, winnerAddress, application string) { + timescaleClient := timescale.Client() + + statementText := fmt.Sprintf( + `INSERT INTO %s ( + network, + epoch, + token_short_name, + amount_earned, + winner, + application, + last_updated + ) + VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7 + ) + ON CONFLICT (id) + DO UPDATE SET + amount_earned = amount_earned + $4, + last_updated = CURRENT_TIMESTAMP`, + + TableLootboxAmountsRewarded, + ) + + _, err := timescaleClient.Exec( + statementText, + network_, + lootboxCurrentEpoch, + tokenShortName, + amountNormalLossy, + application, + winnerAddress, + ) + + if err != nil { + log.Fatal(func(k *log.Log) { + k.Context = Context + k.Message = "Failed to upsert a lootbox amount rewarded!" + k.Payload = err + }) + } +} diff --git a/lib/databases/timescale/lootboxes/rewards_test.go b/lib/databases/timescale/lootboxes/rewards_test.go index 7a74779d2..707382fa6 100644 --- a/lib/databases/timescale/lootboxes/rewards_test.go +++ b/lib/databases/timescale/lootboxes/rewards_test.go @@ -9,131 +9,131 @@ import ( func TestBuildUserRewardValuesString(t *testing.T) { expectedRewardStrings := []string{ - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none')`, - - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 1, 20, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 2, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 3, 2, 'none')`, - - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 1, 20, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 2, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 3, 2, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 1, 15, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 2, 3, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 3, 1, 'none')`, - - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 1, 20, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 2, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 3, 2, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 1, 15, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 2, 3, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 3, 1, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 1, 12, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 2, 1, 'none')`, - - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 1, 20, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 2, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 3, 2, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 1, 15, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 2, 3, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 3, 1, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 1, 12, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 2, 1, 'none'), -($6, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')`, - - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 1, 20, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 2, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 3, 2, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 1, 15, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 2, 3, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 3, 1, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 1, 12, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 2, 1, 'none'), -($6, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')`, - - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 1, 20, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 2, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 3, 2, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 1, 15, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 2, 3, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 3, 1, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 1, 12, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 2, 1, 'none'), -($6, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')`, - - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 1, 20, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 2, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 3, 2, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 1, 15, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 2, 3, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 3, 1, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 1, 12, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 2, 1, 'none'), -($6, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($9, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')`, - - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 1, 20, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 2, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 3, 2, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 1, 15, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 2, 3, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 3, 1, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 1, 12, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 2, 1, 'none'), -($6, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($9, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($10, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')`, - - `($2, '', 'leaderboard_prize', $1, 0, 1, 30, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 2, 10, 'none'), -($2, '', 'leaderboard_prize', $1, 0, 3, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 1, 20, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 2, 5, 'none'), -($3, '', 'leaderboard_prize', $1, 0, 3, 2, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 1, 15, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 2, 3, 'none'), -($4, '', 'leaderboard_prize', $1, 0, 3, 1, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 1, 12, 'none'), -($5, '', 'leaderboard_prize', $1, 0, 2, 1, 'none'), -($6, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($9, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($10, '', 'leaderboard_prize', $1, 0, 1, 10, 'none'), -($11, '', 'leaderboard_prize', $1, 0, 1, 10, 'none')`, + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2)`, + + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 1, 20, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 2, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 3, 2, 'none', $2)`, + + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 1, 20, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 2, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 3, 2, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 1, 15, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 2, 3, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 3, 1, 'none', $2)`, + + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 1, 20, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 2, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 3, 2, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 1, 15, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 2, 3, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 3, 1, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 1, 12, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 2, 1, 'none', $2)`, + + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 1, 20, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 2, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 3, 2, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 1, 15, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 2, 3, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 3, 1, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 1, 12, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 2, 1, 'none', $2), +($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)`, + + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 1, 20, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 2, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 3, 2, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 1, 15, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 2, 3, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 3, 1, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 1, 12, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 2, 1, 'none', $2), +($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)`, + + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 1, 20, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 2, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 3, 2, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 1, 15, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 2, 3, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 3, 1, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 1, 12, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 2, 1, 'none', $2), +($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($9, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)`, + + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 1, 20, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 2, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 3, 2, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 1, 15, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 2, 3, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 3, 1, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 1, 12, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 2, 1, 'none', $2), +($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($9, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($10, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)`, + + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 1, 20, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 2, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 3, 2, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 1, 15, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 2, 3, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 3, 1, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 1, 12, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 2, 1, 'none', $2), +($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($9, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($10, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($11, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)`, + + `($3, '', 'leaderboard_prize', $1, 0, 1, 30, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 2, 10, 'none', $2), +($3, '', 'leaderboard_prize', $1, 0, 3, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 1, 20, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 2, 5, 'none', $2), +($4, '', 'leaderboard_prize', $1, 0, 3, 2, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 1, 15, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 2, 3, 'none', $2), +($5, '', 'leaderboard_prize', $1, 0, 3, 1, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 1, 12, 'none', $2), +($6, '', 'leaderboard_prize', $1, 0, 2, 1, 'none', $2), +($7, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($8, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($9, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($10, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($11, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2), +($12, '', 'leaderboard_prize', $1, 0, 1, 10, 'none', $2)`, } for i, expectedString := range expectedRewardStrings { diff --git a/lib/queues/lootboxes/lootboxes.go b/lib/queues/lootboxes/lootboxes.go index a0a886737..a6ec6d3aa 100644 --- a/lib/queues/lootboxes/lootboxes.go +++ b/lib/queues/lootboxes/lootboxes.go @@ -12,13 +12,9 @@ import ( types "github.com/fluidity-money/fluidity-app/lib/types/lootboxes" ) -const ( - TopicLootboxes = `lootboxes` -) +const TopicLootboxes = `lootboxes` -type ( - Lootbox = types.Lootbox -) +type Lootbox = types.Lootbox func LootboxesAll(f func(Lootbox)) { queue.GetMessages(TopicLootboxes, func(message queue.Message) { diff --git a/lib/types/lootboxes/lootboxes.go b/lib/types/lootboxes/lootboxes.go index 0b2998262..d13522184 100644 --- a/lib/types/lootboxes/lootboxes.go +++ b/lib/types/lootboxes/lootboxes.go @@ -38,4 +38,7 @@ type Lootbox struct { // Application is the application involved in the source transfer Application applications.Application `json:"application"` + + // Epoch of the lootbox program thats taking place + Epoch string `json:"epoch"` } diff --git a/tests/pipeline/transaction_lootbox_test.go b/tests/pipeline/transaction_lootbox_test.go index 16a42f7cc..fc854cedd 100644 --- a/tests/pipeline/transaction_lootbox_test.go +++ b/tests/pipeline/transaction_lootbox_test.go @@ -116,7 +116,7 @@ func TestTransactionLootboxes(t *testing.T) { time.Sleep(time.Second) - allLootboxes := lootboxes_db.GetLootboxes(senderAddress, 1) + allLootboxes := lootboxes_db.GetLootboxes("epoch_1", senderAddress, 1) assert.Len(t, allLootboxes, 1) expectedLootbox := lootboxes.Lootbox{ diff --git a/web/app.fluidity.money/app/components/JoeFarmlandsOrCamelotKingdom/index.tsx b/web/app.fluidity.money/app/components/JoeFarmlandsOrCamelotKingdom/index.tsx new file mode 100644 index 000000000..1339df2c4 --- /dev/null +++ b/web/app.fluidity.money/app/components/JoeFarmlandsOrCamelotKingdom/index.tsx @@ -0,0 +1,26 @@ +import styles from "./styles.css"; + +export const JoeFarmlandsOrCamelotKingdomLinks = () => [ + { rel: "stylesheet", href: styles }, +]; + +const JoeFarmlandsOrCamelotKingdom = () => { + return ( +
+ + + + + + +
+ ); +}; + +export default JoeFarmlandsOrCamelotKingdom; diff --git a/web/app.fluidity.money/app/components/JoeFarmlandsOrCamelotKingdom/styles.css b/web/app.fluidity.money/app/components/JoeFarmlandsOrCamelotKingdom/styles.css new file mode 100644 index 000000000..2ac505afc --- /dev/null +++ b/web/app.fluidity.money/app/components/JoeFarmlandsOrCamelotKingdom/styles.css @@ -0,0 +1,35 @@ +.joe_farmlands_or_camelot_div { + display: grid; + padding-top: 20px; +} + +.joe_farmlands_or_camelot_div a { + grid-area: 1/1; + --_p: calc(-1 * var(--g)); +} + +.joe_farmlands_or_camelot_div img { + width: 100%; + border-radius: 10px; +} + +.joe_farmlands_or_camelot_img_joe { + clip-path: polygon(0% 0%, 0% 100%, 100% 10%); +} + +.joe_farmlands_or_camelot_img_camelot { + padding-top: 12px; + clip-path: polygon(100% 0, 0% 100%, 100% 100%); +} + +.joe_farmlands_or_camelot_div img:hover { + transform-origin: 50% 50% 0; + animation: joe_farmlands_or_camelot_img_scale 100ms ease-in-out forwards; + z-index: -999; +} + +@keyframes joe_farmlands_or_camelot_img_scale { + to { + transform: scale(1.025); + } +} diff --git a/web/app.fluidity.money/app/components/UtilityToken.tsx b/web/app.fluidity.money/app/components/UtilityToken.tsx index d9cee2329..b71f71e26 100644 --- a/web/app.fluidity.money/app/components/UtilityToken.tsx +++ b/web/app.fluidity.money/app/components/UtilityToken.tsx @@ -10,7 +10,12 @@ const UtilityToken = ({ utility, ...imgProps }: IUtilityToken) => { return ; case "sushi": return ; - case "trader": + case "trader_joe": + return ; + case "ramses": + return ; + case "jumper": + return ; case "arb": return ; default: diff --git a/web/app.fluidity.money/app/components/index.ts b/web/app.fluidity.money/app/components/index.ts index df7ea69c4..268d18965 100644 --- a/web/app.fluidity.money/app/components/index.ts +++ b/web/app.fluidity.money/app/components/index.ts @@ -6,3 +6,4 @@ export { ToolTipContent, } from "./ToolTip"; export { UtilityToken } from "./UtilityToken"; +export { JoeFarmlandsOrCamelotKingdomLinks } from "./JoeFarmlandsOrCamelotKingdom"; diff --git a/web/app.fluidity.money/app/queries/addReferral.ts b/web/app.fluidity.money/app/queries/addReferral.ts index aaf284901..2222c90c3 100644 --- a/web/app.fluidity.money/app/queries/addReferral.ts +++ b/web/app.fluidity.money/app/queries/addReferral.ts @@ -40,8 +40,10 @@ const addReferral = (referrer: string, referee: string) => { variables, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + return jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, body, process.env.FLU_HASURA_SECRET ? { diff --git a/web/app.fluidity.money/app/queries/addReferralCode.ts b/web/app.fluidity.money/app/queries/addReferralCode.ts index 0cccdca74..5aba12969 100644 --- a/web/app.fluidity.money/app/queries/addReferralCode.ts +++ b/web/app.fluidity.money/app/queries/addReferralCode.ts @@ -40,8 +40,10 @@ const addReferralCode = (address: string, code: string) => { variables, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + return jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, body, process.env.FLU_HASURA_SECRET ? { diff --git a/web/app.fluidity.money/app/queries/index.ts b/web/app.fluidity.money/app/queries/index.ts index f71e1a3ea..a959ac1e8 100644 --- a/web/app.fluidity.money/app/queries/index.ts +++ b/web/app.fluidity.money/app/queries/index.ts @@ -9,3 +9,4 @@ export * from "./useReferralCount"; export * from "./addReferral"; export * from "./useReferralCode"; export * from "./addReferralCode"; +export * from "./useLootboxConfig"; diff --git a/web/app.fluidity.money/app/queries/useAirdropLeaderboard.ts b/web/app.fluidity.money/app/queries/useAirdropLeaderboard.ts index a5326094b..c85fc0391 100644 --- a/web/app.fluidity.money/app/queries/useAirdropLeaderboard.ts +++ b/web/app.fluidity.money/app/queries/useAirdropLeaderboard.ts @@ -1,7 +1,7 @@ import { jsonPost, gql, fetchInternalEndpoint } from "~/util"; const queryByUserAllTime = gql` - query AirdropLeaderboard($address: String!) { + query AirdropLeaderboard($epoch: lootbox_epoch!, $address: String!) { airdrop_leaderboard(where: { address: { _eq: $address } }, limit: 1) { user: address rank @@ -14,21 +14,28 @@ const queryByUserAllTime = gql` `; const queryAllTime = gql` - query AirdropLeaderboard { - airdrop_leaderboard(limit: 16, order_by: { total_lootboxes: desc }) { + query AirdropLeaderboard($epoch: lootbox_epoch!) { + airdrop_leaderboard( + args: { epoch_: $epoch } + limit: 16 + order_by: { total_lootboxes: desc } + ) { user: address rank referralCount: referral_count bottles: total_lootboxes highestRewardTier: highest_reward_tier liquidityMultiplier: liquidity_multiplier + fusdcEarned: fusdc_earned + arbEarned: arb_earned } } `; const queryByUser24Hours = gql` - query AirdropLeaderboard($address: String!) { + query AirdropLeaderboard($epoch: lootbox_epoch!, $address: String!) { airdrop_leaderboard: airdrop_leaderboard_24_hours( + args: { epoch_: $epoch, application_: $application } where: { address: { _eq: $address } } limit: 1 ) { @@ -38,6 +45,8 @@ const queryByUser24Hours = gql` bottles: total_lootboxes highestRewardTier: highest_reward_tier liquidityMultiplier: liquidity_multiplier + fusdcEarned: fusdc_earned + arbEarned: arb_earned } } `; @@ -45,6 +54,7 @@ const queryByUser24Hours = gql` const query24Hours = gql` query AirdropLeaderboard { airdrop_leaderboard: airdrop_leaderboard_24_hours( + args: { epoch_: $epoch } limit: 16 order_by: { total_lootboxes: desc } ) { @@ -54,17 +64,20 @@ const query24Hours = gql` bottles: total_lootboxes highestRewardTier: highest_reward_tier liquidityMultiplier: liquidity_multiplier + fusdcEarned: fusdc_earned + arbEarned: arb_earned } } `; const query24HoursByUserByApplication = gql` query AirdropLeaderboardApplication( + $epoch: lootbox_epoch! $application: ethereum_application! $address: String! ) { airdrop_leaderboard: airdrop_leaderboard_24_hours_by_application( - args: { application_: $application } + args: { epoch_: $epoch, application_: $application } where: { address: { _eq: $address } } limit: 1 ) { @@ -74,14 +87,19 @@ const query24HoursByUserByApplication = gql` bottles: total_lootboxes highestRewardTier: highest_reward_tier liquidityMultiplier: liquidity_multiplier + fusdcEarned: fusdc_earned + arbEarned: arb_earned } } `; const query24HoursByApplication = gql` - query AirdropLeaderboardByApplication($application: ethereum_application!) { + query AirdropLeaderboardByApplication( + $epoch: lootbox_epoch! + $application: ethereum_application! + ) { airdrop_leaderboard: airdrop_leaderboard_24_hours_by_application( - args: { application_: $application } + args: { epoch_: $epoch, application_: $application } limit: 16 order_by: { total_lootboxes: desc } ) { @@ -91,6 +109,8 @@ const query24HoursByApplication = gql` bottles: total_lootboxes highestRewardTier: highest_reward_tier liquidityMultiplier: liquidity_multiplier + fusdcEarned: fusdc_earned + arbEarned: arb_earned } } `; @@ -125,6 +145,8 @@ export type AirdropLeaderboardEntry = { bottles: number; highestRewardTier: number; liquidityMultiplier: number; + fusdcEarned: number; + arbEarned: number; }; type AirdropLeaderboardResponse = { @@ -134,10 +156,14 @@ type AirdropLeaderboardResponse = { errors?: unknown; }; -export const useAirdropLeaderboardByUserAllTime = (address: string) => { +export const useAirdropLeaderboardByUserAllTime = ( + epoch: string, + address: string +) => { const { url, headers } = fetchInternalEndpoint(); const variables = { + epoch, address, }; const body = { @@ -152,10 +178,12 @@ export const useAirdropLeaderboardByUserAllTime = (address: string) => { ); }; -export const useAirdropLeaderboardAllTime = () => { +export const useAirdropLeaderboardAllTime = (epoch: string) => { const { url, headers } = fetchInternalEndpoint(); + const variables = { epoch }; const body = { query: queryAllTime, + variables, }; return jsonPost( @@ -165,11 +193,15 @@ export const useAirdropLeaderboardAllTime = () => { ); }; -export const useAirdropLeaderboardByUser24Hours = (address: string) => { +export const useAirdropLeaderboardByUser24Hours = ( + epoch: string, + address: string +) => { const { url, headers } = fetchInternalEndpoint(); const variables = { address, + epoch, }; const body = { query: queryByUser24Hours, @@ -183,9 +215,11 @@ export const useAirdropLeaderboardByUser24Hours = (address: string) => { ); }; -export const useAirdropLeaderboard24Hours = () => { +export const useAirdropLeaderboard24Hours = (epoch: string) => { const { url, headers } = fetchInternalEndpoint(); + const variables = { epoch }; const body = { + variables, query: query24Hours, }; @@ -197,12 +231,14 @@ export const useAirdropLeaderboard24Hours = () => { }; export const useAirdropLeaderboardByUserByApplication24Hours = ( + epoch: string, address: string, application: string ) => { const { url, headers } = fetchInternalEndpoint(); const variables = { + epoch, address, application, }; @@ -218,10 +254,12 @@ export const useAirdropLeaderboardByUserByApplication24Hours = ( }; export const useAirdropLeaderboardByApplication24Hours = ( + epoch: string, application: string ) => { const { url, headers } = fetchInternalEndpoint(); const variables = { + epoch, application, }; const body = { diff --git a/web/app.fluidity.money/app/queries/useAirdropStats.ts b/web/app.fluidity.money/app/queries/useAirdropStats.ts index 25828ab32..f712c4d77 100644 --- a/web/app.fluidity.money/app/queries/useAirdropStats.ts +++ b/web/app.fluidity.money/app/queries/useAirdropStats.ts @@ -3,12 +3,15 @@ import { BottleTiers } from "~/routes/$network/query/dashboard/airdrop"; import { jsonPost, gql, fetchInternalEndpoint } from "~/util"; const queryAirdropStatsByAddress = gql` - query AirdropStats($address: String!, $now: timestamp!) { - lootboxCounts: lootbox_counts(where: { address: { _eq: $address } }) { + query AirdropStats($epoch: lootbox_epoch!, $address: String!, $now: timestamp!) { + lootboxCounts: lootbox_counts( + args: { epoch_: $epoch } + where: { address: { _eq: $address } } + ) { ${Rarity.Common}: tier1 ${Rarity.Uncommon}: tier2 ${Rarity.Rare}: tier3 - ${Rarity.UltraRare}: tier4 + ${Rarity.UltraRare}: tier4 ${Rarity.Legendary}: tier5 } liquidityMultiplier: calculate_a_y( @@ -26,11 +29,15 @@ const queryAirdropStatsByAddress = gql` } `; -export const useAirdropStatsByAddress = async (address: string) => { +export const useAirdropStatsByAddress = async ( + address: string, + epoch: string +) => { const { url, headers } = fetchInternalEndpoint(); const variables = { address, + epoch, now: new Date().toISOString(), }; const body = { diff --git a/web/app.fluidity.money/app/queries/useApplicationRewardStatistics.ts b/web/app.fluidity.money/app/queries/useApplicationRewardStatistics.ts index a5a5336b5..e6252aba7 100644 --- a/web/app.fluidity.money/app/queries/useApplicationRewardStatistics.ts +++ b/web/app.fluidity.money/app/queries/useApplicationRewardStatistics.ts @@ -41,7 +41,9 @@ const useApplicationRewardStatistics = async ( network: T | string ) => { const variables = { network }; + const url = "https://fluidity.hasura.app/v1/graphql"; + const body = { variables, query: query, diff --git a/web/app.fluidity.money/app/queries/useAssetStatistics.ts b/web/app.fluidity.money/app/queries/useAssetStatistics.ts index 11bd89fe2..366fb6828 100644 --- a/web/app.fluidity.money/app/queries/useAssetStatistics.ts +++ b/web/app.fluidity.money/app/queries/useAssetStatistics.ts @@ -86,10 +86,10 @@ const useAssetStatistics = ( }, }; - const fluGqlEndpoint = "https://fluidity.hasura.app/v1/graphql"; + const url = "https://fluidity.hasura.app/v1/graphql"; return jsonPost( - fluGqlEndpoint, + url, body, process.env.FLU_HASURA_SECRET ? { diff --git a/web/app.fluidity.money/app/queries/useLootBottles.ts b/web/app.fluidity.money/app/queries/useLootBottles.ts index f48ec6542..6f5025298 100644 --- a/web/app.fluidity.money/app/queries/useLootBottles.ts +++ b/web/app.fluidity.money/app/queries/useLootBottles.ts @@ -41,8 +41,10 @@ const useLootboxesByTxHash = (filterHashes: string[]) => { variables, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + return jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, body, process.env.FLU_HASURA_SECRET ? { diff --git a/web/app.fluidity.money/app/queries/useLootBottlesCount.ts b/web/app.fluidity.money/app/queries/useLootBottlesCount.ts index 60f1927c4..34c28658f 100644 --- a/web/app.fluidity.money/app/queries/useLootBottlesCount.ts +++ b/web/app.fluidity.money/app/queries/useLootBottlesCount.ts @@ -3,8 +3,11 @@ import { BottleTiers } from "~/routes/$network/query/dashboard/airdrop"; import { jsonPost, gql, fetchInternalEndpoint } from "~/util"; const QUERY_REFERRALS_BY_ADDRESS = gql` - query getReferralLootboxCountByAddress($address: String!) { - referralLootboxCounts: lootbox_referral_lootbottle_counts(where: { address: { _eq: $address } }) { + query getReferralLootboxCountByAddress($epoch: String!, $address: String!) { + referralLootboxCounts: lootbox_referral_lootbottle_counts( + args: { epoch_: $epoch } + where: { address: { _eq: $address } } + ) { ${Rarity.Common}: tier1 ${Rarity.Uncommon}: tier2 ${Rarity.Rare}: tier3 @@ -18,6 +21,7 @@ type ReferralLootboxCountBody = { query: string; variables: { address: string; + epoch: string; }; }; @@ -28,10 +32,11 @@ type ReferralLootboxCountRes = { errors?: unknown; }; -const useReferralLootboxesByAddress = (address: string) => { +const useReferralLootboxesByAddress = (epoch: string, address: string) => { const { url, headers } = fetchInternalEndpoint(); const variables = { + epoch, address, }; diff --git a/web/app.fluidity.money/app/queries/useLootboxConfig.ts b/web/app.fluidity.money/app/queries/useLootboxConfig.ts new file mode 100644 index 000000000..e2aed0dc1 --- /dev/null +++ b/web/app.fluidity.money/app/queries/useLootboxConfig.ts @@ -0,0 +1,72 @@ +import { jsonPost, gql, fetchInternalEndpoint } from "~/util"; + +const queryGetConfigCurrentProgram = gql` + query getLootboxConfig { + lootboxConfig: lootbox_config( + where: { is_current_program: { _eq: true } } + limit: 1 + ) { + programBegin: program_begin + programEnd: program_end + epochIdentifier: epoch_identifier + ethereumApplication: ethereum_application + } + } +`; + +const queryGetConfigSpecific = gql` + query getLootboxConfig($identifier: lootbox_epoch!) { + lootboxConfig: lootbox_config( + where: { epoch_identifier: { _eq: $identifier } } + limit: 1 + ) { + programBegin: program_begin + programEnd: program_end + epochIdentifier: epoch_identifier + ethereumApplication: ethereum_application + } + } +`; + +type LootboxConfigBody = { + query: string; +}; + +export type LootboxConfig = { + programBegin: string; + programEnd: string; + epochIdentifier: string; + ethereumApplication: string; + found: boolean; +}; + +type LootboxConfigResponse = { + data?: { + lootboxConfig: Array; + }; + errors?: unknown; +}; + +type IUseLootboxConfig = { + identifier: string | undefined; + shouldFind: boolean | undefined; +}; + +export const useLootboxConfig = ({ + identifier, + shouldFind, +}: IUseLootboxConfig) => { + const { url, headers } = fetchInternalEndpoint(); + + const body = (() => + shouldFind + ? { + query: queryGetConfigCurrentProgram, + } + : { + query: queryGetConfigSpecific, + variables: { identifier }, + })(); + + return jsonPost(url, body, headers); +}; diff --git a/web/app.fluidity.money/app/queries/useReferralCode.ts b/web/app.fluidity.money/app/queries/useReferralCode.ts index b6cd6c5d9..75db76633 100644 --- a/web/app.fluidity.money/app/queries/useReferralCode.ts +++ b/web/app.fluidity.money/app/queries/useReferralCode.ts @@ -54,8 +54,10 @@ const useReferralCodeByAddress = (address: string) => { variables, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + return jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, body, process.env.FLU_HASURA_SECRET ? { @@ -75,8 +77,10 @@ const useReferralCodeByCode = (code: string) => { variables, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + return jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, body, process.env.FLU_HASURA_SECRET ? { diff --git a/web/app.fluidity.money/app/queries/useReferralCount.ts b/web/app.fluidity.money/app/queries/useReferralCount.ts index aabd612b2..0a6ed9fec 100644 --- a/web/app.fluidity.money/app/queries/useReferralCount.ts +++ b/web/app.fluidity.money/app/queries/useReferralCount.ts @@ -64,8 +64,10 @@ const useActiveReferralCountByReferrerAddress = (address: string) => { variables, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + return jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, body, process.env.FLU_HASURA_SECRET ? { @@ -85,8 +87,10 @@ const useActiveReferralCountByRefereeAddress = (address: string) => { variables, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + return jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, body, process.env.FLU_HASURA_SECRET ? { @@ -106,8 +110,10 @@ const useInactiveReferralCountByRefereeAddress = (address: string) => { variables, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + return jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, body, process.env.FLU_HASURA_SECRET ? { diff --git a/web/app.fluidity.money/app/queries/useReferrals.ts b/web/app.fluidity.money/app/queries/useReferrals.ts index 0da3ee93a..731f4b8af 100644 --- a/web/app.fluidity.money/app/queries/useReferrals.ts +++ b/web/app.fluidity.money/app/queries/useReferrals.ts @@ -71,8 +71,10 @@ const useReferralByAddress = (referrer: string, referee: string) => { variables, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + return jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, body, process.env.FLU_HASURA_SECRET ? { @@ -92,8 +94,10 @@ const useInactiveReferralByAddress = (address: string) => { variables, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + return jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, body, process.env.FLU_HASURA_SECRET ? { diff --git a/web/app.fluidity.money/app/queries/useStakingData.ts b/web/app.fluidity.money/app/queries/useStakingData.ts index 9d43759f1..1d335af2f 100644 --- a/web/app.fluidity.money/app/queries/useStakingData.ts +++ b/web/app.fluidity.money/app/queries/useStakingData.ts @@ -24,7 +24,9 @@ export const useStakingDataByAddress = async ( address: `0x${"0".repeat(24)}${address.slice(2)}`, days_elapsed: daysElapsed, }; + const url = "https://fluidity.hasura.app/v1/graphql"; + const body = { variables, query: queryStakingDataByAddress, diff --git a/web/app.fluidity.money/app/queries/useTokens.ts b/web/app.fluidity.money/app/queries/useTokens.ts index 4dad1bd8f..7b16eb8a7 100644 --- a/web/app.fluidity.money/app/queries/useTokens.ts +++ b/web/app.fluidity.money/app/queries/useTokens.ts @@ -23,10 +23,12 @@ export type Asset = { }; export const useTokens = async () => { + const url = "https://fluidity.hasura.app/v1/graphql"; + const { data: { asset }, } = await jsonPost( - "https://fluidity.hasura.app/v1/graphql", + url, { query: tokenQuery }, process.env.FLU_HASURA_SECRET ? { diff --git a/web/app.fluidity.money/app/queries/useUserActionsAggregate.ts b/web/app.fluidity.money/app/queries/useUserActionsAggregate.ts index 546ddebb2..7f037e802 100644 --- a/web/app.fluidity.money/app/queries/useUserActionsAggregate.ts +++ b/web/app.fluidity.money/app/queries/useUserActionsAggregate.ts @@ -10,120 +10,149 @@ export type AggregatedTransaction = Omit< swap_in: boolean; type: "send" | "swap"; timestamp: string; + rewardTier: number; + lootboxCount: number; }; const queryByAddress: Queryable = { arbitrum: gql` - query AggregatedUserTransactionsByAddress($offset: Int = 0, $limit: Int = 12, $address: String!, $token: String) { - arbitrum: aggregated_user_transactions( - order_by: {time: desc}, - limit: $limit, - offset: $offset, - where: { - network: {_eq: "arbitrum"} - token_short_name: {_eq: $token}, - type: {_is_null: false}, - _or: [ - {sender_address: {_eq: $address}}, - {recipient_address: {_eq: $address}} - ] - } + query AggregatedUserTransactionsByAddress( + $offset: Int = 0 + $limit: Int = 12 + $address: String! + $token: String ) { - value: amount - application - network - receiver: recipient_address - rewardHash: reward_hash - sender: sender_address - swap_in - timestamp: time - currency: token_short_name - hash: transaction_hash - type - utility_amount - utility_name - winner: winning_address - reward: winning_amount + arbitrum: aggregated_user_transactions_lootbottles( + order_by: { time: desc } + limit: $limit + offset: $offset + where: { + network: { _eq: "arbitrum" } + token_short_name: { _eq: $token } + type: { _is_null: false } + _or: [ + { sender_address: { _eq: $address } } + { recipient_address: { _eq: $address } } + ] + } + ) { + value: amount + application + network + receiver: recipient_address + rewardHash: reward_hash + sender: sender_address + swap_in + timestamp: time + currency: token_short_name + hash: transaction_hash + type + utility_amount + utility_name + winner: winning_address + reward: winning_amount + rewardTier: reward_tier + lootboxCount: lootbox_count + } } - } `, solana: gql` - query AggregatedUserTransactionsByAddress($offset: Int = 0, $limit: Int = 12, $address: String!) { - solana: aggregated_user_transactions( - order_by: {time: desc}, - limit: $limit, - offset: $offset, - where: { - network: {_eq: "solana"} - type: {_is_null: false}, - _or: [ - {sender_address: {_eq: $address}}, - {recipient_address: {_eq: $address}} - ] - } + query AggregatedUserTransactionsByAddress( + $offset: Int = 0 + $limit: Int = 12 + $address: String! ) { - value: amount - application - network - receiver: recipient_address - rewardHash: reward_hash - sender: sender_address - swap_in - timestamp: time - currency: token_short_name - hash: transaction_hash - type - utility_amount - utility_name - winner: winning_address - reward: winning_amount + solana: aggregated_user_transactions_lootbottles( + order_by: { time: desc } + limit: $limit + offset: $offset + where: { + network: { _eq: "solana" } + type: { _is_null: false } + _or: [ + { sender_address: { _eq: $address } } + { recipient_address: { _eq: $address } } + ] + } + ) { + value: amount + application + network + receiver: recipient_address + rewardHash: reward_hash + sender: sender_address + swap_in + timestamp: time + currency: token_short_name + hash: transaction_hash + type + utility_amount + utility_name + winner: winning_address + reward: winning_amount + rewardTier: reward_tier + lootboxCount: lootbox_count + } } - } `, }; const queryAll: Queryable = { arbitrum: gql` - query aggregatedUserTransactionsAll ($offset: Int = 0, $limit: Int = 12) { - arbitrum: aggregated_user_transactions(order_by: {time: desc}, limit: $limit, offset: $offset, where: {network: {_eq: "arbitrum"}, type: {_is_null: false}}) { - value: amount - application - network - receiver: recipient_address - rewardHash: reward_hash - sender: sender_address - swap_in - timestamp: time - currency: token_short_name - hash: transaction_hash - type - utility_amount - utility_name - winner: winning_address - reward: winning_amount + query aggregatedUserTransactionsAll($offset: Int = 0, $limit: Int = 12) { + arbitrum: aggregated_user_transactions_lootbottles( + order_by: { time: desc } + limit: $limit + offset: $offset + where: { network: { _eq: "arbitrum" }, type: { _is_null: false } } + ) { + value: amount + application + network + receiver: recipient_address + rewardHash: reward_hash + sender: sender_address + swap_in + timestamp: time + currency: token_short_name + hash: transaction_hash + type + utility_amount + utility_name + winner: winning_address + reward: winning_amount + rewardTier: reward_tier + lootboxCount: lootbox_count + } } - } `, solana: gql` - query aggregatedUserTransactionsAll ($offset: Int = 0, $limit: Int = 12) { - solana: aggregated_user_transactions(order_by: {time: desc}, limit: $limit, offset: $offset, where: {network: {_eq: "solana"}, type: {_is_null: false}}) { - value: amount - application - network - receiver: recipient_address - rewardHash: reward_hash - sender: sender_address - swap_in - timestamp: time - currency: token_short_name - hash: transaction_hash - type - utility_amount - utility_name - winner: winning_address - reward: winning_amount + query aggregatedUserTransactionsAll($offset: Int = 0, $limit: Int = 12) { + solana: aggregated_user_transactions_lootbottles( + order_by: { time: desc } + limit: $limit + offset: $offset + where: { network: { _eq: "solana" }, type: { _is_null: false } } + ) { + value: amount + application + network + receiver: recipient_address + rewardHash: reward_hash + sender: sender_address + swap_in + timestamp: time + currency: token_short_name + hash: transaction_hash + type + utility_amount + utility_name + winner: winning_address + reward: winning_amount + rewardTier: reward_tier + lootboxCount: lootbox_count + } } - } `, }; diff --git a/web/app.fluidity.money/app/queries/useUserRewards.ts b/web/app.fluidity.money/app/queries/useUserRewards.ts index 06654c913..d494224b1 100644 --- a/web/app.fluidity.money/app/queries/useUserRewards.ts +++ b/web/app.fluidity.money/app/queries/useUserRewards.ts @@ -196,7 +196,9 @@ const useUserRewardsAll = async (network: string) => { const useUserRewardsByAddress = async (network: string, address: string) => { const variables = { network, address }; + const url = "https://fluidity.hasura.app/v1/graphql"; + const body = { variables, query: queryWinnersByAddress[network as Chain], diff --git a/web/app.fluidity.money/app/queries/useUserTransactions.ts b/web/app.fluidity.money/app/queries/useUserTransactions.ts index b7c8f0012..8db0ae4f9 100644 --- a/web/app.fluidity.money/app/queries/useUserTransactions.ts +++ b/web/app.fluidity.money/app/queries/useUserTransactions.ts @@ -21,7 +21,7 @@ const queryByAddress: Queryable = { $limit: Int = 12 $tokens: [String!] = [] ) { - transfers: user_actions( + transfers: user_actions_lootbottles( where: { network: { _eq: "arbitrum" } token_short_name: { _in: $tokens } @@ -45,6 +45,8 @@ const queryByAddress: Queryable = { type swap_in application + rewardTier: reward_tier + lootboxCount: lootbox_count } } `, @@ -57,7 +59,7 @@ const queryByAddress: Queryable = { $limit: Int = 12 $tokens: [String!] = [] ) { - transfers: user_actions( + transfers: user_actions_lootbottles( where: { network: { _eq: "solana" } token_short_name: { _in: $tokens } @@ -81,6 +83,8 @@ const queryByAddress: Queryable = { type swap_in application + rewardTier: reward_tier + lootboxCount: lootbox_count } } `, @@ -93,7 +97,7 @@ const queryByAddress: Queryable = { $limit: Int = 12 $tokens: [String!] = [] ) { - transfers: user_actions( + transfers: user_actions_lootbottles( where: { network: { _eq: "polygon_zk" } token_short_name: { _in: $tokens } @@ -117,6 +121,8 @@ const queryByAddress: Queryable = { type swap_in application + rewardTier: reward_tier + lootboxCount: lootbox_count } } `, @@ -129,7 +135,7 @@ const queryByTxHash: Queryable = { $filterHashes: [String!] = [] $limit: Int = 12 ) { - transfers: user_actions( + transfers: user_actions_lootbottles( where: { network: { _eq: "arbitrum" } _not: { transaction_hash: { _in: $filterHashes } } @@ -148,6 +154,8 @@ const queryByTxHash: Queryable = { type swap_in application + rewardTier: reward_tier + lootboxCount: lootbox_count } } `, @@ -157,7 +165,7 @@ const queryByTxHash: Queryable = { $filterHashes: [String!] = [] $limit: Int = 12 ) { - transfers: user_actions( + transfers: user_actions_lootbottles( where: { network: { _eq: "solana" } _not: { transaction_hash: { _in: $filterHashes } } @@ -176,6 +184,8 @@ const queryByTxHash: Queryable = { type swap_in application + rewardTier: reward_tier + lootboxCount: lootbox_count } } `, @@ -186,7 +196,7 @@ const queryByTxHash: Queryable = { $filterHashes: [String!] = [] $limit: Int = 12 ) { - transfers: user_actions( + transfers: user_actions_lootbottles( where: { network: { _eq: "polygon_zk" } _not: { transaction_hash: { _in: $filterHashes } } @@ -205,6 +215,8 @@ const queryByTxHash: Queryable = { type swap_in application + rewardTier: reward_tier + lootboxCount: lootbox_count } } `, @@ -218,7 +230,7 @@ const queryAll: Queryable = { $limit: Int = 12 $tokens: [String!] = [] ) { - transfers: user_actions( + transfers: user_actions_lootbottles( where: { network: { _eq: "arbitrum" } _not: { transaction_hash: { _in: $filterHashes } } @@ -238,6 +250,8 @@ const queryAll: Queryable = { type swap_in application + rewardTier: reward_tier + lootboxCount: lootbox_count } } `, @@ -249,7 +263,7 @@ const queryAll: Queryable = { $limit: Int = 12 $tokens: [String!] = [] ) { - transfers: user_actions( + transfers: user_actions_lootbottles( where: { network: { _eq: "solana" } _not: { transaction_hash: { _in: $filterHashes } } @@ -269,6 +283,8 @@ const queryAll: Queryable = { type swap_in application + rewardTier: reward_tier + lootboxCount: lootbox_count } } `, @@ -280,7 +296,7 @@ const queryAll: Queryable = { $limit: Int = 12 $tokens: [String!] = [] ) { - transfers: user_actions( + transfers: user_actions_lootbottles( where: { network: { _eq: "polygon_zk" } _not: { transaction_hash: { _in: $filterHashes } } @@ -300,6 +316,8 @@ const queryAll: Queryable = { type swap_in application + rewardTier: reward_tier + lootboxCount: lootbox_count } } `, @@ -349,6 +367,8 @@ export type UserTransaction = { amount: number; currency: { symbol: string }; application: string; + lootboxCount: number; + rewardTier: number; }; type HasuraUserTransaction = { @@ -362,6 +382,8 @@ type HasuraUserTransaction = { type: "swap" | "send"; swap_in: boolean; application: string; + rewardTier: number; + lootboxCount: number; }; export type HasuraUserTransactionRes = { @@ -542,6 +564,8 @@ const parseHasuraUserTransactions = ( timestamp: { unixtime: hasuraDateToUnix(transfer.time) }, }, application: transfer.application, + lootboxCount: transfer.lootboxCount, + rewardTier: transfer.rewardTier, }; }), }, diff --git a/web/app.fluidity.money/app/queries/useUserUnclaimedRewards.ts b/web/app.fluidity.money/app/queries/useUserUnclaimedRewards.ts index d7152d5ce..7ce75f5de 100644 --- a/web/app.fluidity.money/app/queries/useUserUnclaimedRewards.ts +++ b/web/app.fluidity.money/app/queries/useUserUnclaimedRewards.ts @@ -82,10 +82,10 @@ const useUserUnclaimedRewards = async (network: string, address: string) => { }, }; - const fluGqlEndpoint = "https://fluidity.hasura.app/v1/graphql"; + const url = "https://fluidity.hasura.app/v1/graphql"; return jsonPost( - fluGqlEndpoint, + url, body, process.env.FLU_HASURA_SECRET ? { diff --git a/web/app.fluidity.money/app/root.tsx b/web/app.fluidity.money/app/root.tsx index 819c7a8cd..226392f24 100644 --- a/web/app.fluidity.money/app/root.tsx +++ b/web/app.fluidity.money/app/root.tsx @@ -15,7 +15,7 @@ import { withSentry } from "@sentry/remix"; import globalStylesheetUrl from "./global-styles.css"; import surfingStylesheetUrl from "@fluidity-money/surfing/dist/style.css"; -import { ToolTipLinks } from "./components"; +import { JoeFarmlandsOrCamelotKingdomLinks, ToolTipLinks } from "./components"; import { ToolProvider } from "./components/ToolTip"; import { SplitContextProvider } from "contexts/SplitProvider"; import CacheProvider from "contexts/CacheProvider"; @@ -29,6 +29,7 @@ globalThis.Buffer = Buffer; export const links = () => { return [ ...ToolTipLinks(), + ...JoeFarmlandsOrCamelotKingdomLinks(), { rel: "icon", href: "/favicon.ico" }, { rel: "apple-touch-icon", sizes: "57x57", href: "/apple-icon-57x57.png" }, diff --git a/web/app.fluidity.money/app/routes/$network/dashboard.tsx b/web/app.fluidity.money/app/routes/$network/dashboard.tsx index 80dec9167..eb585bbc9 100644 --- a/web/app.fluidity.money/app/routes/$network/dashboard.tsx +++ b/web/app.fluidity.money/app/routes/$network/dashboard.tsx @@ -461,53 +461,49 @@ export default function Dashboard() { {/* Referral Modal */} - {false && ( - setReferralModalVisibility(false)} - cardPositionStyle={{ - position: "absolute", - top: "1em", - right: isTablet ? "20px" : "60px", - width: 500, + setReferralModalVisibility(false)} + cardPositionStyle={{ + position: "absolute", + top: "1em", + right: isTablet ? "20px" : "60px", + width: 500, + }} + color="holo" + style={{ padding: 0, width: "100%" }} + > + { + setReferralModalVisibility(false); + setWalletModalVisibility(true); }} - color="holo" - style={{ padding: 0, width: "100%" }} - > - { - setReferralModalVisibility(false); - setWalletModalVisibility(true); - }} - referrerClaimed={numActiveReferrerReferrals} - refereeClaimed={numActiveReferreeReferrals} - refereeUnclaimed={numInactiveReferreeReferrals} - progress={inactiveReferrals[0]?.progress || 0} - progressReq={10} - referralCode={referralCode} - loaded={referralCountLoaded} - closeModal={() => setReferralModalVisibility(false)} - /> - - )} + referrerClaimed={numActiveReferrerReferrals} + refereeClaimed={numActiveReferreeReferrals} + refereeUnclaimed={numInactiveReferreeReferrals} + progress={inactiveReferrals[0]?.progress || 0} + progressReq={10} + referralCode={referralCode} + loaded={referralCountLoaded} + closeModal={() => setReferralModalVisibility(false)} + /> + {/* Accept Referral Modal */} - {false && ( - setAcceptReferralModalVisibility(false)} - > - - - )} + setAcceptReferralModalVisibility(false)} + > + + {/* Fluidify Money button, in a portal with z-index above tooltip if another modal isn't open */} @@ -679,36 +675,32 @@ export default function Dashboard() { {/* Referrals Button */} - {false && ( - { - width < airdropMobileBreakpoint - ? navigate(`/${network}/dashboard/airdrop#referrals`) - : setReferralModalVisibility(true); - }} - icon={} - > - {isMobile ? "" : "Referral"} - - )} + { + width < airdropMobileBreakpoint + ? navigate(`/${network}/dashboard/airdrop#referrals`) + : setReferralModalVisibility(true); + }} + icon={} + > + {isMobile ? "" : "Referral"} + {/* Fluidify button */} - {otherModalOpen && showExperiment("Fluidify-Button-Placement") && ( - { - client?.track("user", "click_fluidify"); - navigate(`/${network}/fluidify`); - }} - > - Fluidify{isMobile ? "" : " Money"} - - )} + { + client?.track("user", "click_fluidify"); + navigate(`/${network}/fluidify`); + }} + > + Fluidify{isMobile ? "" : " Money"} + {/* Prize Money */} { export const loader: LoaderFunction = async ({ params }) => { const network = params.network ?? ""; - const epochDays = dayDifference(new Date(), EPOCH_START_DATE); - // Staking Tokens const allowedTokenSymbols = new Set(["fUSDC", "USDC", "wETH"]); const { tokens } = config.config[network]; @@ -98,16 +93,12 @@ export const loader: LoaderFunction = async ({ params }) => { return json({ tokens: allowedTokens, - epochDaysTotal: EPOCH_DAYS_TOTAL, - epochDays, network, } satisfies LoaderData); }; type LoaderData = { tokens: Array; - epochDaysTotal: number; - epochDays: number; network: string; }; @@ -122,9 +113,15 @@ const SAFE_DEFAULT_AIRDROP: AirdropLoaderData = { }, bottlesCount: 0, liquidityMultiplier: 0, - stakes: [], wethPrice: 0, usdcPrice: 0, + programBegin: new Date("2023-05-01T12:00:00+02:00"), + programEnd: new Date("2023-06-28 T12:00:00+02:00"), + epochDaysTotal: 30, + epochDaysElapsed: 30, + epochIdentifier: "epoch_1", + ethereumApplication: "none", + epochFound: false, loaded: false, }; @@ -163,12 +160,7 @@ const GLOBAL_AIRDROP_BOTTLE_TIERS = { }; const Airdrop = () => { - const { - epochDaysTotal, - epochDays, - tokens: defaultTokens, - network, - } = useLoaderData(); + const { tokens: defaultTokens, network } = useLoaderData(); if (network !== "arbitrum") { return ( @@ -177,7 +169,8 @@ const Airdrop = () => { Airdrop - Wrap, Transact and Earn using $fUSDC, provide liquidity for even more rewards! + Wrap, Transact and Earn using $fUSDC, provide liquidity for even more + rewards! ); @@ -207,42 +200,47 @@ const Airdrop = () => { address, balance, stakeTokens, - getStakingDeposits, testStakeTokens, getStakingRatios, redeemableTokens: getRedeemableTokens, redeemTokens, } = useContext(FluidityFacadeContext); + const { width } = useViewport(); + + const navigate = useNavigate(); + + const mobileBreakpoint = 768; + + const isMobile = width < mobileBreakpoint; + const { data: airdropData } = useCache( - address ? `/${network}/query/dashboard/airdrop?address=${address}` : "" + address + ? `/${network}/query/dashboard/airdrop?address=${address}&epoch=${EPOCH_CURRENT_IDENTIFIER}` + : "" ); const { data: airdropLeaderboardData } = useCache( `/${network}/query/dashboard/airdropLeaderboard?period=${ leaderboardFilterIndex === 0 ? "24" : "all" }&address=${address ?? ""}${ - leaderboardFilterIndex === 0 ? "&provider=kyber_classic" : "" - }` + leaderboardFilterIndex === 0 ? "&provider=${currentApplication}" : "" + }&epoch=${EPOCH_CURRENT_IDENTIFIER}` ); const { data: referralData } = useCache( - address ? `/${network}/query/referrals?address=${address}` : "" + address + ? `/${network}/query/referrals?address=${address}&epoch=${EPOCH_CURRENT_IDENTIFIER}` + : "" ); const { data: referralLootboxData } = useCache( - address ? `/${network}/query/referralBottles?address=${address}` : "" + address + ? `/${network}/query/referralBottles?address=${address}&epoch=${EPOCH_CURRENT_IDENTIFIER}` + : "" ); - const { width } = useViewport(); - - const navigate = useNavigate(); - - const mobileBreakpoint = 768; - - const isMobile = width < mobileBreakpoint; - const data = { airdrop: { ...SAFE_DEFAULT_AIRDROP, @@ -269,6 +267,8 @@ const Airdrop = () => { bottlesCount, wethPrice, usdcPrice, + epochDaysTotal, + epochDaysElapsed, }, referrals: { numActiveReferreeReferrals, @@ -292,7 +292,7 @@ const Airdrop = () => { const destModal = location.hash.replace("#", ""); const [currentModal, setCurrentModal] = useState( - isAirdropModal(destModal) ? destModal : "recap" + isAirdropModal(destModal) ? destModal : null ); useEffect(() => { @@ -300,19 +300,12 @@ const Airdrop = () => { setCurrentModal(isAirdropModal(destModal) ? destModal : null); }, [location.hash]); - const [stakes, setStakes] = useState< - Array<{ - fluidAmount: BN; - baseAmount: BN; - durationDays: number; - depositDate: Date; - }> - >([]); - - const fetchUserStakes = async (address: string) => { - const stakingDeposits = (await getStakingDeposits?.(address)) ?? []; - setStakes(stakingDeposits); - }; + const stakes: Array<{ + fluidAmount: BN; + baseAmount: BN; + durationDays: number; + depositDate: Date; + }> = []; const fetchUserTokenBalance = async () => { const userTokenBalance = await Promise.all( @@ -390,8 +383,6 @@ const Airdrop = () => { fetchUserTokenBalance(); - fetchUserStakes(address); - fetchUserRedeemableTokens(address); }, [address]); @@ -402,7 +393,6 @@ const Airdrop = () => { const res = await (await redeemTokens?.())?.confirmTx(); fetchUserTokenBalance(); - fetchUserStakes(address); fetchUserRedeemableTokens(address); return res; @@ -416,10 +406,11 @@ const Airdrop = () => { const [localShouldShowBottleNumbers, setLocalShouldShowBottleNumbers] = useState(undefined); - // const [localShouldShowTutorial, setLocalShouldShowTutorial] = useState< - // boolean | undefined - // >(undefined); - const localShouldShowTutorial = false; + + const [localShouldShowTutorial, setLocalShouldShowTutorial] = useState< + boolean | undefined + >(undefined); + const [localShouldShowRecapIntro, setLocalShouldShowRecapIntro] = useState< boolean | undefined >(undefined); @@ -435,9 +426,11 @@ const Airdrop = () => { setLocalCookieConsent(true); } - // const airdropHasVisited = window.localStorage.getItem("airdropHasVisited"); + const airdropHasVisited = window.localStorage.getItem("airdropHasVisited"); + const airdropBottleCount = window.localStorage.getItem("airdropBottleCount"); + const airdropShouldShowBottleNumbers = window.localStorage.getItem( "airdropShouldShowBottleNumbers" ); @@ -460,17 +453,8 @@ const Airdrop = () => { "airdropShouldShowRecapIntro" ); - if (airdropShouldShowRecapIntro) { - setLocalShouldShowRecapIntro(false); - } else { - setLocalShouldShowRecapIntro(true); - } - - // if (airdropHasVisited) { - // setLocalShouldShowTutorial(false); - // } else { - // setLocalShouldShowTutorial(true); - // } + setLocalShouldShowRecapIntro(!airdropShouldShowRecapIntro); + setLocalShouldShowTutorial(!airdropHasVisited); }, []); useEffect(() => { @@ -519,7 +503,7 @@ const Airdrop = () => { groupId="airdrop" isSelected={currentModal === "recap"} > - Airdrop Recap + Epoch 1 Recap { > Airdrop Dashboard - {/* setCurrentModal("tutorial")} groupId="airdrop" isSelected={isMobile && currentModal === "tutorial"} > Airdrop Tutorial - */} + { @@ -561,7 +545,7 @@ const Airdrop = () => { > Leaderboard - {/* setCurrentModal("referrals")} groupId="airdrop" @@ -569,15 +553,6 @@ const Airdrop = () => { > Referrals - setCurrentModal("stake")} - groupId="airdrop" - isSelected={isMobile && currentModal === "stake"} - disabled={true} - > - Stake - setCurrentModal("testnet-rewards")} @@ -586,7 +561,16 @@ const Airdrop = () => { disabled={true} > Testnet Rewards - */} + + + + Dune + + ); }; @@ -621,7 +605,7 @@ const Airdrop = () => { style={{ marginBottom: "0.5em" }} className={"no-margin"} > - Welcome to Fluidity's Airdrop Event! + Airdrop V2: Arbitrum's Space Expedition. Fluidify your assets, transact them, and boost your rewards by @@ -673,7 +657,7 @@ const Airdrop = () => { seeBottlesDetails={() => setCurrentModal("bottles-details")} seeLeaderboardMobile={() => setCurrentModal("leaderboard")} epochMax={epochDaysTotal} - epochDays={epochDays} + epochDays={epochDaysElapsed} activatedReferrals={numActiveReferrerReferrals} totalBottles={bottlesCount} network={network} @@ -759,7 +743,6 @@ const Airdrop = () => { wethPrice={wethPrice} usdcPrice={usdcPrice} stakeCallback={() => { - fetchUserStakes(address ?? ""); fetchUserTokenBalance(); }} /> @@ -861,7 +844,6 @@ const Airdrop = () => { usdcPrice={usdcPrice} isMobile={isMobile} stakeCallback={() => { - fetchUserStakes(address ?? ""); fetchUserTokenBalance(); }} /> @@ -919,6 +901,19 @@ const Airdrop = () => { ) : ( <>
+
+ +
{ className={"no-margin"} style={{ marginBottom: "0.5em" }} > - Welcome to Fluidity's Airdrop Event! + Airdrop V2: Arbitrum's Space Expedition. Fluidify your assets, transact them, and boost your rewards @@ -973,7 +968,7 @@ const Airdrop = () => { seeBottlesDetails={() => setCurrentModal("bottles-details")} seeLeaderboardMobile={() => setCurrentModal("leaderboard")} epochMax={epochDaysTotal} - epochDays={epochDays} + epochDays={epochDaysElapsed} activatedReferrals={numActiveReferrerReferrals} totalBottles={bottlesCount} network={network} @@ -1169,16 +1164,16 @@ const MultiplierTasks = () => { const [tasks, setTasks] = useState<"1x" | "6x">("6x"); const providerLinks: { provider: Provider; link: string }[] = [ - { provider: "Uniswap", link: "https://app.uniswap.org/#/swap" }, + { provider: "Jumper", link: "https://app.uniswap.org/#/swap" }, { - provider: "Sushiswap", - link: "https://www.sushi.com/swap?fromChainId=42161&fromCurrency=0x4CFA50B7Ce747e2D61724fcAc57f24B748FF2b2A&toChainId=42161&toCurrency=NATIVE&amount=", + provider: "Uniswap", + link: "https://app.uniswap.org/#/swap", }, - { provider: "Camelot", link: "https://app.camelot.exchange/" }, - { provider: "Saddle", link: "https://saddle.exchange/#/" }, - { provider: "Chronos", link: "https://app.chronos.exchange/" }, + { provider: "Trader Joe", link: "https://app.camelot.exchange/" }, + { provider: "Camelot", link: "https://saddle.exchange/#/" }, + { provider: "Sushiswap", link: "https://app.chronos.exchange/" }, { - provider: "Kyber", + provider: "Ramses", link: "https://kyberswap.com/swap/arbitrum/fusdc-to-usdc", }, ]; @@ -1190,7 +1185,7 @@ const MultiplierTasks = () => { Multiplier Tasks - Perform displayed tasks to earn the respective multipliers. + Transact fUSDC on listed platforms to earn more!
{ - const sumLiquidityMultiplier = stakes.reduce( - (sum, { fluidAmount, baseAmount, durationDays, depositDate }) => { - const fluidDecimals = 6; - const fluidUsd = getUsdFromTokenAmount( - fluidAmount, - fluidDecimals, - usdcPrice - ); - - const wethDecimals = 18; - const usdcDecimals = 6; - - // If converting base amount by weth decimals (18) is smaller than $0.01, - // then tentatively assume Token amount is USDC - // A false hit would be a USDC deposit >= $100,000 - const baseUsd = - getUsdFromTokenAmount(baseAmount, wethDecimals, wethPrice) < 0.01 - ? getUsdFromTokenAmount(baseAmount, usdcDecimals, usdcPrice) - : getUsdFromTokenAmount(baseAmount, wethDecimals, wethPrice); - - const stakedDays = dayDifference(new Date(), new Date(depositDate)); - - const multiplier = stakingLiquidityMultiplierEq(stakedDays, durationDays); - - return sum + (fluidUsd + baseUsd) * multiplier; - }, - 0 - ); - - // if there are no stakes, this renders an empty stake on the dashboard which is A PART OF THE DESIGN SPEC - if (stakes.length === 0) - stakes = [ - { - fluidAmount: new BN(0), - baseAmount: new BN(0), - durationDays: 0, - depositDate: new Date(), - }, - ]; - return (
)} -
- MY TOTAL LIQUIDITY MULTIPLIER} - > - - {toSignificantDecimals(sumLiquidityMultiplier, 1)}x - - -
} layout="after" @@ -1357,112 +1297,16 @@ const MyMultiplier = ({ handleClick={seeMyStakingStats} id="mx-see-my-staking-stats" > - MY STAKING STATS + MY EPOCH 1 STAKING STATS - {!isMobile && ( -
- {stakes - .map((stake) => { - const { fluidAmount, baseAmount, durationDays, depositDate } = - stake; - - const stakedDays = dayDifference( - new Date(), - new Date(depositDate) - ); - const multiplier = stakingLiquidityMultiplierEq( - stakedDays, - durationDays - ); - - const fluidDecimals = 6; - const fluidUsd = getUsdFromTokenAmount( - fluidAmount, - fluidDecimals, - usdcPrice - ); - - const wethDecimals = 18; - const usdcDecimals = 6; - - // If converting base amount by weth decimals (18) is smaller than $0.01, - // then tentatively assume Token amount is USDC - // A false hit would be a USDC deposit >= $100,000 - const baseUsd = - getUsdFromTokenAmount(baseAmount, wethDecimals, wethPrice) < - 0.01 - ? getUsdFromTokenAmount(baseAmount, usdcDecimals, usdcPrice) - : getUsdFromTokenAmount(baseAmount, wethDecimals, wethPrice); - - return { - stake, - stakedDays, - multiplier, - fluidUsd, - baseUsd, - }; - }) - .sort((a, b) => { - const stakeAVal = (a.fluidUsd + a.baseUsd) * a.multiplier; - const stakeBVal = (b.fluidUsd + b.baseUsd) * b.multiplier; - - // Sort Descending - return stakeBVal > stakeAVal - ? 1 - : stakeBVal === stakeAVal - ? 0 - : -1; - }) - .slice(0, 3) - .map(({ stake, multiplier, fluidUsd, baseUsd }) => { - const { durationDays } = stake; - - return ( - <> -
- - {numberToMonetaryString(fluidUsd + baseUsd)} FOR{" "} - {Math.floor(durationDays)} DAYS - - -
-
- - {toSignificantDecimals(multiplier, 1)}X - -
- - ); - })} +
+
+ + Provide $fUSDC Liquidity to earn $ARB and Multipliers! +
- )} - : undefined} - layout={"after"} - buttontype="text" - size="medium" - version="primary" - handleClick={seeStakeNow} - id="mx-stake-now-button" - disabled={true} - > - STAKE NOW - + +
); }; @@ -1472,7 +1316,7 @@ const airdropRankRow = ( isMobile = false ): IRow => { const { address } = useContext(FluidityFacadeContext); - const { user, rank, referralCount, liquidityMultiplier, bottles } = data; + const { user, rank, referralCount, fusdcEarned, arbEarned, bottles } = data; return { className: `airdrop-row ${isMobile ? "airdrop-mobile" : ""} ${ @@ -1538,7 +1382,24 @@ const airdropRankRow = ( ); - case "STAKING MULTIPLIER": + case "$fUSDC EARNED": + return ( + + + {fusdcEarned} + + + ); + case "$ARB EARNED": return ( - {toSignificantDecimals(liquidityMultiplier, 1)}x + {arbEarned} ); @@ -1609,6 +1470,8 @@ const Leaderboard = ({ liquidityMultiplier: 0, bottles: 0, highestRewardTier: 0, + fusdcEarned: 0, + arbEarned: 0, }; data.push(userEntry); @@ -1671,7 +1534,8 @@ const Leaderboard = ({ { name: "RANK" }, { name: "USER" }, { name: "BOTTLES" }, - { name: "STAKING MULTIPLIER" }, + { name: "$fUSDC EARNED" }, + { name: "$ARB EARNED" }, { name: "REFERRALS" }, ]} pagination={{ diff --git a/web/app.fluidity.money/app/routes/$network/dashboard/home.tsx b/web/app.fluidity.money/app/routes/$network/dashboard/home.tsx index f4add9655..0539d1cba 100644 --- a/web/app.fluidity.money/app/routes/$network/dashboard/home.tsx +++ b/web/app.fluidity.money/app/routes/$network/dashboard/home.tsx @@ -21,6 +21,7 @@ import { TabButton, toDecimalPlaces, ProviderIcon, + LootBottle, TokenIcon, } from "@fluidity-money/surfing"; import { useState, useContext, useEffect, useMemo } from "react"; @@ -262,6 +263,7 @@ export default function Home() { { name: "VALUE" }, { name: "FLUID REWARDS", show: width >= 500 }, { name: "$UTILITY REWARDS", show: width >= 500 }, + { name: "BOTTLES EARNED", show: width >= 500 }, { name: "ACCOUNT", show: width >= 375 }, { name: "TIME", show: width >= 850, alignRight: true }, ]; @@ -375,6 +377,8 @@ export default function Home() { logo, utilityTokens, application, + rewardTier, + lootboxCount, } = data; const appProviderName = getProviderDisplayName(application); @@ -454,12 +458,33 @@ export default function Home() { )} ); + case "BOTTLES EARNED": + return ( + + {lootboxCount ? ( + + + + ) : ( + - + )}{" "} + + ); case "ACCOUNT": return ( {sender === MintAddress diff --git a/web/app.fluidity.money/app/routes/$network/dashboard/rewards/index.tsx b/web/app.fluidity.money/app/routes/$network/dashboard/rewards/index.tsx index 64f00566d..a9d1ab7a5 100644 --- a/web/app.fluidity.money/app/routes/$network/dashboard/rewards/index.tsx +++ b/web/app.fluidity.money/app/routes/$network/dashboard/rewards/index.tsx @@ -38,6 +38,7 @@ import { WalletIcon, TabButton, toDecimalPlaces, + LootBottle, } from "@fluidity-money/surfing"; import { useContext, useEffect, useState, useMemo } from "react"; import { ToolTipContent, useToolTip, UtilityToken } from "~/components"; @@ -257,6 +258,7 @@ export default function Rewards() { { name: "VALUE" }, { name: "FLUID REWARDS", show: width >= 500 }, { name: "$UTILITY REWARDS", show: width >= 500 }, + { name: "BOTTLES EARNED", show: width >= 500 }, { name: "WINNER", show: width >= 850 }, { name: "REWARDED TIME", show: width >= 850, alignRight: true }, ]; @@ -432,6 +434,8 @@ export default function Rewards() { currency, utilityTokens, application, + lootboxCount, + rewardTier, } = data; const toolTip = useToolTip(); @@ -537,6 +541,25 @@ export default function Rewards() { )} ); + case "BOTTLES EARNED": + return ( + + {lootboxCount ? ( + + + + ) : ( + - + )}{" "} + + ); case "WINNER": return ( @@ -628,23 +651,23 @@ export default function Rewards() {
- { network == "arbitrum" ? - <> - + {network == "arbitrum" ? ( + <> + - - - - - - - - - - - - : - <> + + + + + + + + + + + + ) : ( + <> @@ -658,7 +681,7 @@ export default function Rewards() { - } + )}
Swap To Earn {!isMobile && Swap Fluid Assets to generate yield.} diff --git a/web/app.fluidity.money/app/routes/$network/query/dashboard/airdrop.tsx b/web/app.fluidity.money/app/routes/$network/query/dashboard/airdrop.tsx index c46ae93df..5a0685b79 100644 --- a/web/app.fluidity.money/app/routes/$network/query/dashboard/airdrop.tsx +++ b/web/app.fluidity.money/app/routes/$network/query/dashboard/airdrop.tsx @@ -4,7 +4,7 @@ import { JsonRpcProvider } from "@ethersproject/providers"; import { LoaderFunction, json } from "@remix-run/node"; import { captureException } from "@sentry/react"; import { useAirdropStatsByAddress } from "~/queries/useAirdropStats"; -import { useStakingDataByAddress } from "~/queries/useStakingData"; +import { useLootboxConfig } from "~/queries"; import { getWethUsdPrice } from "~/util/chainUtils/ethereum/transaction"; import EACAggregatorProxyAbi from "~/util/chainUtils/ethereum/EACAggregatorProxy.json"; import config from "~/webapp.config.server"; @@ -29,17 +29,19 @@ export type AirdropLoaderData = { bottleTiers: BottleTiers; bottlesCount: number; liquidityMultiplier: number; - stakes: Array; wethPrice: number; usdcPrice: number; - loaded: boolean; referralCode?: string; + programBegin: Date; + programEnd: Date; + epochDaysTotal: number; + epochDaysElapsed: number; + epochIdentifier: string; + ethereumApplication: string; + epochFound: boolean; + loaded: boolean; }; -const EPOCH_DAYS_TOTAL = 31; -// temp: april 19th, 2023 -const EPOCH_START_DATE = new Date(2023, 3, 20); - const MAINNET_ID = 0; const dayDifference = (date1: Date, date2: Date) => @@ -50,13 +52,57 @@ export const loader: LoaderFunction = async ({ params, request }) => { const url = new URL(request.url); const address_ = url.searchParams.get("address"); + const epochIdentifier = url.searchParams.get("epoch") ?? ""; const address = address_?.toLowerCase(); - if (!address || !network) throw new Error("Invalid Request"); + if (!address || !network || !epochIdentifier) + throw new Error("Invalid Request"); + + const { data, errors } = await useLootboxConfig({ + identifier: epochIdentifier, + shouldFind: false, + }); + + if (errors) { + captureException(errors, { + tags: { + section: "airdrop", + }, + }); + + return new Error("Server could not fulfill request"); + } + + if (!data) { + // return defaults + return null; + } + + const { lootboxConfig: configs } = data; + + // if we didn't find anything, then we need to return the defaults + + if (configs.length != 1) { + return null; + } - const daysElapsed = - dayDifference(new Date(), EPOCH_START_DATE) % EPOCH_DAYS_TOTAL; + const { + programBegin: programBegin_, + programEnd: programEnd_, + ethereumApplication, + found: epochFound, + } = configs[0]; + + const programBegin = new Date(programBegin_); + const programEnd = new Date(programEnd_); + + const epochDaysTotal = Math.round( + (programEnd.valueOf() - programBegin.valueOf()) / (1000 * 60 * 60 * 24) + ); + + const epochDaysElapsed = + dayDifference(new Date(), programBegin) % epochDaysTotal; const infuraRpc = config.drivers[network][MAINNET_ID].rpc.http; const provider = new JsonRpcProvider(infuraRpc); @@ -65,18 +111,17 @@ export const loader: LoaderFunction = async ({ params, request }) => { config.contract.eac_aggregator_proxy[network as Chain]; try { - const [ - { data: airdropStatsData, errors: airdropStatsErrors }, - { data: stakingData, errors: stakingErrors }, - wethPrice, - ] = await Promise.all([ - useAirdropStatsByAddress(address), - useStakingDataByAddress(address, daysElapsed), - getWethUsdPrice(provider, eacAggregatorProxyAddr, EACAggregatorProxyAbi), - ]); + const [{ data: airdropStatsData, errors: airdropStatsErrors }, wethPrice] = + await Promise.all([ + useAirdropStatsByAddress(address, epochIdentifier), + getWethUsdPrice( + provider, + eacAggregatorProxyAddr, + EACAggregatorProxyAbi + ), + ]); if (airdropStatsErrors || !airdropStatsData) throw airdropStatsErrors; - if (stakingErrors || !stakingData) throw stakingErrors; const { lootboxCounts: [bottleTiers_], @@ -86,15 +131,6 @@ export const loader: LoaderFunction = async ({ params, request }) => { }, } = airdropStatsData; - const stakes = stakingData.stakes.map( - ({ multiplier, durationSecs, amount, ...stake }) => ({ - ...stake, - amountUsd: amount / 1e6, - durationDays: durationSecs / 60 / 60 / 24, - multiplier: multiplier[0].result, - }) - ); - const bottleTiers = bottleTiers_ || { [Rarity.Common]: 0, [Rarity.Uncommon]: 0, @@ -111,10 +147,16 @@ export const loader: LoaderFunction = async ({ params, request }) => { (sum: number, quantity: number) => sum + quantity, 0 ), - stakes, wethPrice, usdcPrice: 1, loaded: true, + programBegin, + programEnd, + epochDaysTotal, + epochDaysElapsed, + epochIdentifier, + epochFound, + ethereumApplication, } satisfies AirdropLoaderData); } catch (err) { captureException(new Error(`Could not fetch airdrop data: ${err}`), { diff --git a/web/app.fluidity.money/app/routes/$network/query/dashboard/airdropLeaderboard.tsx b/web/app.fluidity.money/app/routes/$network/query/dashboard/airdropLeaderboard.tsx index f7836405e..044c8fff0 100644 --- a/web/app.fluidity.money/app/routes/$network/query/dashboard/airdropLeaderboard.tsx +++ b/web/app.fluidity.money/app/routes/$network/query/dashboard/airdropLeaderboard.tsx @@ -22,6 +22,11 @@ export const loader: LoaderFunction = async ({ params, request }) => { const { network } = params; const url = new URL(request.url); + + const epoch = url.searchParams.get("epoch"); + + if (!epoch) throw new Error("Invalid Request"); + const address = url.searchParams.get("address") ?? ""; const period = url.searchParams.get("period") ?? ""; const provider_ = url.searchParams.get("provider") ?? ""; @@ -38,15 +43,17 @@ export const loader: LoaderFunction = async ({ params, request }) => { switch (true) { case useAll: { return [ - useAirdropLeaderboardAllTime, - useAirdropLeaderboardByUserAllTime, + () => useAirdropLeaderboardAllTime(epoch), + (address: string) => + useAirdropLeaderboardByUserAllTime(epoch, address), ]; } case use24Hours && !!provider: { return [ - () => useAirdropLeaderboardByApplication24Hours(provider), + () => useAirdropLeaderboardByApplication24Hours(epoch, provider), (address: string) => useAirdropLeaderboardByUserByApplication24Hours( + epoch, address, provider ), @@ -55,8 +62,9 @@ export const loader: LoaderFunction = async ({ params, request }) => { case use24Hours && !provider: default: { return [ - useAirdropLeaderboard24Hours, - useAirdropLeaderboardByUser24Hours, + () => useAirdropLeaderboard24Hours(epoch), + (address: string) => + useAirdropLeaderboardByUser24Hours(epoch, address), ]; } } @@ -105,6 +113,8 @@ export const loader: LoaderFunction = async ({ params, request }) => { referralCount: 0, bottles: 0, highestRewardTier: 0, + fusdcEarned: 0, + arbEarned: 0, } satisfies AirdropLeaderboardEntry, ] ).concat(leaderboard); diff --git a/web/app.fluidity.money/app/routes/$network/query/dashboard/prizePool.tsx b/web/app.fluidity.money/app/routes/$network/query/dashboard/prizePool.tsx index 182786e8d..30fcbda26 100644 --- a/web/app.fluidity.money/app/routes/$network/query/dashboard/prizePool.tsx +++ b/web/app.fluidity.money/app/routes/$network/query/dashboard/prizePool.tsx @@ -25,6 +25,7 @@ export async function loader() { }, ].map(({ network, abi, getPrizePool }) => { const infuraRpc = config.drivers[network][mainnetId].rpc.http; + console.log("infura rpc", infuraRpc); const provider = new JsonRpcProvider(infuraRpc); const rewardPoolAddr = config.contract.prize_pool[network as Chain]; diff --git a/web/app.fluidity.money/app/routes/$network/query/referralBottles.tsx b/web/app.fluidity.money/app/routes/$network/query/referralBottles.tsx index 07a50c3b0..d6c89b4c3 100644 --- a/web/app.fluidity.money/app/routes/$network/query/referralBottles.tsx +++ b/web/app.fluidity.money/app/routes/$network/query/referralBottles.tsx @@ -15,15 +15,14 @@ export const loader: LoaderFunction = async ({ request }) => { const url = new URL(request.url); const address_ = url.searchParams.get("address") ?? ""; + const epoch = url.searchParams.get("epoch"); const address = address_.toLocaleLowerCase(); - if (!address) { - throw new Error("Invalid Request"); - } + if (!address || !epoch) throw new Error("Invalid Request"); const { data: referralBottleCountData, errors: referralBottleCountErr } = - await useReferralLootboxesByAddress(address); + await useReferralLootboxesByAddress(epoch, address); if (referralBottleCountErr || !referralBottleCountData) { throw new Error("Could not fetch Referral Bottles"); diff --git a/web/app.fluidity.money/app/routes/$network/query/userTransactions.tsx b/web/app.fluidity.money/app/routes/$network/query/userTransactions.tsx index 4085cecbb..2000c80a1 100644 --- a/web/app.fluidity.money/app/routes/$network/query/userTransactions.tsx +++ b/web/app.fluidity.money/app/routes/$network/query/userTransactions.tsx @@ -38,6 +38,8 @@ type UserTransaction = { value: number; currency: string; application: string; + rewardTier: number; + lootboxCount: number; }; export type TransactionsLoaderData = { @@ -117,6 +119,8 @@ export const loader: LoaderFunction = async ({ params, request }) => { utility_amount, type, swap_in, + rewardTier, + lootboxCount, }) => { const utilityName = utility_name?.match(ALPHA_NUMERIC)?.[0]; @@ -142,14 +146,17 @@ export const loader: LoaderFunction = async ({ params, request }) => { // convert to JS timestamp timestamp: new Date(timestamp + "Z").getTime(), value, - currency: 'f' + currency, + currency: "f" + currency, application, swapType, provider: application ?? "Fluidity", logo: tokenLogoMap[currency] || defaultLogo, - utilityTokens: utilityName && utility_amount > 0 - ? { [utilityName]: utility_amount } - : undefined, + utilityTokens: + utilityName && utility_amount > 0 + ? { [utilityName]: utility_amount } + : undefined, + rewardTier, + lootboxCount, }; } ); @@ -351,6 +358,8 @@ export const loader: LoaderFunction = async ({ params, request }) => { amount: value, currency: { symbol: currency }, application, + lootboxCount, + rewardTier, } = transaction; return { @@ -367,6 +376,8 @@ export const loader: LoaderFunction = async ({ params, request }) => { : value, currency, application, + lootboxCount, + rewardTier, }; } ); @@ -408,6 +419,8 @@ export const loader: LoaderFunction = async ({ params, request }) => { swapType, utilityTokens: winner?.utility, application: tx.application, + rewardTier: tx.rewardTier, + lootboxCount: tx.lootboxCount, }; }); diff --git a/web/app.fluidity.money/app/routes/$network/query/winningUserTransactions.tsx b/web/app.fluidity.money/app/routes/$network/query/winningUserTransactions.tsx index 71520113b..7380b3d3e 100644 --- a/web/app.fluidity.money/app/routes/$network/query/winningUserTransactions.tsx +++ b/web/app.fluidity.money/app/routes/$network/query/winningUserTransactions.tsx @@ -26,6 +26,8 @@ type UserTransaction = { value: number; currency: string; application: string; + lootboxCount: number; + rewardTier: number; }; export type TransactionsLoaderData = { @@ -206,6 +208,8 @@ export const loader: LoaderFunction = async ({ params, request }) => { amount: value, currency: { symbol: currency }, application, + lootboxCount, + rewardTier, } = transaction; return { @@ -222,6 +226,8 @@ export const loader: LoaderFunction = async ({ params, request }) => { : value, currency, application, + lootboxCount, + rewardTier, }; } ); @@ -286,6 +292,8 @@ export const loader: LoaderFunction = async ({ params, request }) => { swapType, utilityTokens: winner.utility, application: tx.application, + lootboxCount: tx.lootboxCount, + rewardTier: tx.rewardTier, }; }); diff --git a/web/app.fluidity.money/app/routes/arbitrum/index.tsx b/web/app.fluidity.money/app/routes/arbitrum/index.tsx new file mode 100644 index 000000000..36f70df78 --- /dev/null +++ b/web/app.fluidity.money/app/routes/arbitrum/index.tsx @@ -0,0 +1,5 @@ +import { LoaderFunction, redirect } from "@remix-run/node"; + +export const loader: LoaderFunction = async () => { + return redirect(`/arbitrum/dashboard/home`); +}; diff --git a/web/app.fluidity.money/app/routes/arbitrum/query/dashboard/home.tsx b/web/app.fluidity.money/app/routes/arbitrum/query/dashboard/home.tsx index f7e2b4375..6d7d10076 100644 --- a/web/app.fluidity.money/app/routes/arbitrum/query/dashboard/home.tsx +++ b/web/app.fluidity.money/app/routes/arbitrum/query/dashboard/home.tsx @@ -49,10 +49,12 @@ export const loader: LoaderFunction = async ({ request }) => { } if (rewardsErr || !rewardsData) { - throw new Error("Could not fetch rewards data"); + console.log("Could not fetch rewards data", rewardsErr); + throw new Error(`Could not fetch rewards data: ${rewardsErr}`); } if (graphErr || !graphData) { + console.log("Could not fetch graph data", graphErr); throw new Error("Could not fetch graph data"); } diff --git a/web/app.fluidity.money/app/routes/polygon_zk/index.tsx b/web/app.fluidity.money/app/routes/polygon_zk/index.tsx new file mode 100644 index 000000000..b83703f40 --- /dev/null +++ b/web/app.fluidity.money/app/routes/polygon_zk/index.tsx @@ -0,0 +1,5 @@ +import { LoaderFunction, redirect } from "@remix-run/node"; + +export const loader: LoaderFunction = async () => { + return redirect(`/polygon_zk/dashboard/home`); +}; diff --git a/web/app.fluidity.money/app/routes/solana/index.tsx b/web/app.fluidity.money/app/routes/solana/index.tsx new file mode 100644 index 000000000..8d02d7a2a --- /dev/null +++ b/web/app.fluidity.money/app/routes/solana/index.tsx @@ -0,0 +1,5 @@ +import { LoaderFunction, redirect } from "@remix-run/node"; + +export const loader: LoaderFunction = async () => { + return redirect(`/solana/dashboard/home`); +}; diff --git a/web/app.fluidity.money/app/styles/dashboard.css b/web/app.fluidity.money/app/styles/dashboard.css index f5c50dbf0..7d3166ba0 100644 --- a/web/app.fluidity.money/app/styles/dashboard.css +++ b/web/app.fluidity.money/app/styles/dashboard.css @@ -943,4 +943,9 @@ ul.sidebar-nav li div.active { stroke: currentColor; } +.fluidify-button-dashboard { + background-color: white; + color: black; +} + /*# sourceMappingURL=dashboard.css.map */ diff --git a/web/app.fluidity.money/app/styles/dashboard.scss b/web/app.fluidity.money/app/styles/dashboard.scss index f143136b5..423e58c21 100644 --- a/web/app.fluidity.money/app/styles/dashboard.scss +++ b/web/app.fluidity.money/app/styles/dashboard.scss @@ -1007,3 +1007,8 @@ ul.sidebar-nav { } } } + +.fluidify-button-dashboard { + background-color: white; + color: black; +} diff --git a/web/app.fluidity.money/app/styles/dashboard/airdrop.css b/web/app.fluidity.money/app/styles/dashboard/airdrop.css index a75578573..4e0b8e444 100644 --- a/web/app.fluidity.money/app/styles/dashboard/airdrop.css +++ b/web/app.fluidity.money/app/styles/dashboard/airdrop.css @@ -391,9 +391,6 @@ .airdrop-my-multiplier { position: relative; - display: grid; - grid-template-columns: 1fr 1fr; - gap: 2em; } .airdrop-my-multiplier svg, .airdrop-my-multiplier path { @@ -426,9 +423,6 @@ } .airdrop-my-multiplier #mx-see-my-staking-stats { width: 100%; - grid-area: 2/1; - box-sizing: border-box; - align-self: end; } .airdrop-my-multiplier #mx-my-stakes { display: grid; @@ -982,4 +976,21 @@ aspect-ratio: 1/1; } +#tutorial, +#staking-stats, +#bottles-details, +#testnet-rewards { + position: fixed; +} + +.airdrop-arb-multipliers-container { + display: flex; + padding-top: 20px; +} + +.airdrop-arb-multipliers-container > * { + margin-left: auto; + margin-right: auto; +} + /*# sourceMappingURL=airdrop.css.map */ diff --git a/web/app.fluidity.money/app/styles/dashboard/airdrop.scss b/web/app.fluidity.money/app/styles/dashboard/airdrop.scss index bb43ffe2e..f76b7cfbf 100644 --- a/web/app.fluidity.money/app/styles/dashboard/airdrop.scss +++ b/web/app.fluidity.money/app/styles/dashboard/airdrop.scss @@ -400,9 +400,6 @@ $holo: linear-gradient( .airdrop-my-multiplier { position: relative; - display: grid; - grid-template-columns: 1fr 1fr; - gap: 2em; svg, path { @@ -442,9 +439,6 @@ $holo: linear-gradient( #mx-see-my-staking-stats { width: 100%; - grid-area: 2 / 1; - box-sizing: border-box; - align-self: end; } #mx-my-stakes { @@ -1066,3 +1060,20 @@ $holo: linear-gradient( } } } + +#tutorial, +#staking-stats, +#bottles-details, +#testnet-rewards { + position: fixed; +} + +.airdrop-arb-multipliers-container { + display: flex; + padding-top: 20px; +} + +.airdrop-arb-multipliers-container > * { + margin-left: auto; + margin-right: auto; +} diff --git a/web/app.fluidity.money/app/types/Transaction.ts b/web/app.fluidity.money/app/types/Transaction.ts index 98583c11c..47597bc68 100644 --- a/web/app.fluidity.money/app/types/Transaction.ts +++ b/web/app.fluidity.money/app/types/Transaction.ts @@ -14,6 +14,8 @@ type Transaction = { swapType?: "in" | "out"; utilityTokens?: { [tokens: string]: number }; application: string; + rewardTier: number; + lootboxCount: number; }; export default Transaction; diff --git a/web/app.fluidity.money/app/util/api/graphql.ts b/web/app.fluidity.money/app/util/api/graphql.ts index 59a11e4ea..d112f9b87 100644 --- a/web/app.fluidity.money/app/util/api/graphql.ts +++ b/web/app.fluidity.money/app/util/api/graphql.ts @@ -11,6 +11,8 @@ type GqlEndpoint = { type GqlBackend = "hasura"; +const HasuraUrl = "https://fluidity.hasura.app/v1/graphql"; + export const networkGqlBackend = (network: string): GqlBackend | null => { switch (network) { case "solana": @@ -26,7 +28,7 @@ export const fetchGqlEndpoint = (network: string): GqlEndpoint | null => { switch (networkGqlBackend(network)) { case "hasura": return { - url: "https://fluidity.hasura.app/v1/graphql", + url: HasuraUrl, headers: { "x-hasura-admin-secret": process.env.FLU_HASURA_SECRET ?? "", }, @@ -37,7 +39,7 @@ export const fetchGqlEndpoint = (network: string): GqlEndpoint | null => { }; export const fetchInternalEndpoint = (): GqlEndpoint => ({ - url: "https://fluidity.hasura.app/v1/graphql", + url: HasuraUrl, headers: typeof process.env.FLU_HASURA_SECRET === "string" ? { diff --git a/web/app.fluidity.money/app/util/chainUtils/ethereum/transaction.ts b/web/app.fluidity.money/app/util/chainUtils/ethereum/transaction.ts index 545721d1d..428a89178 100644 --- a/web/app.fluidity.money/app/util/chainUtils/ethereum/transaction.ts +++ b/web/app.fluidity.money/app/util/chainUtils/ethereum/transaction.ts @@ -628,10 +628,10 @@ export const handleContractErrors = async ( if (metaMaskError?.value.code === -32603) { throw new Error( - `Failed to make swap. Please reset your Metamask account (settings -> advanced -> reset account)` + `RPC error. Please reset your Metamask account (settings -> advanced -> reset account)` ); } else if (metaMaskError?.value.code === -32000) { - throw new Error(`Failed to make swap. Gas limit too low.`); + throw new Error(`RPC error. Gas limit too low.`); } // otherwise, check for a 'non intrinsic' gas error (gas exhausted) @@ -640,12 +640,12 @@ export const handleContractErrors = async ( // found revert opcode, assume it's a gas error since we can't call this with an insufficient balance within the application if (receipt?.status === 0) { throw new Error( - `Failed to make swap. Gas limit of ${receipt?.gasUsed?.toNumber()} exhausted!` + `RPC error. Gas limit of ${receipt?.gasUsed?.toNumber()} exhausted!` ); } } catch (e) { // otherwise, use a generic error - throw new Error(`Failed to make swap. ${msg}`); + throw new Error(`RPC error. ${msg}`); } }; diff --git a/web/app.fluidity.money/app/util/provider.ts b/web/app.fluidity.money/app/util/provider.ts index 6f01ff400..cc92159f8 100644 --- a/web/app.fluidity.money/app/util/provider.ts +++ b/web/app.fluidity.money/app/util/provider.ts @@ -69,8 +69,12 @@ export const getProviderDisplayName = (name?: string): Provider => { return "Lifinity"; case "mercurial": return "Mercurial"; + case "ramses": + return "Ramses"; case "trader_joe": return "Trader Joe"; + case "jumper": + return "Jumper"; case "meteora": return "Meteora"; case "fluidity": diff --git a/web/app.fluidity.money/public/images/epoch2AirdropBanner.png b/web/app.fluidity.money/public/images/epoch2AirdropBanner.png new file mode 100644 index 000000000..eb7a4eb31 --- /dev/null +++ b/web/app.fluidity.money/public/images/epoch2AirdropBanner.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72209eaa5f613befc0b8fcc53d66b42693d48fa565404b028dcab5034a9fe1fc +size 170872 diff --git a/web/app.fluidity.money/public/images/hero/common.png b/web/app.fluidity.money/public/images/hero/common.png index afb7e2da1..9651b6b77 100644 --- a/web/app.fluidity.money/public/images/hero/common.png +++ b/web/app.fluidity.money/public/images/hero/common.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf873fc5dbc6f6493a8729e240d33026022a45def8c53c5d847873b1bb01bd5c -size 267816 +oid sha256:fbfdf8c12d6b02bad36eca229eadffb462b819d39b0a8385de3b864a85f83814 +size 167937 diff --git a/web/app.fluidity.money/public/images/hero/legendary.png b/web/app.fluidity.money/public/images/hero/legendary.png index 06fb62a20..a38f1a1f6 100644 --- a/web/app.fluidity.money/public/images/hero/legendary.png +++ b/web/app.fluidity.money/public/images/hero/legendary.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:252ea1e42636e41520a4e003056825f11e77ecf39f0766f93b6d2b6005eedfd3 -size 882592 +oid sha256:cfb9fdce40d47bd56d85f36f53f890113c97d48b52f35cf3a1772077c95132f4 +size 216190 diff --git a/web/app.fluidity.money/public/images/hero/rare.png b/web/app.fluidity.money/public/images/hero/rare.png index 282a12a3c..8b80c9ea0 100644 --- a/web/app.fluidity.money/public/images/hero/rare.png +++ b/web/app.fluidity.money/public/images/hero/rare.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:246725e96442ac0ac7e5aeafd33415937c1984eed2b83a6cb839dcf8e0dce6d6 -size 209084 +oid sha256:60677247e65f4ce979c7b4a709e51d310285f5bc6a971f753710e7cddc301439 +size 157634 diff --git a/web/app.fluidity.money/public/images/hero/ultra_rare.png b/web/app.fluidity.money/public/images/hero/ultra_rare.png index c90a7c963..96c422d32 100644 --- a/web/app.fluidity.money/public/images/hero/ultra_rare.png +++ b/web/app.fluidity.money/public/images/hero/ultra_rare.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:872c19d8fa54a4f59c7d11ba9dd3f84870f6955e817b8c8c91d1d866dbd49265 -size 241863 +oid sha256:576235a66a879f45a55773b95591a0a78c022b15aa82a41c7674471e6284bd76 +size 169854 diff --git a/web/app.fluidity.money/public/images/hero/uncommon.png b/web/app.fluidity.money/public/images/hero/uncommon.png index ad5d7c2fa..0010e8234 100644 --- a/web/app.fluidity.money/public/images/hero/uncommon.png +++ b/web/app.fluidity.money/public/images/hero/uncommon.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:238e51be54c715fbb00d8233d809cd6d4ff6e6a05fdd581d95c28a5c6344dabe -size 259633 +oid sha256:c2e149ca67b2f012270d78c53bbf2f052de5cc41c799c73a5698cebd16bb8649 +size 177534 diff --git a/web/app.fluidity.money/public/images/joe-farmlands.png b/web/app.fluidity.money/public/images/joe-farmlands.png new file mode 100644 index 000000000..4a1b50dbd --- /dev/null +++ b/web/app.fluidity.money/public/images/joe-farmlands.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2395663fa922328621ea843e25ca3ad697d61c2414bb6a18b3bb57c2c577140d +size 248359 diff --git a/web/app.fluidity.money/public/images/kingdom-of-camelot.png b/web/app.fluidity.money/public/images/kingdom-of-camelot.png new file mode 100644 index 000000000..5e33cf3f3 --- /dev/null +++ b/web/app.fluidity.money/public/images/kingdom-of-camelot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e623e4cabc261485889e786d0624b00466829912c28b43b8e2678b59df78ffb4 +size 131162 diff --git a/web/app.fluidity.money/public/images/providers/Jumper.svg b/web/app.fluidity.money/public/images/providers/Jumper.svg new file mode 100644 index 000000000..d7f19b72e --- /dev/null +++ b/web/app.fluidity.money/public/images/providers/Jumper.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60185b2e2158d5c994f8665081a708e721849bc038e4decdc36bbb64bf171446 +size 1180 diff --git a/web/app.fluidity.money/public/images/providers/Ramses.svg b/web/app.fluidity.money/public/images/providers/Ramses.svg new file mode 100644 index 000000000..492f06864 --- /dev/null +++ b/web/app.fluidity.money/public/images/providers/Ramses.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3519ca5687a2779d0ad4af62ffbff2bf2eeb5ec2f5ff6ca53593254a3ec9e00e +size 2668 diff --git a/web/fluidity.money/Dockerfile.frontend b/web/fluidity.money/Dockerfile.frontend index 1706b1d9d..161cb8c98 100644 --- a/web/fluidity.money/Dockerfile.frontend +++ b/web/fluidity.money/Dockerfile.frontend @@ -11,6 +11,8 @@ ARG GITHUB_TOKEN ARG NEXT_PUBLIC_FLU_ETH_RPC_HTTP +ARG NEXT_PUBLIC_FLU_HASURA_URL + ENV CI $CI ENV PATH /app/node_modules.bin:$PATH diff --git a/web/surfing/src/types/provider.ts b/web/surfing/src/types/provider.ts index f45511be6..ad579fe80 100644 --- a/web/surfing/src/types/provider.ts +++ b/web/surfing/src/types/provider.ts @@ -30,4 +30,6 @@ export type Provider = | "Trader Joe" | "Uniswap" | "XY Finance" + | "Ramses" + | "Jumper" | "Meteora"; diff --git a/web/surfing/src/util/liquidityProviders/providers.ts b/web/surfing/src/util/liquidityProviders/providers.ts index dd553245f..0715ca022 100644 --- a/web/surfing/src/util/liquidityProviders/providers.ts +++ b/web/surfing/src/util/liquidityProviders/providers.ts @@ -17,6 +17,7 @@ const providerImgNames: { [K in Provider]: string } = { Curve: "curve.png", Dodo: "DODO.png", Fluidity: "logoMetallic.png", + Jumper: "Jumper.svg", Jupiter: "Jupiter.svg", Kyber: "Kyber.svg", Lemniscap: "Lemniscap.png", @@ -29,6 +30,7 @@ const providerImgNames: { [K in Provider]: string } = { Oneinch: "1inch.svg", Orca: "Orca.svg", Polygon: "Polygon.svg", + Ramses: "Ramses.svg", Raydium: "raydium.png", Saber: "Saber.svg", Saddle: "Saddle.svg",