shufflecom-calculations 2.3.4 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,140 @@
1
+ import BigNumber from 'bignumber.js';
2
+ import dayjs from 'dayjs';
3
+ import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
4
+ import utc from 'dayjs/plugin/utc';
5
+ import { VipLevelBase, getVipLevelBase } from '../types/vip-level-base.type';
6
+ import { VipLevel } from '../types/vip-level.type';
7
+ import { Currency } from '../utils/currency';
8
+ dayjs.extend(isSameOrAfter);
9
+ dayjs.extend(utc);
10
+
11
+ const BPS_DIVISOR = 10_000;
12
+
13
+ const VIP_RANK_TO_MULTIPLIER: Record<VipLevelBase, number> = {
14
+ [VipLevelBase.UNRANKED]: 1,
15
+ [VipLevelBase.WOOD]: 1,
16
+ [VipLevelBase.BRONZE]: 1,
17
+ [VipLevelBase.SILVER]: 1.01,
18
+ [VipLevelBase.GOLD]: 1.02,
19
+ [VipLevelBase.PLATINUM]: 1.03,
20
+ [VipLevelBase.JADE]: 1.04,
21
+ [VipLevelBase.SAPPHIRE]: 1.05,
22
+ [VipLevelBase.RUBY]: 1.06,
23
+ [VipLevelBase.DIAMOND]: 1.07,
24
+ [VipLevelBase.OPAL]: 1.08,
25
+ [VipLevelBase.DRAGON]: 1.09,
26
+ [VipLevelBase.MYTHIC]: 1.1,
27
+ [VipLevelBase.DARK]: 1.1,
28
+ [VipLevelBase.LEGEND]: 1.1,
29
+ };
30
+
31
+ type AirdropMultiplier = {
32
+ bps: number;
33
+ raw: number;
34
+ };
35
+
36
+ type TokenAllocation = {
37
+ allocatedAmount: BigNumber;
38
+ usdWageredSnapshot?: BigNumber;
39
+ eventId: number;
40
+ };
41
+
42
+ type AirdropAllocationInfoArgs = {
43
+ tokenAllocations: TokenAllocation[];
44
+ tokensClaimed: BigNumber;
45
+ usdTotalWagered: BigNumber;
46
+ hasKyc2: boolean;
47
+ };
48
+
49
+ export type AirdropAllocationInfo = {
50
+ totalAllocation: BigNumber;
51
+ tokensClaimed: BigNumber;
52
+ tokensClaimable: BigNumber;
53
+ };
54
+
55
+ export class AirdropCalculator {
56
+ static readonly points_normalizer = 0.04;
57
+ static readonly min_edge_threshold = 0.01;
58
+ static readonly default_multiplier = 1;
59
+ static readonly original_game_multiplier = 1.2;
60
+ static readonly shfl_multiplier = 1.25;
61
+ static readonly allocation_instant_unlock = 0.1;
62
+ static readonly default_usd_wager_to_vest_rate = 20;
63
+ static readonly kyc2_usd_wager_to_vest_rate = 10;
64
+
65
+ private static formatMultiplier(value: number): AirdropMultiplier {
66
+ return {
67
+ bps: BigNumber(value).multipliedBy(BPS_DIVISOR).toNumber(),
68
+ raw: value,
69
+ };
70
+ }
71
+
72
+ static getGameMultiplier(providerIsShuffle: boolean): AirdropMultiplier {
73
+ if (providerIsShuffle) {
74
+ return this.formatMultiplier(this.original_game_multiplier);
75
+ }
76
+ return this.formatMultiplier(this.default_multiplier);
77
+ }
78
+
79
+ static getCurrencyMultiplier(currency?: Currency): AirdropMultiplier {
80
+ if (currency === Currency.SHFL) return this.formatMultiplier(this.shfl_multiplier);
81
+ return this.formatMultiplier(this.default_multiplier);
82
+ }
83
+
84
+ static getRankMultiplier(vipLevel: VipLevel): AirdropMultiplier {
85
+ return this.formatMultiplier(VIP_RANK_TO_MULTIPLIER[getVipLevelBase(vipLevel)]);
86
+ }
87
+
88
+ /**
89
+ * aidrop event length is 1 minute short of a full week
90
+ * Start: Saturday, 12:00 AM UTC
91
+ * End: Friday, 11:59 PM UTC
92
+ * gets the event start date by subtracting distribution date by 6 days and 23 hours and 59 minutes
93
+ * startDates only apply to race event types as initial airdrop events are considered active until distribution date
94
+ */
95
+ static getAirdropEventStartDate(distributionDate: Date): Date {
96
+ return dayjs(distributionDate).subtract(1, 'week').add(1, 'minute').toDate();
97
+ }
98
+
99
+ /**
100
+ * @decription calculates the points without limit on decimal places
101
+ * @note: default round down to 2 decimal places
102
+ */
103
+ static calculateAirdropPoints({ vipLevel, airdropWageredEv }: { vipLevel: VipLevel; airdropWageredEv: BigNumber }): BigNumber {
104
+ const rankMultiplier = this.getRankMultiplier(vipLevel).raw;
105
+
106
+ return airdropWageredEv.multipliedBy(rankMultiplier).dividedBy(this.points_normalizer);
107
+ }
108
+
109
+ static getTokenAllocationInfo({ tokenAllocations, tokensClaimed, usdTotalWagered, hasKyc2 }: AirdropAllocationInfoArgs): AirdropAllocationInfo {
110
+ let totalAllocation = new BigNumber(0);
111
+ let totalUnlockedAmount = new BigNumber(0);
112
+ let wagerConsumedSoFar = new BigNumber(0);
113
+
114
+ const sortedAllocations = tokenAllocations.sort((a, b) => b.eventId - a.eventId);
115
+
116
+ for (const allocation of sortedAllocations) {
117
+ if (allocation?.usdWageredSnapshot && allocation.allocatedAmount.isGreaterThan(0)) {
118
+ const initialUnlockedAmount = allocation.allocatedAmount.multipliedBy(this.allocation_instant_unlock);
119
+
120
+ const wagerToVestRate = new BigNumber(hasKyc2 ? this.kyc2_usd_wager_to_vest_rate : this.default_usd_wager_to_vest_rate);
121
+ const wageredSinceSnapshot = usdTotalWagered.minus(allocation.usdWageredSnapshot);
122
+ const availableWager = BigNumber.max(0, wageredSinceSnapshot.minus(wagerConsumedSoFar));
123
+ const wagerToVestAmount = availableWager.dividedBy(wagerToVestRate);
124
+
125
+ const totalUnlocked = BigNumber.min(allocation.allocatedAmount, initialUnlockedAmount.plus(wagerToVestAmount));
126
+ const wagerConsumed = totalUnlocked.minus(initialUnlockedAmount).multipliedBy(wagerToVestRate);
127
+ wagerConsumedSoFar = wagerConsumedSoFar.plus(wagerConsumed);
128
+
129
+ totalUnlockedAmount = totalUnlockedAmount.plus(totalUnlocked);
130
+ }
131
+ totalAllocation = totalAllocation.plus(allocation.allocatedAmount);
132
+ }
133
+
134
+ return {
135
+ totalAllocation,
136
+ tokensClaimed,
137
+ tokensClaimable: BigNumber.max(0, totalUnlockedAmount.minus(tokensClaimed)),
138
+ };
139
+ }
140
+ }
package/src/index.ts CHANGED
@@ -17,6 +17,7 @@ export { Limbo } from './games/limbo';
17
17
  export { Keno, KenoRiskLevel } from './games/keno';
