Skip to content
Merged
50 changes: 50 additions & 0 deletions agent/strategyParser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { parseStrategyIntent, validateStrategyPayload, StrategyPayload } from './strategyParser';

describe('Strategy Parser', () => {
it('parses valid input and sums allocations to 100', () => {
const input = {
monthlyContributionAmount: 500,
blendAllocationX: 40,
soroswapAllocationX: 30,
goldAllocationX: 30,
};
const result = parseStrategyIntent(input);
expect(result).toEqual({
monthlyContributionAmount: 500,
blendAllocationX: 40,
soroswapAllocationX: 30,
goldAllocationX: 30,
});
expect(validateStrategyPayload(result)).toBe(true);
});

it('normalizes allocations if they do not sum to 100', () => {
const input = {
monthlyContributionAmount: 1000,
blendAllocationX: 50,
soroswapAllocationX: 30,
goldAllocationX: 10,
};
const result = parseStrategyIntent(input);
expect(result.blendAllocationX + result.soroswapAllocationX + result.goldAllocationX).toBe(100);
expect(validateStrategyPayload(result)).toBe(true);
});

it('handles malformed input with fallbacks', () => {
const input = {
monthlyContributionAmount: 'not-a-number',
blendAllocationX: null,
soroswapAllocationX: undefined,
goldAllocationX: -10,
};
const result = parseStrategyIntent(input);
expect(result.monthlyContributionAmount).toBe(0);
expect(result.blendAllocationX + result.soroswapAllocationX + result.goldAllocationX).toBe(100);
expect(validateStrategyPayload(result)).toBe(true);
});

it('returns false for invalid schema', () => {
const invalid = { foo: 1, bar: 2 };
expect(validateStrategyPayload(invalid)).toBe(false);
});
});
67 changes: 67 additions & 0 deletions agent/strategyParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Strategy Parser Tool for Issue 1.3
// Converts conversational intent into a strict JSON payload for the agent

export interface StrategyPayload {
monthlyContributionAmount: number;
blendAllocationX: number;
soroswapAllocationX: number;
goldAllocationX: number;
}

/**
* Validates and normalizes the strategy payload.
* Ensures allocations sum to 100 and all fields are present.
* Applies fallbacks for malformed input.
*/
export function parseStrategyIntent(input: any): StrategyPayload {
// Fallbacks for missing or malformed fields
let monthlyContributionAmount = Number(input.monthlyContributionAmount);
if (isNaN(monthlyContributionAmount) || monthlyContributionAmount < 0) {
monthlyContributionAmount = 0;
}

// Parse allocations, fallback to 0 if missing or invalid
let blend = Number(input.blendAllocationX);
let soroswap = Number(input.soroswapAllocationX);
let gold = Number(input.goldAllocationX);
if (isNaN(blend) || blend < 0) blend = 0;
if (isNaN(soroswap) || soroswap < 0) soroswap = 0;
if (isNaN(gold) || gold < 0) gold = 0;

// Normalize allocations to sum to 100
const total = blend + soroswap + gold;
if (total === 0) {
// All allocations are invalid or zero, fallback to 100/0/0
blend = 100;
soroswap = 0;
gold = 0;
} else if (total !== 100) {
blend = Math.round((blend / total) * 100);
soroswap = Math.round((soroswap / total) * 100);
gold = 100 - blend - soroswap; // Ensure sum is exactly 100
}

return {
monthlyContributionAmount,
blendAllocationX: blend,
soroswapAllocationX: soroswap,
goldAllocationX: gold,
};
}

/**
* Validates that the payload matches the schema and allocations sum to 100.
*/
export function validateStrategyPayload(payload: any): boolean {
if (
typeof payload !== 'object' ||
typeof payload.monthlyContributionAmount !== 'number' ||
typeof payload.blendAllocationX !== 'number' ||
typeof payload.soroswapAllocationX !== 'number' ||
typeof payload.goldAllocationX !== 'number'
) {
return false;
}
const sum = payload.blendAllocationX + payload.soroswapAllocationX + payload.goldAllocationX;
return sum === 100;
}
2 changes: 1 addition & 1 deletion contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
soroban-sdk = "22.0.0"

[dev_dependencies]
[dev-dependencies]
soroban-sdk = { version = "22.0.0", features = ["testutils"] }

[features]
Expand Down
Loading
Loading