Internet Computer HTTPS Interface

The Internet Computer HTTPS API accepts a CBOR-encoded data structure via POST to a URL such as https://ic0.app/api/v2/canister/fmfd2-7qaaa-aaaae-aaapq-cai/call

We show how to craft the CBOR object described in the spec and send it to a given canister. For a challenge, we avoid libraries while keeping our source short enough to fit comfortably on this page.

Encoding to CBOR

The following Haskell compiles to a wasm binary that produces a CBOR blob with a suitable expiry timestamp given a canister, request type, method name, and argument:

module Main where
import Base
foreign import ccall "argc"    argc    :: IO Int
foreign import ccall "argvlen" argvlen :: Int -> IO Int
foreign import ccall "argvat"  argvat  :: Int -> Int -> IO Char
foreign import ccall "putchar" putChar :: Char -> IO ()
foreign import ccall "now"     now     :: IO Int

byte n = (toEnum n:)

cborN t n
  | n < 24    = hdr $ fromIntegral n
  | n < 2^8   = hdr 24 . byte (fromIntegral n)
  | n < 2^16  = hdr 25 . cborLong 2 n
  | n < 2^32  = hdr 26 . cborLong 4 n
  | otherwise = hdr 27 . cborLong 8 n
  where hdr k = byte $ t*32 + k

cborLong 0 _ = id
cborLong k n = cborLong (k - 1) q . byte (fromIntegral r)
  where (q, r) = divMod n 256

cborBlob bs = cborN 2 (length bs) . foldr (.) id (byte <$> bs)
cborTxt   s = cborN 3 (length s) . (s++)
cborMap kvs = cborN 5 (length kvs) . foldr (.) id (uncurry (.) <$> kvs)

enc :: [Int] -> String -> String -> [Int] -> Integer -> String -> String
enc canID request_type method arg t = cborMap [(cborTxt "content", cborMap
    [ (cborTxt "ingress_expiry", cborN 0 t)
    , (cborTxt "sender"        , cborBlob [0x04])
    , (cborTxt "canister_id"   , cborBlob canID)
    , (cborTxt "request_type"  , cborTxt request_type)
    , (cborTxt "method_name"   , cborTxt method)
    , (cborTxt "arg"           , cborBlob arg)
    ]
  )]

unbase32 raw = go 0 0 s where
  go acc len rest
    | 8 <= len = fromIntegral q : go r (len - 8) rest
    | otherwise = case rest of
      [] -> []
      n:ns -> go (acc*32 + n) (len + 5) ns
    where (q, r) = divMod acc (2^(len - 8))
  tab = zip (['a'..'z'] ++ ['2'..'7']) [0..]
  s = concatMap (maybe [] pure . (`lookup` tab)) raw

getArgs = do
  count <- argc
  forM [0..count - 1] \n -> do
    len <- argvlen n
    forM [0..len - 1] $ argvat n

foreign export ccall "go" main
main = do
  [can, request_type, method, arg] <- getArgs
  t <- fromIntegral <$> now
  let buf = enc (drop 4 $ unbase32 can) request_type method (fromEnum <$> arg) (10^9 * (t + 120)) ""
  mapM_ putChar buf

ingress_expiry: This time is given in nanoseconds(!) since the epoch, thus requires a 64-bit int. Unfortunately, 64-bit ints are difficult to work with in JavaScript. To work around this, we add a function that returns the epoch time in seconds to the wasm environment, and convert to nanoseconds in Haskell.

sender: Our demo uses the anonymous sender, which is represented with the fixed ID 4.

canister_id: The raw canister ID can be computed by base32-decoding the ID then dropping the initial 4-byte CRC checksum. In the shell:

echo fmfd2-7qaaa-aaaae-aaapq-cai= | tr a-z A-Z | base32 -d -i | tail -c+5 | xxd

request_type: This must agree with the end of the URL path, which is call in the example above. This seems redundant, but perhaps there is a good reason for also specifying the method type in the URL.

Sending a POST

Below, our agent JavaScript function runs the wasm produced by the above Haskell in a suitable environment to send requests via the Internet Computer HTTPS API.

Clicking on the "Send!" button triggers the go function, which calls agent to send a request to the Internet Computer. A successful call yields a plain 202 response. A query yields a 200 with a blob containing a CBOR-encoded map. Within this map, the status field indicates whether the query was rejected or accepted.

We assume status 200 queries are successful and a fragile ad hoc CBOR decoder does just enough work to extract the reply, which is typically a Candid message.



See also

DFINITY maintains agent-js, a comprehensive JavaScript agent (with many dependencies) for communicating with the Internet Computer.