100 lines
2.8 KiB
TypeScript
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,
|
|
}
|
|
})
|
|
}
|