/** * 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, } }) }