package polymarket

  1. Overview
  2. Docs
OCaml client library for the Polymarket prediction market API

Install

dune-project
 Dependency

Authors

Maintainers

Sources

0.2.0.tar.gz
md5=4eb4c5d2f63ff081c9713d90be5a51b2
sha512=0e3de0c9b40683e09ab8f9f966a44784ef1b9b482c3eefef84104a7e8042c92f1d79893ee9588b24fa3d0decaed7f365509f4d1c23c66ce8328efb64e721f276

doc/src/polymarket.websocket/handshake.ml.html

Source file handshake.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
(** WebSocket handshake implementation (RFC 6455 Section 4).

    Performs HTTP/1.1 Upgrade handshake directly over a TLS flow. *)

let src = Logs.Src.create "polymarket.wss.handshake" ~doc:"WebSocket handshake"

module Log = (val Logs.src_log src : Logs.LOG)

(** WebSocket GUID for Sec-WebSocket-Accept calculation *)
let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

(** Generate a random 16-byte nonce and base64 encode it *)
let generate_key () =
  let bytes = Bytes.create 16 in
  for i = 0 to 15 do
    Bytes.set bytes i (Char.chr (Random.int 256))
  done;
  Base64.encode_string (Bytes.to_string bytes)

(** Calculate expected Sec-WebSocket-Accept value *)
let expected_accept key =
  let concat = key ^ websocket_guid in
  let hash = Digestif.SHA1.(digest_string concat |> to_raw_string) in
  Base64.encode_string hash

(** Read a line from flow (up to \r\n) *)
let read_line flow =
  let buf = Buffer.create 128 in
  let rec loop prev_cr =
    let byte_buf = Cstruct.create 1 in
    let _ = Eio.Flow.single_read flow byte_buf in
    let c = Cstruct.get_char byte_buf 0 in
    if prev_cr && c = '\n' then
      (* Remove trailing \r and return *)
      let s = Buffer.contents buf in
      if String.length s > 0 && s.[String.length s - 1] = '\r' then
        String.sub s 0 (String.length s - 1)
      else s
    else begin
      Buffer.add_char buf c;
      loop (c = '\r')
    end
  in
  loop false

(** Parse HTTP response status line *)
let parse_status_line line =
  (* HTTP/1.1 101 Switching Protocols *)
  match String.split_on_char ' ' line with
  | version :: code :: _ ->
      let code = int_of_string code in
      (version, code)
  | _ -> failwith ("Invalid HTTP status line: " ^ line)

(** Parse HTTP headers until empty line *)
let parse_headers flow =
  let headers = Hashtbl.create 16 in
  let rec loop () =
    let line = read_line flow in
    if String.length line = 0 then headers
    else
      match String.index_opt line ':' with
      | Some i ->
          let name =
            String.lowercase_ascii (String.trim (String.sub line 0 i))
          in
          let value =
            String.trim (String.sub line (i + 1) (String.length line - i - 1))
          in
          Hashtbl.add headers name value;
          loop ()
      | None ->
          (* Malformed header, skip *)
          loop ()
  in
  loop ()

(** Handshake result *)
type result = Success | Failed of string

(** Perform WebSocket handshake over a TLS flow *)
let perform ~flow ~host ~port ~resource =
  Log.debug (fun m -> m "Starting handshake to %s:%d%s" host port resource);

  (* Generate key for Sec-WebSocket-Key *)
  let key = generate_key () in
  let expected = expected_accept key in

  (* Build HTTP request *)
  let request =
    Printf.sprintf
      "GET %s HTTP/1.1\r\n\
       Host: %s:%d\r\n\
       Upgrade: websocket\r\n\
       Connection: Upgrade\r\n\
       Sec-WebSocket-Key: %s\r\n\
       Sec-WebSocket-Version: 13\r\n\
       Origin: https://polymarket.com\r\n\
       User-Agent: polymarket-ocaml/1.0\r\n\
       \r\n"
      resource host port key
  in

  Log.debug (fun m -> m "Sending request with key %s" key);

  (* Send request *)
  Eio.Flow.copy_string request flow;

  (* Read status line *)
  let status_line = read_line flow in
  Log.debug (fun m -> m "Status: %s" status_line);

  let _version, status_code = parse_status_line status_line in

  if status_code <> 101 then begin
    let msg = Printf.sprintf "Expected 101, got %d" status_code in
    Log.err (fun m -> m "Handshake failed: %s" msg);
    Failed msg
  end
  else begin
    (* Parse headers *)
    let headers = parse_headers flow in

    (* Verify Sec-WebSocket-Accept *)
    let accept =
      match Hashtbl.find_opt headers "sec-websocket-accept" with
      | Some v -> v
      | None -> ""
    in

    if accept <> expected then begin
      let msg =
        Printf.sprintf "Invalid Sec-WebSocket-Accept: expected %s, got %s"
          expected accept
      in
      Log.err (fun m -> m "Handshake failed: %s" msg);
      Failed msg
    end
    else begin
      Log.debug (fun m -> m "Handshake successful");
      Success
    end
  end