18
18
  export { Hilo, HiloGuess } from './games/hilo';
19
19
  export { CardGames, CardDetail, CardValue, Suits, suitsArr } from './games/cardGames';
20
+ export { AirdropCalculator, AirdropAllocationInfo } from './airdrop/airdrop-calculator';
20
21
  export {
21
22
  BlackjackAction,
22
23
  Blackjack,
@@ -0,0 +1,81 @@
1
+ import { VipLevel } from './vip-level.type';
2
+
3
+ export enum VipLevelBase {
4
+ UNRANKED = 'UNRANKED',
5
+ WOOD = 'WOOD',
6
+ BRONZE = 'BRONZE',
7
+ SILVER = 'SILVER',
8
+ GOLD = 'GOLD',
9
+ PLATINUM = 'PLATINUM',
10
+ JADE = 'JADE',
11
+ SAPPHIRE = 'SAPPHIRE',
12
+ RUBY = 'RUBY',
13
+ DIAMOND = 'DIAMOND',
14
+ OPAL = 'OPAL',
15
+ DRAGON = 'DRAGON',
16
+ MYTHIC = 'MYTHIC',
17
+ DARK = 'DARK',
18
+ LEGEND = 'LEGEND',
19
+ }
20
+
21
+ const vipLevelToBaseMapping: Record<VipLevel, VipLevelBase> = {
22
+ [VipLevel.UNRANKED]: VipLevelBase.UNRANKED,
23
+ [VipLevel.WOOD]: VipLevelBase.WOOD,
24
+ [VipLevel.BRONZE_1]: VipLevelBase.BRONZE,
25
+ [VipLevel.BRONZE_2]: VipLevelBase.BRONZE,
26
+ [VipLevel.BRONZE_3]: VipLevelBase.BRONZE,
27
+ [VipLevel.BRONZE_4]: VipLevelBase.BRONZE,
28
+ [VipLevel.BRONZE_5]: VipLevelBase.BRONZE,
29
+ [VipLevel.SILVER_1]: VipLevelBase.SILVER,
30
+ [VipLevel.SILVER_2]: VipLevelBase.SILVER,
31
+ [VipLevel.SILVER_3]: VipLevelBase.SILVER,
32
+ [VipLevel.SILVER_4]: VipLevelBase.SILVER,
33
+ [VipLevel.SILVER_5]: VipLevelBase.SILVER,
34
+ [VipLevel.GOLD_1]: VipLevelBase.GOLD,
35
+ [VipLevel.GOLD_2]: VipLevelBase.GOLD,
36
+ [VipLevel.GOLD_3]: VipLevelBase.GOLD,
37
+ [VipLevel.GOLD_4]: VipLevelBase.GOLD,
38
+ [VipLevel.GOLD_5]: VipLevelBase.GOLD,
39
+ [VipLevel.PLATINUM_1]: VipLevelBase.PLATINUM,
40
+ [VipLevel.PLATINUM_2]: VipLevelBase.PLATINUM,
41
+ [VipLevel.PLATINUM_3]: VipLevelBase.PLATINUM,
42
+ [VipLevel.PLATINUM_4]: VipLevelBase.PLATINUM,
43
+ [VipLevel.PLATINUM_5]: VipLevelBase.PLATINUM,
44
+ [VipLevel.JADE_1]: VipLevelBase.JADE,
45
+ [VipLevel.JADE_2]: VipLevelBase.JADE,
46
+ [VipLevel.JADE_3]: VipLevelBase.JADE,
47
+ [VipLevel.JADE_4]: VipLevelBase.JADE,
48
+ [VipLevel.JADE_5]: VipLevelBase.JADE,
49
+ [VipLevel.SAPPHIRE_1]: VipLevelBase.SAPPHIRE,
50
+ [VipLevel.SAPPHIRE_2]: VipLevelBase.SAPPHIRE,
51
+ [VipLevel.SAPPHIRE_3]: VipLevelBase.SAPPHIRE,
52
+ [VipLevel.SAPPHIRE_4]: VipLevelBase.SAPPHIRE,
53
+ [VipLevel.SAPPHIRE_5]: VipLevelBase.SAPPHIRE,
54
+ [VipLevel.RUBY_1]: VipLevelBase.RUBY,
55
+ [VipLevel.RUBY_2]: VipLevelBase.RUBY,
56
+ [VipLevel.RUBY_3]: VipLevelBase.RUBY,
57
+ [VipLevel.RUBY_4]: VipLevelBase.RUBY,
58
+ [VipLevel.RUBY_5]: VipLevelBase.RUBY,
59
+ [VipLevel.DIAMOND_1]: VipLevelBase.DIAMOND,
60
+ [VipLevel.DIAMOND_2]: VipLevelBase.DIAMOND,
61
+ [VipLevel.DIAMOND_3]: VipLevelBase.DIAMOND,
62
+ [VipLevel.DIAMOND_4]: VipLevelBase.DIAMOND,
63
+ [VipLevel.DIAMOND_5]: VipLevelBase.DIAMOND,
64
+ [VipLevel.OPAL_1]: VipLevelBase.OPAL,
65
+ [VipLevel.OPAL_2]: VipLevelBase.OPAL,
66
+ [VipLevel.OPAL_3]: VipLevelBase.OPAL,
67
+ [VipLevel.OPAL_4]: VipLevelBase.OPAL,
68
+ [VipLevel.OPAL_5]: VipLevelBase.OPAL,
69
+ [VipLevel.DRAGON_1]: VipLevelBase.DRAGON,
70
+ [VipLevel.DRAGON_2]: VipLevelBase.DRAGON,
71
+ [VipLevel.DRAGON_3]: VipLevelBase.DRAGON,
72
+ [VipLevel.DRAGON_4]: VipLevelBase.DRAGON,
73
+ [VipLevel.DRAGON_5]: VipLevelBase.DRAGON,
74
+ [VipLevel.MYTHIC]: VipLevelBase.MYTHIC,
75
+ [VipLevel.DARK]: VipLevelBase.DARK,
76
+ [VipLevel.LEGEND]: VipLevelBase.LEGEND,
77
+ } satisfies Record<VipLevel, VipLevelBase>;
78
+
79
+ export const getVipLevelBase = (vipLevel: VipLevel): VipLevelBase => {
80
+ return vipLevelToBaseMapping[vipLevel];
81
+ };
@@ -0,0 +1,57 @@
1
+ export enum VipLevel {
2
+ UNRANKED = 'UNRANKED',
3
+ WOOD = 'WOOD',
4
+ BRONZE_1 = 'BRONZE_1',
5
+ BRONZE_2 = 'BRONZE_2',
6
+ BRONZE_3 = 'BRONZE_3',
7
+ BRONZE_4 = 'BRONZE_4',
8
+ BRONZE_5 = 'BRONZE_5',
9
+ SILVER_1 = 'SILVER_1',
10
+ SILVER_2 = 'SILVER_2',
11
+ SILVER_3 = 'SILVER_3',
12
+ SILVER_4 = 'SILVER_4',
13
+ SILVER_5 = 'SILVER_5',
14
+ GOLD_1 = 'GOLD_1',
15
+ GOLD_2 = 'GOLD_2',
16
+ GOLD_3 = 'GOLD_3',
17
+ GOLD_4 = 'GOLD_4',
18
+ GOLD_5 = 'GOLD_5',
19
+ PLATINUM_1 = 'PLATINUM_1',
20
+ PLATINUM_2 = 'PLATINUM_2',
21
+ PLATINUM_3 = 'PLATINUM_3',
22
+ PLATINUM_4 = 'PLATINUM_4',
23
+ PLATINUM_5 = 'PLATINUM_5',
24
+ JADE_1 = 'JADE_1',
25
+ JADE_2 = 'JADE_2',
26
+ JADE_3 = 'JADE_3',
27
+ JADE_4 = 'JADE_4',
28
+ JADE_5 = 'JADE_5',
29
+ SAPPHIRE_1 = 'SAPPHIRE_1',
30
+ SAPPHIRE_2 = 'SAPPHIRE_2',
31
+ SAPPHIRE_3 = 'SAPPHIRE_3',
32
+ SAPPHIRE_4 = 'SAPPHIRE_4',
33
+ SAPPHIRE_5 = 'SAPPHIRE_5',
34
+ RUBY_1 = 'RUBY_1',
35
+ RUBY_2 = 'RUBY_2',
36
+ RUBY_3 = 'RUBY_3',
37
+ RUBY_4 = 'RUBY_4',
38
+ RUBY_5 = 'RUBY_5',
39
+ DIAMOND_1 = 'DIAMOND_1',
40
+ DIAMOND_2 = 'DIAMOND_2',
41
+ DIAMOND_3 = 'DIAMOND_3',
42
+ DIAMOND_4 = 'DIAMOND_4',
43
+ DIAMOND_5 = 'DIAMOND_5',
44
+ OPAL_1 = 'OPAL_1',
45
+ OPAL_2 = 'OPAL_2',
46
+ OPAL_3 = 'OPAL_3',
47
+ OPAL_4 = 'OPAL_4',
48
+ OPAL_5 = 'OPAL_5',
49
+ DRAGON_1 = 'DRAGON_1',
50
+ DRAGON_2 = 'DRAGON_2',
51
+ DRAGON_3 = 'DRAGON_3',
52
+ DRAGON_4 = 'DRAGON_4',
53
+ DRAGON_5 = 'DRAGON_5',
54
+ MYTHIC = 'MYTHIC',
55
+ DARK = 'DARK',
56
+ LEGEND = 'LEGEND',
57
+ }
@@ -123,6 +123,7 @@ export class SportsFixtureInfo {
123
123
  status: SportsFixtureStatus;
124
124
  bannerType: SportsFixtureBannerType;
125
125
  defaultMarketsInfo: DefaultMarketInfo;
126
+ pinned?: boolean;
126
127
  }
127
128
 
128
129
  export class SportsFixtureInfoWithCompetition extends SportsFixtureInfo {
@@ -1,26 +0,0 @@
1
- import BigNumber from 'bignumber.js';
2
- import dayjs from 'dayjs';
3
- export interface AirdropDetails {
4
- VESTING_SECONDS: number;
5
- USD_PER_SHFL: number;
6
- FRACTION_UNLOCKED_INSTANTLY: number;
7
- }
8
- export declare const AIRDROP: AirdropDetails;
9
- export declare const AIRDROP_2: AirdropDetails;
10
- export interface AirdropInfo {
11
- airdropAllocation: BigNumber;
12
- tokensVested: BigNumber;
13
- tokensClaimed: BigNumber;
14
- tokensClaimable: BigNumber;
15
- usdWageredSnapshot: BigNumber;
16
- noSnapshot: boolean;
17
- }
18
- export interface AirdropInfoArgs {
19
- airdropAllocation: BigNumber;
20
- usdWageredSnapshot?: BigNumber;
21
- usdTotalWagered: BigNumber;
22
- claimedAmount: BigNumber;
23
- startOfAirdropVesting: dayjs.Dayjs;
24
- airdropDetails: AirdropDetails;
25
- }
26
- export declare const getAirdropInfo: ({ airdropAllocation, usdWageredSnapshot, usdTotalWagered, claimedAmount, startOfAirdropVesting, airdropDetails, }: AirdropInfoArgs) => AirdropInfo;
@@ -1,51 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getAirdropInfo = exports.AIRDROP_2 = exports.AIRDROP = void 0;
7
- const bignumber_js_1 = __importDefault(require("bignumber.js"));
8
- const dayjs_1 = __importDefault(require("dayjs"));
9
- exports.AIRDROP = {
10
- VESTING_SECONDS: 90 * 24 * 60 * 60,
11
- USD_PER_SHFL: 50,
12
- FRACTION_UNLOCKED_INSTANTLY: 0.2,
13
- };
14
- exports.AIRDROP_2 = {
15
- VESTING_SECONDS: 180 * 24 * 60 * 60,
16
- USD_PER_SHFL: 50,
17
- FRACTION_UNLOCKED_INSTANTLY: 0.1,
18
- };
19
- const getAirdropInfo = ({ airdropAllocation, usdWageredSnapshot, usdTotalWagered, claimedAmount, startOfAirdropVesting, airdropDetails, }) => {
20
- if (!airdropAllocation || airdropAllocation.isZero()) {
21
- return {
22
- airdropAllocation: (0, bignumber_js_1.default)(0),
23
- tokensVested: (0, bignumber_js_1.default)(0),
24
- tokensClaimed: (0, bignumber_js_1.default)(0),
25
- tokensClaimable: (0, bignumber_js_1.default)(0),
26
- noSnapshot: true,
27
- usdWageredSnapshot: usdWageredSnapshot || (0, bignumber_js_1.default)(0),
28
- };
29
- }
30
- const usdWageredSinceStartOfVesting = usdWageredSnapshot ? bignumber_js_1.default.max(0, usdTotalWagered.minus(usdWageredSnapshot)) : (0, bignumber_js_1.default)(0);
31
- const initialTokensUnlocked = airdropAllocation.times(airdropDetails.FRACTION_UNLOCKED_INSTANTLY);
32
- const totalVestedTokens = airdropAllocation.minus(initialTokensUnlocked);
33
- const timeSinceVestingStarted = dayjs_1.default.utc().diff(startOfAirdropVesting, 'milliseconds');
34
- const tokensVestedByTime = (0, bignumber_js_1.default)(timeSinceVestingStarted)
35
- .div(airdropDetails.VESTING_SECONDS * 1000)
36
- .times(totalVestedTokens);
37
- const tokensVestedByWagering = usdWageredSinceStartOfVesting.dividedBy(airdropDetails.USD_PER_SHFL).integerValue(bignumber_js_1.default.ROUND_FLOOR);
38
- const totalTokensUnlocked = bignumber_js_1.default.min(airdropAllocation, initialTokensUnlocked.plus(tokensVestedByTime).plus(tokensVestedByWagering).integerValue(bignumber_js_1.default.ROUND_FLOOR));
39
- const tokensClaimable = totalTokensUnlocked.minus(claimedAmount);
40
- const airdropStarted = dayjs_1.default.utc().isSameOrAfter(startOfAirdropVesting);
41
- return {
42
- airdropAllocation,
43
- tokensVested: airdropStarted ? totalTokensUnlocked : (0, bignumber_js_1.default)(0),
44
- tokensClaimed: claimedAmount,
45
- tokensClaimable: airdropStarted ? tokensClaimable : (0, bignumber_js_1.default)(0),
46
- noSnapshot: !usdWageredSnapshot || usdWageredSnapshot.isLessThanOrEqualTo(0),
47
- usdWageredSnapshot: usdWageredSnapshot || (0, bignumber_js_1.default)(0),
48
- };
49
- };
50
- exports.getAirdropInfo = getAirdropInfo;
51
- //# sourceMappingURL=airdrop.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"airdrop.js","sourceRoot":"","sources":["../../src/utils/airdrop.ts"],"names":[],"mappings":"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"}
@@ -1,162 +0,0 @@
1
- import BigNumber from 'bignumber.js';
2
- import dayjs, { Dayjs, extend } from 'dayjs';
3
- import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
4
- extend(isSameOrAfter);
5
- import utc from 'dayjs/plugin/utc';
6
- import { AIRDROP, AirdropInfo, AirdropInfoArgs, getAirdropInfo } from './airdrop';
7
-
8
- extend(utc);
9
-
10
- export function mockdatetime(date: Date | number | Dayjs) {
11
- if (date instanceof dayjs) {
12
- date = (date as Dayjs).toDate();
13
- }
14
-
15
- jest
16
- .useFakeTimers({ doNotFake: ['nextTick', 'setImmediate', 'clearImmediate', 'setInterval', 'clearInterval', 'setTimeout', 'clearTimeout'] })
17
- .setSystemTime(date as Date | number);
18
- }
19
-
20
- const startOfAirdropVesting = dayjs.utc('2023-02-01T03:00:00Z');
21
- const checkValue = (input: Omit<AirdropInfoArgs, 'startOfAirdropVesting'>, expected: AirdropInfo) => {
22
- const result = getAirdropInfo({ ...input, startOfAirdropVesting });
23
- expect(result).toStrictEqual(expected);
24
- };
25
-
26
- describe('Airdrop', () => {
27
- it('should not be able to claim airdrop before start', () => {
28
- mockdatetime(startOfAirdropVesting.subtract(1, 'day'));
29
-
30
- checkValue(
31
- {
32
- airdropAllocation: BigNumber(1000),
33
- usdWageredSnapshot: undefined,
34
- usdTotalWagered: BigNumber(1000),
35
- claimedAmount: BigNumber(0),
36
- airdropDetails: AIRDROP,
37
- },
38
- {
39
- airdropAllocation: BigNumber(1000),
40
- tokensVested: BigNumber(0),
41
- tokensClaimed: BigNumber(0),
42
- tokensClaimable: BigNumber(0),
43
- noSnapshot: true,
44
- usdWageredSnapshot: BigNumber(0),
45
- },
46
- );
47
- });
48
-
49
- it('should be able to 20% of airdrop allocation at the start of airdrop vesting', () => {
50
- mockdatetime(startOfAirdropVesting);
51
-
52
- checkValue(
53
- {
54
- airdropAllocation: BigNumber(1000),
55
- usdWageredSnapshot: BigNumber(10),
56
- usdTotalWagered: BigNumber(10),
57
- claimedAmount: BigNumber(0),
58
- airdropDetails: AIRDROP,
59
- },
60
- {
61
- airdropAllocation: BigNumber(1000),
62
- tokensVested: BigNumber(200),
63
- tokensClaimed: BigNumber(0),
64
- tokensClaimable: BigNumber(200),
65
- noSnapshot: false,
66
- usdWageredSnapshot: BigNumber(10),
67
- },
68
- );
69
- });
70
-
71
- it('should be able to wager to vest', () => {
72
- mockdatetime(startOfAirdropVesting);
73
-
74
- checkValue(
75
- {
76
- airdropAllocation: BigNumber(1000),
77
- usdWageredSnapshot: BigNumber(1000),
78
- usdTotalWagered: BigNumber(2000),
79
- claimedAmount: BigNumber(20),
80
- airdropDetails: AIRDROP,
81
- },
82
- {
83
- airdropAllocation: BigNumber(1000),
84
- tokensVested: BigNumber(200).plus(1000 / 50),
85
- tokensClaimable: BigNumber(200)
86
- .plus(1000 / 50)
87
- .minus(20),
88
- tokensClaimed: BigNumber(20),
89
- noSnapshot: false,
90
- usdWageredSnapshot: BigNumber(1000),
91
- },
92
- );
93
- });
94
-
95
- it('should be able to wager to vest and wager by time', () => {
96
- mockdatetime(startOfAirdropVesting.add(45, 'day'));
97
-
98
- checkValue(
99
- {
100
- airdropAllocation: BigNumber(1000),
101
- usdWageredSnapshot: BigNumber(1000),
102
- usdTotalWagered: BigNumber(1900),
103
- claimedAmount: BigNumber(20),
104
- airdropDetails: AIRDROP,
105
- },
106
- {
107
- airdropAllocation: BigNumber(1000),
108
- tokensVested: BigNumber(600).plus(900 / 50),
109
- tokensClaimed: BigNumber(20),
110
- tokensClaimable: BigNumber(600)
111
- .plus(900 / 50)
112
- .minus(20),
113
- noSnapshot: false,
114
- usdWageredSnapshot: BigNumber(1000),
115
- },
116
- );
117
- });
118
-
119
- it('should be able to fully wager to vest', () => {
120
- mockdatetime(startOfAirdropVesting);
121
-
122
- checkValue(
123
- {
124
- airdropAllocation: BigNumber(1000),
125
- usdWageredSnapshot: BigNumber(5000),
126
- usdTotalWagered: BigNumber(45000),
127
- claimedAmount: BigNumber(100),
128
- airdropDetails: AIRDROP,
129
- },
130
- {
131
- airdropAllocation: BigNumber(1000),
132
- tokensVested: BigNumber(1000),
133
- tokensClaimed: BigNumber(100),
134
- tokensClaimable: BigNumber(900),
135
- noSnapshot: false,
136
- usdWageredSnapshot: BigNumber(5000),
137
- },
138
- );
139
- });
140
-
141
- it('should be able to vest fully with time only', () => {
142
- mockdatetime(startOfAirdropVesting.add(90, 'day'));
143
-
144
- checkValue(
145
- {
146
- airdropAllocation: BigNumber(1000),
147
- usdWageredSnapshot: BigNumber(1000),
148
- usdTotalWagered: BigNumber(1000),
149
- claimedAmount: BigNumber(0),
150
- airdropDetails: AIRDROP,
151
- },
152
- {
153
- airdropAllocation: BigNumber(1000),
154
- tokensVested: BigNumber(1000),
155
- tokensClaimed: BigNumber(0),
156
- tokensClaimable: BigNumber(1000),
157
- noSnapshot: false,
158
- usdWageredSnapshot: BigNumber(1000),
159
- },
160
- );
161
- });
162
- });
@@ -1,92 +0,0 @@
1
- import BigNumber from 'bignumber.js';
2
- import dayjs from 'dayjs';
3
-
4
- export interface AirdropDetails {
5
- VESTING_SECONDS: number;
6
- USD_PER_SHFL: number;
7
- FRACTION_UNLOCKED_INSTANTLY: number;
8
- }
9
-
10
- export const AIRDROP: AirdropDetails = {
11
- VESTING_SECONDS: 90 * 24 * 60 * 60, // 90 days
12
- USD_PER_SHFL: 50,
13
- FRACTION_UNLOCKED_INSTANTLY: 0.2,
14
- };
15
-
16
- export const AIRDROP_2: AirdropDetails = {
17
- VESTING_SECONDS: 180 * 24 * 60 * 60, // 180 days
18
- USD_PER_SHFL: 50,
19
- FRACTION_UNLOCKED_INSTANTLY: 0.1,
20
- };
21
-
22
- export interface AirdropInfo {
23
- airdropAllocation: BigNumber;
24
- tokensVested: BigNumber;
25
- tokensClaimed: BigNumber;
26
- tokensClaimable: BigNumber;
27
- usdWageredSnapshot: BigNumber;
28
- noSnapshot: boolean;
29
- }
30
-
31
- export interface AirdropInfoArgs {
32
- airdropAllocation: BigNumber;
33
- usdWageredSnapshot?: BigNumber;
34
- usdTotalWagered: BigNumber;
35
- claimedAmount: BigNumber;
36
- startOfAirdropVesting: dayjs.Dayjs;
37
- airdropDetails: AirdropDetails;
38
- }
39
-
40
- export const getAirdropInfo = ({
41
- airdropAllocation,
42
- usdWageredSnapshot,
43
- usdTotalWagered,
44
- claimedAmount,
45
- startOfAirdropVesting,
46
- airdropDetails,
47
- }: AirdropInfoArgs): AirdropInfo => {
48
- // if a snapshot has not been taken, the user can view their airdrop allocation but cannot claim it
49
- if (!airdropAllocation || airdropAllocation.isZero()) {
50
- return {
51
- airdropAllocation: BigNumber(0),
52
- tokensVested: BigNumber(0),
53
- tokensClaimed: BigNumber(0),
54
- tokensClaimable: BigNumber(0),
55
- noSnapshot: true,
56
- usdWageredSnapshot: usdWageredSnapshot || BigNumber(0),
57
- };
58
- }
59
-
60
- const usdWageredSinceStartOfVesting = usdWageredSnapshot ? BigNumber.max(0, usdTotalWagered.minus(usdWageredSnapshot)) : BigNumber(0);
61
-
62
- // 20% of tokens are unlocked immediately
63
- const initialTokensUnlocked = airdropAllocation.times(airdropDetails.FRACTION_UNLOCKED_INSTANTLY);
64
- const totalVestedTokens = airdropAllocation.minus(initialTokensUnlocked);
65
-
66
- // tokens unlocked so far
67
- const timeSinceVestingStarted = dayjs.utc().diff(startOfAirdropVesting, 'milliseconds');
68
- const tokensVestedByTime = BigNumber(timeSinceVestingStarted)
69
- .div(airdropDetails.VESTING_SECONDS * 1000)
70
- .times(totalVestedTokens);
71
-
72
- const tokensVestedByWagering = usdWageredSinceStartOfVesting.dividedBy(airdropDetails.USD_PER_SHFL).integerValue(BigNumber.ROUND_FLOOR);
73
-
74
- // min is used to make sure that the user cannot claim more than their allocated airdrop
75
- const totalTokensUnlocked = BigNumber.min(
76
- airdropAllocation,
77
- initialTokensUnlocked.plus(tokensVestedByTime).plus(tokensVestedByWagering).integerValue(BigNumber.ROUND_FLOOR),
78
- );
79
-
80
- const tokensClaimable = totalTokensUnlocked.minus(claimedAmount);
81
-
82
- const airdropStarted = dayjs.utc().isSameOrAfter(startOfAirdropVesting);
83
-
84
- return {
85
- airdropAllocation,
86
- tokensVested: airdropStarted ? totalTokensUnlocked : BigNumber(0),
87
- tokensClaimed: claimedAmount,
88
- tokensClaimable: airdropStarted ? tokensClaimable : BigNumber(0),
89
- noSnapshot: !usdWageredSnapshot || usdWageredSnapshot.isLessThanOrEqualTo(0),
90
- usdWageredSnapshot: usdWageredSnapshot || BigNumber(0),
91
- };
92
- };