Awesome
æREPL-web
Description
æREPL-web is a websocket interface to æREPL which allows executing æternity Sophia Smart Contract Language interactively through Elixir channels.
Setup
- Clone the project and get the dependencies:
git clone https://github.com/aeternity/aerepl-web
cd aerepl-web && mix deps.get
- Update the environment:
export ERL_LIBS=$ERL_LIBS:$(pwd)/deps/aerepl/_build/prod/lib
- Install Node.js dependencies:
cd assets && npm install && cd ..
- Start Phoenix endpoint:
mix phx.server
Now you can visit localhost:4000
from your browser.
Connection
To communicate with the server you will need to use channels provided by Phoenix Framework.
The connection is bound to a session on the server side where the REPL's state is kept.
See the example in assets/js/socket.js
.
Example setup
Import the necessary dependency:
import {Socket} from "phoenix";
To open the channel you will need to join the repl_session
lobby:
let channel = socket.channel("repl_session:lobby", {});
Then register the callback for incoming messages:
channel.on(/*reponse type*/, payload => {
...
});
It may be good to register several additional handlers:
channel.onError( () => console.log("Error") );
channel.onClose( () => console.log("Successful close") );
channel.join()
.receive("ok", resp => { console.log("Joined successfully"); })
.receive("error", resp => { console.log("Unable to join", resp); });
To push a message to a channel use the push
method:
channel.push(/*message type*/, /*payload*/);
Protocol
Opening a channel
socket.channel("repl_session:lobby", DATA);
Fields in DATA
:
user_session
(string, optional) — to rejoin an existing session, specify this field. If unspecified, or the session does not exist, a fresh session will be created.config
(object, optional) — initial configuration, defaults to{}
. Fields:colors
(bool, optional) — iftrue
, colors output using ANSI terminal coloring. Defaults tofalse
.
Response fields:
user_session
(string) — identifier of the session. Ifuser_session
has been specified when the channel was opened, this should be the same value.
Standard responses
Most calls have the same response format. The fields are as follows:
user_session
(string, optional)prompt
(string, optional) — the prompt to be displayed to the user. Indicates REPL state (eg. whether at breakpoint). If not specified, it hasn't changed since the previous call.msg
(string, optional) — human-readable result of the call. If not specified, then a success without a comment.raw
(any, optional) — some calls include a JSON of the Erlang value returned by the REPL. May be useful if the reply has to be parsed.
"call_str"
CLI-style interface to the REPL.
Input fields:
user_session
(string)input
(string) — evaluates textual input query. For more details, type":help"
here.
Response fields standard.
"call"
(unstable)
Raw interface to the underlying Erlang gen_server
. Don't use unless you really
want to hack.
Input fields:
user_session
(string)data
(any) — See (aerepl)[https://github.com/aeternity/aerepl?tab=readme-ov-file#generic-server-interface] for more details.
"cast"
(unstable)
Same as in call
, but does not return anything.
"app_version"
Input fields: (none)
Response: application version as string.
"banner"
Returns a nice show-offy banner wit the REPL logo and version information.
Input fields:
user_session
(string)
Response: banner as string.
"prompt"
Returns a suggestion for the user prompt, which indicates the REPL state.
Input fields:
user_session
(string)
Response fields standard. Possible values of msg
:
AESO
— REPL ready stateAESO(DBG)
— at breakpointAESO(ABORT)
— after a reverted execution (eg. due to explicit call toabort
or failed pattern matching)
"reset"
Reset the REPL state.
Input fields:
user_session
(string)
Response fields standard.
"type"
Typechecks a Sophia expression
Input fields:
user_session
(string)expr
(string) — Sophia expression to typecheck
Response fields standard.
"state"
Sets the in-REPL contract store (the value of state
in Sophia)
Input fields:
user_session
(string)expr
(string) — Sophia expression to provide the new value
Response fields standard.
"eval"
Evaluates a Sophia expression.
Input fields:
user_session
(string)expr
(string) — Sophia expression to evaluate.
Response fields standard.
"load"
Loads files into the REPL context. Only the first one will be explicitly
included. Note that the files have to be first uploaded using
update_filesystem_cache
. It also does not deploy contracts, but only makes
them visible in the REPL context.
Input fields:
user_session
(string)files
(list of string) — file names
Response fields standard.
"reload"
Reloads already files that have been loaded. Note that the files have to be
first uploaded using update_filesystem_cache
.
Input fields:
user_session
(string)files
(list of string) — file names to reload. If empty, all will be reloaded.
Response fields standard.
"update_filesystem_cache"
Uploads files to the REPL filesystem cache to make them accessible by the load
and reload
commands. Note that this does not deploy any contracts, but only
makes them visible in the REPL context.
Input fields:
user_session
(string)files
(list of objects) — list of file descriptions:filename
(string) — name of the filecontent
(string) — contents of the file
Response is empty.
"set"
Adjusts REPL's behavior. See :help set
or aerepl
documentation for more details.
Input fields:
user_session
(string)option
(string) — name of the config entryvalue
(any) — new value
Response fields standard.
"help"
Returns information about commands. Note that it's mostly relevant for CLI-style users.
Input fields:
user_session
(string)command
(string, optional) — if specified, returns information about the command. Otherwise lists all available commands in CLI style.
Response fields standard.
"disas"
Presents FATE assembly of a thing.
Input fields:
user_session
(string)ref
(string) — the thing (eg. contract function) to be disassembled
Response fields standard.
"break"
Sets a breakpoint.
Input fields:
user_session
(string)file
(string) — file nameline
(integer) — line number
Response fields standard.
"delete_break"
Removes breakpoint by id.
Input fields:
user_session
(string)id
(integer) — id of the breakpoint to be removed
Response fields standard.
"delete_break_loc"
Removes breakpoint by location.
Input fields:
user_session
(string)file
(string) — file nameline
(integer) — line number
Response fields standard.
"continue"
Resumes paused execution.
Input fields:
user_session
(string)
Response fields standard.
"stepover"
Moves paused execution by one line in the source code, skipping function calls.
Input fields:
user_session
(string)
Response fields standard.
"stepin"
Moves paused execution by one line in the source code, entering function calls.
Input fields:
user_session
(string)
Response fields standard.
"stepout"
Moves paused execution until return of the currently visited function.
Input fields:
user_session
(string)
Response fields standard.
"location"
Shows location in code of currently paused execution.
Input fields:
user_session
(string)
Response fields standard.
"print_var"
Prints value of a local variable in the currently paused execution.
Input fields:
user_session
(string)name
— variable name
Response fields standard.
"print_vars"
Prints values of all local variables in the currently paused execution.
Input fields:
user_session
(string)
Response fields standard.
"stacktrace"
Prints the stacktrace.
Input fields:
user_session
(string)
Response fields standard.
"version"
Returns version information about the REPL itself, FATE, Sophia and aeternity node.
Input fields:
user_session
(string)
Response fields standard.
Examples
What's the type of Oracle.query
?
Two ways to do it:
- CLI style:
let r = channel.push("call_str", {input: ":t Oracle.query", user_session: ...})
- Direct call:
let r = channel.push("type", {expr: "Oracle.query", user_session: ...})
Then the result can be accessed like r.receive("ok", resp => console.log(resp.msg))
.
Deploy a contract
First, obtain the contract source
let src = "contract MyContract = entrypoint f() = 123\n";
Upload the source to the REPL file cache. The contract is not deployed, nor even
typechecked yet. If you are working with multiple files, include them all in the
files
field.
let name = "MyContract.aes";
let fs = [{filename: name, content: src}];
channel.push("update_filesystem_cache", {files: fs, user_session: session});
Load the contract in the REPL context. This step is necessary for REPL to know which files to consider included directly while performing calls.
let result = channel.push("load", {files: [name], user_session: session});
Now, the result
variable contains information about whether the contract was
successfully loaded. This may not be the case for example when there are type errors.
result.receive("ok", (resp) => console.log(resp.msg));
There is no direct way to deploy contracts in the REPL, because Sophia/FATE can do it perfectly fine. To create an instance of the contract, run:
let deploy_cmd = "let my_contract = Chain.create() : MyContract";
channel.push("eval", {expr: cmd, user_session: session});
Finally, call the contract using the remote call notation from Sophia:
let call_cmd = "my_contract.f()";
channel.push("eval", {expr: cmd, user_session: session})
.receive("ok", (resp) => console.log(resp.msg));