Start claiming rebate

This commit is contained in:
2025-06-03 02:54:48 +07:00
parent 2550f917e3
commit f41af96406
4 changed files with 312 additions and 0 deletions

View File

@@ -102,6 +102,7 @@ declare module 'fastify' {
closeGmxPosition: typeof closeGmxPosition;
getGmxTrade: typeof getGmxTrade;
getGmxPositions: typeof getGmxPositions;
getGmxRebateStats: typeof getGmxRebateStats;
}
}
@@ -860,6 +861,7 @@ export default fp(async (fastify) => {
fastify.decorateRequest('closeGmxPosition', closeGmxPosition)
fastify.decorateRequest('getGmxTrade', getGmxTrade)
fastify.decorateRequest('getGmxPositions', getGmxPositions)
fastify.decorateRequest('getGmxRebateStats', getGmxRebateStats)
// Pre-populate and refresh the markets cache on startup
fastify.addHook('onReady', async () => {
@@ -872,4 +874,159 @@ export default fp(async (fastify) => {
}
});
})
export const getGmxRebateStatsImpl = async (
sdk: GmxSdk
): Promise<{
totalRebateUsd: number;
discountUsd: number;
volume: number;
tier: number;
rebateFactor: number;
discountFactor: number;
} | null> => {
try {
// Get the referral storage contract address
const { getContract } = await import('../../generated/gmxsdk/configs/contracts.js');
const { decodeReferralCode } = await import('../../generated/gmxsdk/utils/referrals.js');
const { basisPointsToFloat } = await import('../../generated/gmxsdk/utils/numbers.js');
const { zeroHash } = await import('viem');
const referralStorageAddress = getContract(sdk.chainId, "ReferralStorage");
// Get user referral code
const userRefCodeRes = await sdk.executeMulticall({
referralStorage: {
contractAddress: referralStorageAddress,
abiId: "ReferralStorage",
calls: {
traderReferralCodes: {
methodName: "traderReferralCodes",
params: [sdk.account],
},
},
},
});
console.log("traderReferralCodes", userRefCodeRes.data.referralStorage.traderReferralCodes.returnValues)
const userReferralCode = userRefCodeRes.data.referralStorage.traderReferralCodes.returnValues[0];
const userReferralCodeString = decodeReferralCode(userReferralCode);
console.log("userReferralCodeAfterDecode", userReferralCodeString)
// If no referral code, return default values
if (!userReferralCode || userReferralCode === zeroHash) {
return {
totalRebateUsd: 0,
discountUsd: 0,
volume: 0,
tier: 0,
rebateFactor: 0,
discountFactor: 0
};
}
// Get code owner and affiliate tier
const [codeOwnerRes, affiliateTierRes] = await Promise.all([
sdk.executeMulticall({
referralStorage: {
contractAddress: referralStorageAddress,
abiId: "ReferralStorage",
calls: {
codeOwner: {
methodName: "codeOwners",
params: [userReferralCodeString],
},
},
},
}),
sdk.executeMulticall({
referralStorage: {
contractAddress: referralStorageAddress,
abiId: "ReferralStorage",
calls: {
referrerTiers: {
methodName: "referrerTiers",
params: [sdk.account],
},
},
},
})
]);
const codeOwner = codeOwnerRes.data.referralStorage.codeOwner.returnValues[0];
const tierId = affiliateTierRes.data.referralStorage.referrerTiers.returnValues[0];
// Get tier information
const tiersRes = await sdk.executeMulticall({
referralStorage: {
contractAddress: referralStorageAddress,
abiId: "ReferralStorage",
calls: {
tiers: {
methodName: "tiers",
params: [tierId],
},
},
},
});
const [totalRebate, discountShare] = tiersRes.data.referralStorage.tiers.returnValues ?? [0n, 0n];
// Get custom discount share if available
let customDiscountShare = 0n;
if (codeOwner) {
const customDiscountRes = await sdk.executeMulticall({
referralStorage: {
contractAddress: referralStorageAddress,
abiId: "ReferralStorage",
calls: {
referrerDiscountShares: {
methodName: "referrerDiscountShares",
params: [codeOwner],
},
},
},
});
customDiscountShare = customDiscountRes.data.referralStorage.referrerDiscountShares.returnValues[0] || 0n;
}
const finalDiscountShare = customDiscountShare > 0n ? customDiscountShare : discountShare;
// Convert bigint values to numbers for JSON serialization
const totalRebateFactor = basisPointsToFloat(totalRebate);
const discountFactor = basisPointsToFloat(finalDiscountShare);
return {
totalRebateUsd: Number(totalRebate) / 1e4, // Convert from basis points to decimal
discountUsd: Number(finalDiscountShare) / 1e4, // Convert from basis points to decimal
volume: 0, // Volume data would need to be fetched from different endpoint
tier: Number(tierId),
rebateFactor: Number(totalRebateFactor),
discountFactor: Number(discountFactor)
};
} catch (error) {
console.error('Error getting GMX rebate stats:', error);
throw error;
}
};
export async function getGmxRebateStats(
this: FastifyRequest,
reply: FastifyReply,
account: string
) {
try {
const sdk = await this.getClientForAddress(account);
const rebateStats = await getGmxRebateStatsImpl(sdk);
return {
success: true,
rebateStats
};
} catch (error) {
return handleError(this, reply, error, 'getGmxRebateStats');
}
}

View File

@@ -149,6 +149,37 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
account
)
})
// Define route to get rebate stats
fastify.get('/rebate-stats', {
schema: {
querystring: Type.Object({
account: Type.String()
}),
response: {
200: Type.Object({
success: Type.Boolean(),
rebateStats: Type.Optional(Type.Object({
totalRebateUsd: Type.Number(),
discountUsd: Type.Number(),
volume: Type.Number(),
tier: Type.Number(),
rebateFactor: Type.Number(),
discountFactor: Type.Number()
})),
error: Type.Optional(Type.String())
})
}
}
}, async (request, reply) => {
const { account } = request.query
// Call the getGmxRebateStats method from the GMX plugin
return request.getGmxRebateStats(
reply,
account
)
})
}
export default plugin

