rfunds-online/lib/integrations.ts

100 lines
2.8 KiB
TypeScript

/**
* Integration transforms: convert external API data into FlowNodes
*/
import type { FlowNode, FunnelNodeData, OutcomeNodeData, IntegrationSource } from './types'
import type { SafeBalance } from './api/safe-client'
// ─── Safe Balances → Funnel Nodes ────────────────────────────
export function safeBalancesToFunnels(
balances: SafeBalance[],
safeAddress: string,
chainId: number,
startPosition = { x: 0, y: 100 }
): FlowNode[] {
// Filter to non-zero balances with meaningful fiat value (> $1)
const meaningful = balances.filter((b) => {
const fiat = parseFloat(b.fiatBalance)
return fiat > 1
})
return meaningful.map((b, i) => {
const fiatValue = parseFloat(b.fiatBalance)
const source: IntegrationSource = {
type: 'safe',
safeAddress,
safeChainId: chainId,
tokenAddress: b.tokenAddress,
tokenSymbol: b.symbol,
tokenDecimals: b.token?.decimals ?? 18,
lastFetchedAt: Date.now(),
}
const data: FunnelNodeData = {
label: `${b.symbol} Treasury`,
currentValue: fiatValue,
minThreshold: Math.round(fiatValue * 0.2),
maxThreshold: Math.round(fiatValue * 0.8),
maxCapacity: Math.round(fiatValue * 1.5),
inflowRate: 0,
overflowAllocations: [],
spendingAllocations: [],
source,
}
return {
id: `safe-${chainId}-${b.tokenAddress || 'native'}-${Date.now()}`,
type: 'funnel' as const,
position: { x: startPosition.x + i * 280, y: startPosition.y },
data,
}
})
}
// ─── rVote Proposals → Outcome Nodes ─────────────────────────
export interface RVoteProposal {
id: string
title: string
description?: string
status: string
score: number
author?: { id: string; name: string | null }
createdAt?: string
_count?: { votes: number; finalVotes: number }
}
export function proposalsToOutcomes(
proposals: RVoteProposal[],
spaceSlug: string,
startPosition = { x: 0, y: 600 }
): FlowNode[] {
return proposals.map((p, i) => {
const source: IntegrationSource = {
type: 'rvote',
rvoteSpaceSlug: spaceSlug,
rvoteProposalId: p.id,
rvoteProposalStatus: p.status,
rvoteProposalScore: p.score,
lastFetchedAt: Date.now(),
}
const data: OutcomeNodeData = {
label: p.title.length > 40 ? p.title.slice(0, 37) + '...' : p.title,
description: p.description?.slice(0, 200) || '',
fundingReceived: 0,
fundingTarget: 0, // user sets this manually
status: 'not-started',
source,
}
return {
id: `rvote-${p.id}-${Date.now()}`,
type: 'outcome' as const,
position: { x: startPosition.x + i * 250, y: startPosition.y },
data,
}
})
}