-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add getSettings endpoint, MeshJS wallet integration #145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JaeBrian
wants to merge
3
commits into
main
Choose a base branch
from
issue-131/getSettings-refactor
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import type { Env } from '../../types/env'; | ||
| import { errorResponse } from '../../services/vmClient'; | ||
|
|
||
| // TODO: Integrate with VM backend for claim status polling | ||
| export const onRequestGet: PagesFunction<Env> = async (context) => { | ||
| const hash = new URL(context.request.url).searchParams.get('hash'); | ||
|
|
||
| if (!hash) { | ||
| return errorResponse('hash query parameter is required', 400); | ||
| } | ||
|
|
||
| return errorResponse('Claim status not yet integrated with VM backend', 501); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import type { Env } from '../../types/env'; | ||
| import { errorResponse, optionsResponse } from '../../services/vmClient'; | ||
|
|
||
| // TODO: Integrate with VM backend for claim submission | ||
| export const onRequestPost: PagesFunction<Env> = async (context) => { | ||
| try { | ||
| const body = await context.request.json() as { stakeAddress: string; assets: string[]; airdropHash: string }; | ||
|
|
||
| if (!body.stakeAddress || !body.assets?.length || !body.airdropHash) { | ||
| return errorResponse('stakeAddress, assets, and airdropHash are required', 400); | ||
| } | ||
|
|
||
| return errorResponse('Claim submission not yet integrated with VM backend', 501); | ||
| } catch (error) { | ||
| console.error('Claim submit error:', error); | ||
| return errorResponse('Submit failed'); | ||
| } | ||
| }; | ||
|
|
||
| export const onRequestOptions: PagesFunction = async () => optionsResponse(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import type { Env } from '../../types/env'; | ||
| import { errorResponse, optionsResponse } from '../../services/vmClient'; | ||
|
|
||
| // TODO: Integrate with VM backend for transaction submission | ||
| export const onRequestPost: PagesFunction<Env> = async (context) => { | ||
| try { | ||
| const body = await context.request.json() as { signedTx: string; airdropHash: string }; | ||
|
|
||
| if (!body.signedTx || !body.airdropHash) { | ||
| return errorResponse('signedTx and airdropHash are required', 400); | ||
| } | ||
|
|
||
| return errorResponse('Transaction submission not yet integrated with VM backend', 501); | ||
| } catch (error) { | ||
| console.error('Submit transaction error:', error); | ||
| return errorResponse('Transaction submission failed'); | ||
| } | ||
| }; | ||
|
|
||
| export const onRequestOptions: PagesFunction = async () => optionsResponse(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import type { Env } from '../../types/env'; | ||
| import { errorResponse, optionsResponse } from '../../services/vmClient'; | ||
|
|
||
| // TODO: Integrate with VM backend for actual claim validation | ||
| export const onRequestPost: PagesFunction<Env> = async (context) => { | ||
| try { | ||
| const body = await context.request.json() as { stakeAddress: string; assets: string[] }; | ||
|
|
||
| if (!body.stakeAddress || !body.assets?.length) { | ||
| return errorResponse('stakeAddress and assets are required', 400); | ||
| } | ||
|
|
||
| return errorResponse('Claim validation not yet integrated with VM backend', 501); | ||
| } catch (error) { | ||
| console.error('Claim validate error:', error); | ||
| return errorResponse('Validation failed'); | ||
| } | ||
| }; | ||
|
|
||
| export const onRequestOptions: PagesFunction = async () => optionsResponse(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,183 +1,100 @@ | ||
| import { | ||
| isNativeToken, | ||
| getTokenValue, | ||
| type ClaimableToken, | ||
| type GetRewardsDto, | ||
| type TokenInfo, | ||
| } from '../../src/shared/rewards'; | ||
| import type { Env } from '../types/env'; | ||
| import { initVmSdk, jsonResponse, errorResponse } from '../services/vmClient'; | ||
|
|
||
| function mergeAmounts(...sources: (Record<string, number> | undefined)[]): Record<string, number> { | ||
| const merged: Record<string, number> = {}; | ||
| for (const source of sources) { | ||
| if (!source) continue; | ||
| for (const [id, amount] of Object.entries(source)) { | ||
| merged[id] = (merged[id] ?? 0) + amount; | ||
| } | ||
| } | ||
| return merged; | ||
| } | ||
|
|
||
| interface Env { | ||
| VITE_VM_API_KEY: string; | ||
| function toClaimableTokens( | ||
| amounts: Record<string, number>, | ||
| tokens: Record<string, TokenInfo>, | ||
| premium: boolean, | ||
| ): ClaimableToken[] { | ||
| return Object.entries(amounts) | ||
| .filter(([assetId]) => tokens[assetId]) | ||
| .map(([assetId, rawAmount]) => { | ||
| const { decimals: tokenDecimals = 0, logo = '', ticker = '' } = tokens[assetId]; | ||
| const decimals = Number(tokenDecimals); | ||
| return { | ||
| assetId, | ||
| ticker, | ||
| logo, | ||
| decimals, | ||
| amount: rawAmount / Math.pow(10, decimals), | ||
| premium, | ||
| native: premium ? isNativeToken(assetId) : false, | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| async function getRewards(stakeAddress: string, env: Env): Promise<ClaimableToken[]> { | ||
| const { getRewards: getRewardsFromVM, getTokens: getTokensFromVM, setApiToken } = await import('vm-sdk'); | ||
| setApiToken(env.VITE_VM_API_KEY); | ||
| const { getRewards: getRewardsFromVM, getTokens: getTokensFromVM } = await initVmSdk(env); | ||
|
|
||
| const [getRewardsResponse, tokensRaw] = await Promise.all([ | ||
| const [rewardsResponse, tokensRaw] = await Promise.all([ | ||
| getRewardsFromVM(stakeAddress) as Promise<GetRewardsDto | null>, | ||
| getTokensFromVM(), | ||
| ]); | ||
|
|
||
| let tokens = tokensRaw as unknown as Record<string, TokenInfo> | null; | ||
| if (!rewardsResponse || !tokens) { | ||
| console.warn('getRewards: SDK returned null for', !rewardsResponse ? 'rewards' : 'tokens', { stakeAddress }); | ||
| return []; | ||
| } | ||
|
|
||
| const claimableTokens: ClaimableToken[] = []; | ||
|
|
||
| if (getRewardsResponse == null) return claimableTokens; | ||
| if (tokens == null) return claimableTokens; | ||
|
|
||
| const consolidatedAvailableReward: { [key: string]: number } = {}; | ||
| const consolidatedAvailableRewardPremium: { [key: string]: number } = {}; | ||
|
|
||
| // Accumulate regular rewards from consolidated_promises and consolidated_rewards | ||
| const addToConsolidated = (target: { [key: string]: number }, source: Record<string, number> | undefined) => { | ||
| if (!source) return; | ||
| Object.entries(source).forEach(([assetId, amount]) => { | ||
| const numAmount = Number(amount); | ||
| if (!isNaN(numAmount)) { | ||
| target[assetId] = (target[assetId] || 0) + numAmount; | ||
| const regular = mergeAmounts(rewardsResponse.consolidated_promises, rewardsResponse.consolidated_rewards); | ||
| const premium = mergeAmounts( | ||
| rewardsResponse.project_locked_rewards?.consolidated_promises, | ||
| rewardsResponse.project_locked_rewards?.consolidated_rewards, | ||
| ); | ||
|
|
||
| const allAssetIds = [...Object.keys(regular), ...Object.keys(premium)]; | ||
| for (const assetId of allAssetIds) { | ||
| if (!tokens[assetId]) { | ||
| tokens = (await getTokensFromVM()) as unknown as Record<string, TokenInfo> | null; | ||
| if (!tokens) { | ||
| console.warn('getRewards: token re-fetch returned null', { stakeAddress }); | ||
| return []; | ||
| } | ||
| }); | ||
| }; | ||
|
|
||
| addToConsolidated(consolidatedAvailableReward, getRewardsResponse.consolidated_promises); | ||
| addToConsolidated(consolidatedAvailableReward, getRewardsResponse.consolidated_rewards); | ||
|
|
||
| // Accumulate premium rewards from project_locked counterparts | ||
| if (getRewardsResponse.project_locked_rewards) { | ||
| addToConsolidated(consolidatedAvailableRewardPremium, getRewardsResponse.project_locked_rewards.consolidated_promises); | ||
| addToConsolidated(consolidatedAvailableRewardPremium, getRewardsResponse.project_locked_rewards.consolidated_rewards); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| const allAssetIds = [ | ||
| ...Object.keys(consolidatedAvailableReward), | ||
| ...Object.keys(consolidatedAvailableRewardPremium), | ||
| return [ | ||
| ...toClaimableTokens(regular, tokens, false), | ||
| ...toClaimableTokens(premium, tokens, true), | ||
| ]; | ||
|
|
||
| let hasMissingToken = false; | ||
| if (tokens) { | ||
| hasMissingToken = allAssetIds.some((id) => !(tokens as Record<string, TokenInfo>)[id]); | ||
| } else { | ||
| hasMissingToken = true; | ||
| } | ||
| if (hasMissingToken) { | ||
| const refreshedTokens = await getTokensFromVM(); | ||
| tokens = refreshedTokens as unknown as Record<string, TokenInfo> | null; | ||
| if (tokens == null) return claimableTokens; | ||
| } | ||
|
|
||
| const addTokensToClaimable = (rewardsByAsset: Record<string, number>, premium: boolean) => { | ||
| Object.keys(rewardsByAsset).forEach((assetId) => { | ||
| const token = tokens[assetId]; | ||
| if (!token) { | ||
| console.warn(`Token metadata missing for asset: ${assetId}`); | ||
| return; | ||
| } | ||
| const { decimals: tokenDecimals = 0, logo = "", ticker = "" } = token || {}; | ||
| const decimals = Number(tokenDecimals); | ||
| const amount = rewardsByAsset[assetId] / Math.pow(10, decimals); | ||
| // TODO: Integrate real pricing when available - currently using empty prices map for minimal implementation | ||
| const { price, total } = getTokenValue(assetId, amount, {}); | ||
|
|
||
| claimableTokens.push({ | ||
| assetId, | ||
| ticker: ticker as string, | ||
| logo: logo as string, | ||
| decimals, | ||
| amount, | ||
| premium, | ||
| native: isNativeToken(assetId), | ||
| price, | ||
| total, | ||
| }); | ||
| }); | ||
| }; | ||
|
|
||
| addTokensToClaimable(consolidatedAvailableReward, false); | ||
| addTokensToClaimable(consolidatedAvailableRewardPremium, true); | ||
|
|
||
| return claimableTokens; | ||
| } | ||
|
|
||
| export const onRequestGet: PagesFunction<Env> = async (context) => { | ||
| const { request, env } = context; | ||
| const url = new URL(request.url); | ||
| const stakeAddress = url.searchParams.get("walletId"); | ||
|
|
||
| console.log("getRewards called with stakeAddress:", stakeAddress); | ||
| console.log("API Key exists:", !!env.VITE_VM_API_KEY); | ||
| console.log("API Key length:", env.VITE_VM_API_KEY?.length); | ||
| const stakeAddress = new URL(request.url).searchParams.get('walletId'); | ||
|
|
||
| if (!stakeAddress) { | ||
| return new Response( | ||
| JSON.stringify({ error: "stakeAddress is required" }), | ||
| { | ||
| status: 400, | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Access-Control-Allow-Origin": "*", | ||
| "Access-Control-Allow-Methods": "GET", | ||
| "Access-Control-Allow-Headers": "Content-Type" | ||
| } | ||
| } | ||
| ); | ||
| return errorResponse('walletId is required', 400); | ||
| } | ||
JaeBrian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (!env.VITE_VM_API_KEY || env.VITE_VM_API_KEY === 'your_api_key_here' || env.VITE_VM_API_KEY.trim() === '') { | ||
| return new Response( | ||
| JSON.stringify({ | ||
| error: "API key not available in environment. Please set VITE_VM_API_KEY in .dev.vars file", | ||
| details: "The API key is missing or set to a placeholder value" | ||
| }), | ||
| { | ||
| status: 500, | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Access-Control-Allow-Origin": "*", | ||
| "Access-Control-Allow-Methods": "GET", | ||
| "Access-Control-Allow-Headers": "Content-Type" | ||
| } | ||
| } | ||
| ); | ||
| if (!env.VITE_VM_API_KEY || env.VITE_VM_API_KEY.trim() === '') { | ||
| return errorResponse('Server configuration error', 500); | ||
| } | ||
|
|
||
| try { | ||
| console.log("About to call getRewards with stakeAddress:", stakeAddress); | ||
| const claimableTokens = await getRewards(stakeAddress, env); | ||
| console.log("getRewards completed successfully, found", claimableTokens.length, "tokens"); | ||
|
|
||
| return new Response( | ||
| JSON.stringify({ rewards: claimableTokens }), | ||
| { | ||
| status: 200, | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Access-Control-Allow-Origin": "*", | ||
| "Access-Control-Allow-Methods": "GET", | ||
| "Access-Control-Allow-Headers": "Content-Type" | ||
| } | ||
| } | ||
| ); | ||
| return jsonResponse({ rewards: claimableTokens }); | ||
| } catch (error) { | ||
| console.error("Full error object:", error); | ||
| console.error("Error message:", error instanceof Error ? error.message : 'Unknown error'); | ||
| console.error("Error stack:", error instanceof Error ? error.stack : 'No stack trace'); | ||
|
|
||
| return new Response( | ||
| JSON.stringify({ | ||
| error: 'Failed to process request', | ||
| details: 'Internal server error', | ||
| stakeAddress: stakeAddress | ||
| }), | ||
| { | ||
| status: 500, | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "Access-Control-Allow-Origin": "*", | ||
| "Access-Control-Allow-Methods": "GET", | ||
| "Access-Control-Allow-Headers": "Content-Type" | ||
| } | ||
| } | ||
| ); | ||
| console.error('getRewards error:', error); | ||
| return errorResponse('Failed to process request'); | ||
| } | ||
| }; | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import type { Env } from '../types/env'; | ||
| import { jsonResponse, errorResponse, optionsResponse } from '../services/vmClient'; | ||
|
|
||
| const VM_URL = 'https://vmprev.adaseal.eu'; | ||
| const CACHE_KEY = '__internal:settings_cache'; | ||
| const CACHE_TTL = 3600; | ||
|
|
||
| export const onRequestGet: PagesFunction<Env> = async (context) => { | ||
| const { env } = context; | ||
|
|
||
| if (!env.VITE_VM_API_KEY || env.VITE_VM_API_KEY.trim() === '') { | ||
| return errorResponse('Server configuration error', 500); | ||
| } | ||
|
|
||
| try { | ||
| const cached = await env.VM_WEB_PROFILES.get(CACHE_KEY, { type: 'json' }); | ||
| if (cached !== null) { | ||
| return jsonResponse(cached); | ||
| } | ||
|
|
||
| const url = `${VM_URL}/api.php?action=get_settings`; | ||
| const response = await fetch(url, { | ||
| headers: { 'X-API-Token': env.VITE_VM_API_KEY }, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| return errorResponse('Upstream service error', 502); | ||
| } | ||
|
|
||
| const settings = await response.json(); | ||
|
|
||
| await env.VM_WEB_PROFILES.put(CACHE_KEY, JSON.stringify(settings), { | ||
| expirationTtl: CACHE_TTL, | ||
| }); | ||
|
|
||
| return jsonResponse(settings); | ||
| } catch (error) { | ||
| console.error('getSettings error:', error); | ||
| return errorResponse('Failed to fetch settings'); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| export const onRequestOptions: PagesFunction = async () => optionsResponse(); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.