View File

@@ -0,0 +1,62 @@
import { test } from 'node:test'
import assert from 'node:assert'
import { getClientForAddress, getGmxRebateStatsImpl } from '../../src/plugins/custom/gmx'
test('GMX get rebate stats', async (t) => {
await t.test('should get rebate stats for account', async () => {
const testAccount = '0x932167388dD9aad41149b3cA23eBD489E2E2DD78'
const sdk = await getClientForAddress(testAccount)
const result = await getGmxRebateStatsImpl(sdk)
console.log('Rebate stats result:', result)
assert.ok(result, 'Rebate stats result should be defined')
// Check that the result has the expected structure
assert.ok(typeof result.totalRebateUsd === 'number', 'totalRebateUsd should be a number')
assert.ok(typeof result.discountUsd === 'number', 'discountUsd should be a number')
assert.ok(typeof result.volume === 'number', 'volume should be a number')
assert.ok(typeof result.tier === 'number', 'tier should be a number')
assert.ok(typeof result.rebateFactor === 'number', 'rebateFactor should be a number')
assert.ok(typeof result.discountFactor === 'number', 'discountFactor should be a number')
// All values should be non-negative
assert.ok(result.totalRebateUsd >= 0, 'totalRebateUsd should be non-negative')
assert.ok(result.discountUsd >= 0, 'discountUsd should be non-negative')
assert.ok(result.volume >= 0, 'volume should be non-negative')
assert.ok(result.tier >= 0, 'tier should be non-negative')
assert.ok(result.rebateFactor >= 0, 'rebateFactor should be non-negative')
assert.ok(result.discountFactor >= 0, 'discountFactor should be non-negative')
})
await t.test('should handle account with no referral info', async () => {
// Test with a different account that might not have referral data
const testAccount = '0x0000000000000000000000000000000000000000'
const sdk = await getClientForAddress(testAccount)
const result = await getGmxRebateStatsImpl(sdk)
console.log('Rebate stats result for empty account:', result)
assert.ok(result, 'Rebate stats result should be defined even for empty account')
// Should return default values for account with no referral info
assert.strictEqual(result.totalRebateUsd, 0, 'totalRebateUsd should be 0 for empty account')
assert.strictEqual(result.discountUsd, 0, 'discountUsd should be 0 for empty account')
assert.strictEqual(result.volume, 0, 'volume should be 0 for empty account')
assert.strictEqual(result.tier, 0, 'tier should be 0 for empty account')
assert.strictEqual(result.rebateFactor, 0, 'rebateFactor should be 0 for empty account')
assert.strictEqual(result.discountFactor, 0, 'discountFactor should be 0 for empty account')
})
await t.test('should handle errors gracefully', async () => {
// Test with an invalid account address to trigger error handling
try {
const sdk = await getClientForAddress('invalid-address')
await getGmxRebateStatsImpl(sdk)
assert.fail('Should have thrown an error for invalid address')
} catch (error) {
assert.ok(error instanceof Error, 'Should throw an Error instance')
console.log('Expected error for invalid address:', error.message)
}
})
})

