Skip to content
Open
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
107e5f6
feat: added gmx agent config
JustUzair Dec 29, 2025
6805ca0
feat: partial agent setup for copilotkit route.ts
JustUzair Dec 29, 2025
e972289
feat: init langgraph project and setup basic agent
JustUzair Dec 30, 2025
6d5682d
feat: added GMX Order Type Schem; added public and wallet client; add…
JustUzair Dec 30, 2025
cfa6623
fix: added constants file export
JustUzair Dec 30, 2025
50dc116
feat: added workflow context; updated constants ;updated .env.example
JustUzair Dec 30, 2025
0828e43
feat: added gmx agent context
JustUzair Dec 31, 2025
a48c934
feat: added command codes to hire and fire agent
JustUzair Dec 31, 2025
4bb98f8
feat: added hire and fire node to the graph; configured graph in lang…
JustUzair Jan 1, 2026
f84e3d3
fix: fixed agent config
JustUzair Jan 1, 2026
f6e9513
fix: fixed edges in the graph; fixed bugs in the agent context file
JustUzair Jan 1, 2026
d3590e9
feat: added bootstrap node to show initial state after hiring
JustUzair Jan 1, 2026
01b54e5
feat: added polling; open positionl hold position; close position nodes
JustUzair Jan 4, 2026
5627e6a
fix: removed boilerplate state
JustUzair Jan 6, 2026
d88bad4
feat: added onchain-actions client to create long and short position tx
JustUzair Jan 6, 2026
de7667d
fix: moved the gmx agent to a separate port to prevent clashes
JustUzair Jan 7, 2026
8aa41e9
feat: added boostrap context caching
JustUzair Jan 7, 2026
4c71bcc
feat: added gmx actions, telemetry and states
JustUzair Jan 7, 2026
e42ff35
feat: registered gmx agent to a separate port to prevent clashes
JustUzair Jan 7, 2026
eac0f53
todo: remove open close and hold commands and use decision engine ins…
JustUzair Jan 7, 2026
847f575
feat: added conditional bootstrapping to commands in case of missing …
JustUzair Jan 7, 2026
7460930
partial-feat: added helper scripts to create gmx order calldatas (due…
JustUzair Jan 7, 2026
fe11f8e
feat: added summarize node
JustUzair Jan 8, 2026
a00e6d7
fix: updated create-delegation-calldata script to work with zod; adde…
JustUzair Jan 8, 2026
92391b7
fix: removed open, close and hold position nodes and moved the logic …
JustUzair Jan 8, 2026
84b45ce
modify: modified context, types and constants
JustUzair Jan 8, 2026
21ed263
feat: added decision evaluation and calldata creation to open positio…
JustUzair Jan 8, 2026
ce44116
fix: removed unwanted states and commands from runCommand node
JustUzair Jan 8, 2026
34fab36
fix: error handling for polling node, code cleanup
JustUzair Jan 8, 2026
891840b
fix: fixed agent context types and properties
JustUzair Jan 8, 2026
33afdf9
fix: minor bug fixes
JustUzair Jan 8, 2026
9a9ba21
feat: added close position logic
JustUzair Jan 8, 2026
ee7702b
feat: added execution logic for agent after polling and decision
JustUzair Jan 9, 2026
6bde6b3
feat: added standalone scripts to mock approval and token transfers f…
JustUzair Jan 12, 2026
4c82e06
feat: added required constants for approval and intermediate transfer…
JustUzair Jan 12, 2026
02dba87
fix: fixed logic bugs to construct correct calldata for opening/closi…
JustUzair Jan 12, 2026
cefd684
feat: added tx telemetry for the on-chain executions by agent
JustUzair Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import {
GMXMarket,
GMXActivity,
DelegationBundle,
logInfo,
} from '../context.js';
import { cancelCronForThread } from '../cronScheduler.js';
import { Command } from '@langchain/langgraph';
import { createClients } from '../../clients/clients.js';
import { loadBootstrapContext } from '../store.js';
import { GMXOrderParams } from '../../domain/types.js';
import { parseUnits } from 'viem';
import { parseEther, parseUnits } from 'viem';
import { createGmxCalldata } from '../helpers/create-gmx-calldata.js';
import { PositionDirection } from '../helpers/utils/types.js';

type Configurable = { configurable?: { thread_id?: string } };

Expand All @@ -27,30 +30,49 @@ export const pollCommandNode = async (
config: CopilotKitConfig,
): Promise<GMXUpdate> => {
const mode = state.private.mode ?? 'debug';

if (!state.private.bootstrapped) {
return new Command({
goto: 'bootstrap',
});
}
console.log('Inside Poll Command Node');
const { task, statusEvent } = buildTaskStatus(
state.view.task,
'working',
`[GMX-Agent] Starting Polling for GMX Market(s)`,
);
const hasPosition = Array.isArray(state.view.positions) && state.view.positions.length > 0;

if (!hasPosition) {
return new Command({
goto: 'openPositionCommand',
update: {
view: {
task,
},
private: {
mode,
},
},
});
const decision: GMXAction = await evaluateGMXDecision({
markets: state.view.profile.allowedMarkets,
tokens: state.view.profile.allowedTokens,
telemetry: state.view.activity.telemetry,
});

const { account, agentWalletAddress } = await loadBootstrapContext();

logInfo(`Poll Decision: `, decision);
if (decision.kind === 'open-position') {
const acceptablePrice = parseUnits('3100', 30); // 3100 $ hardcoded market price of ETH
logInfo(
`Market ${decision.direction == 0 ? 'Long' : 'Short'} Calldata`,
await createGmxCalldata({

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sample Output:

[GMX-Perp][2026-01-08T14:35:14.413Z] Initialized LangGraph workflow context {
agent-gmx:dev:   mode: 'debug',
agent-gmx:dev:   pollIntervalMs: 30000,
agent-gmx:dev:   streamLimit: -1,
agent-gmx:dev:   delegationsBypassActive: false
agent-gmx:dev: }
agent-gmx:dev: Inside Poll Command Node
agent-gmx:dev: [GMX-Perp][2026-01-08T14:35:14.418Z] Poll Decision:  {
agent-gmx:dev:   kind: 'open-position',
agent-gmx:dev:   marketAddress: '0x70d95587d40A2caf56bd97485aB3Eec10Bee6336',
agent-gmx:dev:   direction: 0,
agent-gmx:dev:   sizeUsd: '10',
agent-gmx:dev:   leverage: '2',
agent-gmx:dev:   collateralAmount: '5',
agent-gmx:dev:   collateralToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
agent-gmx:dev:   reason: 'No open GMX position detected; opening demo long'
agent-gmx:dev: }
agent-gmx:dev: ==============================================
agent-gmx:dev: Order Type: MarketIncrease
agent-gmx:dev: Direction: Long
agent-gmx:dev: Decrease Swap Type : N/A
agent-gmx:dev: Market: 0x70d95587d40A2caf56bd97485aB3Eec10Bee6336
agent-gmx:dev: Collateral Token: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831
agent-gmx:dev: Collateral Amount: 5000000
agent-gmx:dev: Size Delta USD: 10000000000000000000000000000000
agent-gmx:dev: Acceptable Price: 3100000000000000000000000000000000
agent-gmx:dev: Execution Fee: 1000000000000000 ETH
agent-gmx:dev: Swap Path Length: 0
agent-gmx:dev: ==============================================
agent-gmx:dev: 
agent-gmx:dev: sendWnt Calldata:
agent-gmx:dev:   Function: sendWnt
agent-gmx:dev:   Receiver: 0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5
agent-gmx:dev:   Amount: 1000000000000000 ETH
agent-gmx:dev: ==============================================
agent-gmx:dev: 
agent-gmx:dev: sendTokens Calldata:
agent-gmx:dev:   Function: sendTokens
agent-gmx:dev:   Token: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831
agent-gmx:dev:   Receiver: 0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5
agent-gmx:dev:   Amount: 5000000
agent-gmx:dev: ==============================================
agent-gmx:dev: 
agent-gmx:dev: createOrder Calldata:
agent-gmx:dev:   Function: createOrder
agent-gmx:dev:   Order Type: MarketIncrease
agent-gmx:dev:   Decrease Swap Type: N/A
agent-gmx:dev:   Is Long: true
agent-gmx:dev:   Should Unwrap: false
agent-gmx:dev:   Trigger Price: 0
agent-gmx:dev:   Min Output: 0
agent-gmx:dev: [GMX-Perp][2026-01-08T14:35:14.424Z] Market Long Calldata {
agent-gmx:dev:   sendWntCalldata: '0x7d39aaf100000000000000000000000031ef83a530fde1b38ee9a18093a333d8bbbc40d500000000000000000000000000000000000000000000000000038d7ea4c68000',
agent-gmx:dev:   sendTokensCalldata: '0xe6d66ac8000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000031ef83a530fde1b38ee9a18093a333d8bbbc40d500000000000000000000000000000000000000000000000000000000004c4b40',
agent-gmx:dev:   createOrderCalldata: '0xf59c48eb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000007e37be2022c0914b268000000000000000000000000000000000000000000000000000000000000000004c4b40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000098d7803aea152ff1009f0000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000003fcc61a10abdb7fae57af4cb0db19188088c696400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070d95587d40a2caf56bd97485ab3eec10bee6336000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
agent-gmx:dev:   multicallCalldata: '0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000447d39aaf100000000000000000000000031ef83a530fde1b38ee9a18093a333d8bbbc40d500000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064e6d66ac8000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000031ef83a530fde1b38ee9a18093a333d8bbbc40d500000000000000000000000000000000000000000000000000000000004c4b40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000344f59c48eb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000007e37be2022c0914b268000000000000000000000000000000000000000000000000000000000000000004c4b40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000098d7803aea152ff1009f0000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000003fcc61a10abdb7fae57af4cb0db19188088c696400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070d95587d40a2caf56bd97485ab3eec10bee6336000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
agent-gmx:dev:   orderTypeName: 'MarketIncrease'
agent-gmx:dev: }
agent-gmx:dev: [GMX-Perp][2026-01-08T14:35:14.427Z] Inside Summarize Node.
agent-gmx:dev: [GMX-Perp][2026-01-08T14:35:14.427Z] GMX workflow completed...
agent-gmx:dev: info:    ┏ Background run succeeded
agent-gmx:dev: info:    ┃ [1] {
agent-gmx:dev: info:    ┃ [2]   run_id: '72b25f52-528c-49ba-ae88-9618689a8ddb',
agent-gmx:dev: info:    ┃ [3]   run_attempt: 1,
agent-gmx:dev: info:    ┃ [4]   run_created_at: 2026-01-08T14:35:14.389Z,
agent-gmx:dev: info:    ┃ [5]   run_started_at: 2026-01-08T14:35:14.403Z,
agent-gmx:dev: info:    ┃ [6]   run_ended_at: 2026-01-08T14:35:14.429Z,
agent-gmx:dev: info:    ┃ [7]   run_exec_ms: 26
agent-gmx:dev: info:    β”— [8] }
agent-gmx:dev: info:    β–ͺ <-- GET /threads/c0d649d0-8c61-459c-963d-3526cf4b53bb/state
agent-gmx:dev: info:    β–ͺ --> GET /threads/c0d649d0-8c61-459c-963d-3526cf4b53bb/state 200 1ms
web:dev:  POST /api/copilotkit 200 in 24.8s

receiver: agentWalletAddress,
orderType: 2,
direction: decision.direction ?? 0,
sizeDeltaUsd: parseUnits(decision.sizeUsd, 30),
acceptablePrice: acceptablePrice,
collateralToken: decision.collateralToken,
collateralAmount: parseUnits(decision.collateralAmount, 6), // assuming this is USDC for now
marketAddress: decision.marketAddress,
executionFee: parseEther('0.001'),
}),
);
}

/// TODO add states here
return new Command({
goto: 'holdPositionCommand',
goto: 'summarize',
update: {
view: {
task,
Expand All @@ -67,34 +89,34 @@ export type GMXDecisionContext = {
markets: GMXMarket[];
tokens: string[];
telemetry: GMXActivity['telemetry'];
marketAddress: `0x${string}`;
};

export function evaluateGMXDecision(ctx: GMXDecisionContext): GMXAction {
export async function evaluateGMXDecision(ctx: GMXDecisionContext): GMXAction {
const hasOpenPosition = (ctx.telemetry?.length ?? 0) > 0;

// Case 1: No position β†’ OPEN
if (!hasOpenPosition) {
// 10$ position with $5 collateral
return {
kind: 'open-position',
marketAddress: ctx.marketAddress,
marketAddress: ctx.markets[0].marketToken as `0x${string}`,
direction: PositionDirection.Long,
sizeUsd: '10',
leverage: '2',
collateralToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC
collateralAmount: '5',
collateralToken: ctx.markets[0].shortToken, // USDC
reason: 'No open GMX position detected; opening demo long',
};
}

// Case 2: Position exists β†’ CLOSE
return {
kind: 'close-position',
marketAddress: ctx.marketAddress,
marketAddress: ctx.markets[0].marketToken as `0x${string}`,
reason: 'Existing GMX position detected; closing for demo',
};
}

export function executeGMXDecision({
export async function executeGMXDecision({
action,
delegationBundle,
delegationsBypassActive,
Expand All @@ -110,7 +132,7 @@ export function executeGMXDecision({
if (action.kind === 'hold') {
throw new Error('executeDecision invoked with hold action');
}
const { account } = loadBootstrapContext();
const { account } = await loadBootstrapContext();
const gmxPayload: GMXOrderParams = {};
if (action.kind === 'open-position') {
/// TODO: Bring in the offchain util scripts for creating calldata for GMX execution
Expand Down