package socket import ( "context" protoSocket "git.toki-labs.com/toki/proto-socket/go" altv1 "git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1" "git.toki-labs.com/toki/alt/services/api/internal/workerclient" ) // paperHandlers returns the API session handlers for the paper trading surface. // Each handler validates the request shape and forwards it to the worker through // the injected Worker client; paper execution and state stay worker-owned. func paperHandlers(worker workerclient.WorkerClient) []sessionHandler { return []sessionHandler{ { requestType: protoSocket.TypeNameOf(&altv1.StartPaperTradingRequest{}), register: func(client *protoSocket.WsClient) { protoSocket.AddRequestListenerTyped[*altv1.StartPaperTradingRequest, *altv1.StartPaperTradingResponse](&client.Communicator, func(req *altv1.StartPaperTradingRequest) (*altv1.StartPaperTradingResponse, error) { return handleStartPaperTrading(worker, req) }) }, }, { requestType: protoSocket.TypeNameOf(&altv1.GetPaperTradingStateRequest{}), register: func(client *protoSocket.WsClient) { protoSocket.AddRequestListenerTyped[*altv1.GetPaperTradingStateRequest, *altv1.GetPaperTradingStateResponse](&client.Communicator, func(req *altv1.GetPaperTradingStateRequest) (*altv1.GetPaperTradingStateResponse, error) { return handleGetPaperTradingState(worker, req) }) }, }, } } func handleStartPaperTrading(worker workerclient.WorkerClient, req *altv1.StartPaperTradingRequest) (*altv1.StartPaperTradingResponse, error) { if req.GetAccountId() == "" { return &altv1.StartPaperTradingResponse{Error: invalidRequest("account_id is required")}, nil } if req.GetSpec() == nil { return &altv1.StartPaperTradingResponse{Error: invalidRequest("spec is required")}, nil } if req.GetStartingCash() == nil { return &altv1.StartPaperTradingResponse{Error: invalidRequest("starting_cash is required")}, nil } if worker == nil { return &altv1.StartPaperTradingResponse{Error: workerUnavailable()}, nil } ctx, cancel := context.WithTimeout(context.Background(), workerRequestTimeout) defer cancel() if err := worker.Connect(ctx); err != nil { return &altv1.StartPaperTradingResponse{Error: workerErrorInfo(err)}, nil } res, err := worker.StartPaperTrading(ctx, req) if err != nil { return &altv1.StartPaperTradingResponse{Error: workerErrorInfo(err)}, nil } if res == nil { return &altv1.StartPaperTradingResponse{Error: internalError("worker returned no paper start response")}, nil } return res, nil } func handleGetPaperTradingState(worker workerclient.WorkerClient, req *altv1.GetPaperTradingStateRequest) (*altv1.GetPaperTradingStateResponse, error) { if req.GetAccountId() == "" { return &altv1.GetPaperTradingStateResponse{Error: invalidRequest("account_id is required")}, nil } if worker == nil { return &altv1.GetPaperTradingStateResponse{Error: workerUnavailable()}, nil } ctx, cancel := context.WithTimeout(context.Background(), workerRequestTimeout) defer cancel() if err := worker.Connect(ctx); err != nil { return &altv1.GetPaperTradingStateResponse{Error: workerErrorInfo(err)}, nil } res, err := worker.GetPaperTradingState(ctx, req) if err != nil { return &altv1.GetPaperTradingStateResponse{Error: workerErrorInfo(err)}, nil } if res == nil { return &altv1.GetPaperTradingStateResponse{Error: internalError("worker returned no paper state response")}, nil } return res, nil }