Start claiming rebate
This commit is contained in:
@@ -102,6 +102,7 @@ declare module 'fastify' {
|
|||||||
closeGmxPosition: typeof closeGmxPosition;
|
closeGmxPosition: typeof closeGmxPosition;
|
||||||
getGmxTrade: typeof getGmxTrade;
|
getGmxTrade: typeof getGmxTrade;
|
||||||
getGmxPositions: typeof getGmxPositions;
|
getGmxPositions: typeof getGmxPositions;
|
||||||
|
getGmxRebateStats: typeof getGmxRebateStats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,6 +861,7 @@ export default fp(async (fastify) => {
|
|||||||
fastify.decorateRequest('closeGmxPosition', closeGmxPosition)
|
fastify.decorateRequest('closeGmxPosition', closeGmxPosition)
|
||||||
fastify.decorateRequest('getGmxTrade', getGmxTrade)
|
fastify.decorateRequest('getGmxTrade', getGmxTrade)
|
||||||
fastify.decorateRequest('getGmxPositions', getGmxPositions)
|
fastify.decorateRequest('getGmxPositions', getGmxPositions)
|
||||||
|
fastify.decorateRequest('getGmxRebateStats', getGmxRebateStats)
|
||||||
|
|
||||||
// Pre-populate and refresh the markets cache on startup
|
// Pre-populate and refresh the markets cache on startup
|
||||||
fastify.addHook('onReady', async () => {
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,37 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
|||||||
account
|
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
|
export default plugin
|
||||||
62
src/Managing.Web3Proxy/test/plugins/get-gmx-rebate-stats
Normal file
62
src/Managing.Web3Proxy/test/plugins/get-gmx-rebate-stats
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user