View File

@@ -0,0 +1,62 @@
import {test} from 'node:test'
import assert from 'node:assert'
import {getClientForAddress, getGmxRebateStatsImpl} from '../../src/plugins/custom/gmx.ts'
test('GMX get rebate stats', async (t) => {
await t.test('should get rebate stats for account', async () => {
const testAccount = '0xbBA4eaA534cbD0EcAed5E2fD6036Aec2E7eE309f'
const sdk = await getClientForAddress(testAccount)
const result = await getGmxRebateStatsImpl(sdk)
console.log('Rebate stats result:', result)
assert.ok(result, 'Rebate stats result should be defined')
// Check that the result has the expected structure
assert.ok(typeof result.totalRebateUsd === 'number', 'totalRebateUsd should be a number')
assert.ok(typeof result.discountUsd === 'number', 'discountUsd should be a number')
assert.ok(typeof result.volume === 'number', 'volume should be a number')
assert.ok(typeof result.tier === 'number', 'tier should be a number')
assert.ok(typeof result.rebateFactor === 'number', 'rebateFactor should be a number')
assert.ok(typeof result.discountFactor === 'number', 'discountFactor should be a number')
// All values should be non-negative
assert.ok(result.totalRebateUsd >= 0, 'totalRebateUsd should be non-negative')
assert.ok(result.discountUsd >= 0, 'discountUsd should be non-negative')
assert.ok(result.volume >= 0, 'volume should be non-negative')
assert.ok(result.tier >= 0, 'tier should be non-negative')
assert.ok(result.rebateFactor >= 0, 'rebateFactor should be non-negative')
assert.ok(result.discountFactor >= 0, 'discountFactor should be non-negative')
})
await t.test('should handle account with no referral info', async () => {
// Test with a different account that might not have referral data
const testAccount = '0x0000000000000000000000000000000000000000'
const sdk = await getClientForAddress(testAccount)
const result = await getGmxRebateStatsImpl(sdk)
console.log('Rebate stats result for empty account:', result)
assert.ok(result, 'Rebate stats result should be defined even for empty account')
// Should return default values for account with no referral info
assert.strictEqual(result.totalRebateUsd, 0, 'totalRebateUsd should be 0 for empty account')
assert.strictEqual(result.discountUsd, 0, 'discountUsd should be 0 for empty account')
assert.strictEqual(result.volume, 0, 'volume should be 0 for empty account')
assert.strictEqual(result.tier, 0, 'tier should be 0 for empty account')
assert.strictEqual(result.rebateFactor, 0, 'rebateFactor should be 0 for empty account')
assert.strictEqual(result.discountFactor, 0, 'discountFactor should be 0 for empty account')
})
await t.test('should handle errors gracefully', async () => {
// Test with an invalid account address to trigger error handling
try {
const sdk = await getClientForAddress('invalid-address')
await getGmxRebateStatsImpl(sdk)
assert.fail('Should have thrown an error for invalid address')
} catch (error) {
assert.ok(error instanceof Error, 'Should throw an Error instance')
console.log('Expected error for invalid address:', error.message)
}
})
})