[wip] updating sidekick/jex/awcp for message sign demo

This commit is contained in:
Foo Bar
2022-11-25 18:01:28 -07:00
parent 9305208ba6
commit c3194b1ec2
6 changed files with 425 additions and 690 deletions
+2 -489
View File
@@ -1,490 +1,3 @@
# Sidekick
Sidekick is a simple JavaScript library for talking to an Aeternity
browser wallet extension such as Superhero from the document context
of a webpage.
| **Thing** | **URL** |
| --- | --- |
| Bug Tracker | https://gitlab.com/DoctorAjayKumar/sidekick/-/issues |
| Documentation | http://orangepill.healthcare/projects/sidekick/current/docs |
| Examples | https://gitlab.com/DoctorAjayKumar/sidekick/-/tree/master/examples |
| Git repository | https://gitlab.com/DoctorAjayKumar/sidekick |
| Homepage | http://orangepill.healthcare/projects/sidekick |
| License | ./LICENSE.txt |
| Maintainer | Dr. Ajay Kumar PHD <DoctorAjayKumar@protonmail.com> |
| Releases | http://orangepill.healthcare/projects/sidekick/releases |
> One of the most important things for designing a computer, which I
> think most designers don't do, is you study the problem you want to
> solve. And then use what you learn from studying the problem you
> want to solve to put in the mechanisms needed to solve it in the
> computer you're building. No more, no less.
— Gerald Jay Sussman
### What sidekick is NOT
Sidekick is **not** ideal if you operate in the Node/NPM ecosystem. If you are
working in the Node/NPM ecosystem, you probably want the [Aeternity JavaScript
SDK](https://github.com/aeternity/aepp-sdk-js/). Every single
Aeternity-related thing you could ever possibly want to do is possible to
accomplish with the SDK.
All that Sidekick knows how to do is talk to a browser wallet extension. In an
application, there is a great deal of necessary functionality (e.g. forming
transactions for the wallet to sign) which sidekick assumes your server-side
code has already handled.
In all software there is a tradeoff between simplicity and number of features.
Sidekick is very simple: 2300 lines of TypeScript, including comments, with no
dependencies. Therefore Sidekick intentionally only has a very limited set of
features.
# How to use this library
To obtain this library, download and unpack a release tarball
wget http://zxq9.com/projects/vanillae/sidekick/releases/sidekick_dist_X-Y-Z.tar.gz
tar xzvf sidekick_dist_X-Y-Z.tar.gz
You *should* be able to get all of the functionality you need just
from the exposed functions in the `sidekick` module. (If this is not
the case, please report this as a bug.)
You probably want to read the documentation of:
sidekick.js : top-level function calls
awcp/awcp.js : explains the return types of top-level sidekick
functions, explains what sidekick actually does
for you, and explains how all the various
modules fit together
skylight.js : main data structure in sidekick
helpers.js : what it sounds like
There are examples explained in this document. Their source can be
found in full in the GitLab repository, link above. The examples
exhibit how to interact with the standard release tarball using
TypeScript.
## General flow of Sidekick usage
1. Import the sidekick.js file:
```typescript
import * as sk from '/path/to/sidekick_dist_X-Y-Z/dist_js/sidekick.js'
```
2. Create a `Skylight`. This is the main data structure of Sidekick.
There are two ways to do this
1. `start_dwim() : Promise<Skylight>`
This is probably the one you want. It starts up the Skylight
and does the handshake with the wallet (will pop up the
little window for your user). The effect of this is to
populate all the variables like the user's public key.
Note that this is an async function. It doesn't return until
the handshake is complete.
```typescript
// Needs to be wrapped in an async function in order to use
// await keyword
async function main() : Promise<void> {
let my_skylight = await sk.start_dwim();
...
}
main();
```
2. `start : Skylight`
This is the vanilla option. All it does is create the
Skylight object.
Beware that this does start up a message queue that passively
listens for messages from the wallet.
3. Pass the skylight in to various functions along with additional
calldata generated by your server-side backend code.
## Example 1: Hello World
The effect of this example is to print "hello world" to the JS
console (Ctrl+Shift+C in most browsers). This example is included for
debugging/mental-quicksand-escape purposes.
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello world: Sidekick</title>
</head>
<body>
<h1>Sidekick Example: Hello World</h1>
<h4>Check the console</h4>
<script type="module">
import * as sk from '../sidekick_dist/dist_js/sidekick.js';
sk.hello();
</script>
</body>
</html>
```
The important lines are
```html
<script type="module">
import * as sk from '../sidekick_dist/dist_js/sidekick.js';
sk.hello();
</script>
```
This shows
- you need to use `type="module"` in order for imports to work
- how to do an import
- how to call functions from an imported module
## Example 2: get the user to sign a transaction
Anything you will want the user to do (e.g. sign smart contracts) is
contained in "get the user to sign a transaction". This example
illustrates the basic things you need to do in order to accomplish
that.
Sidekick does not provide functionality for forming the transaction
string you want the user to sign. Your backend should be doing that.
The example here uses a shim library called `parasite`, which you can find in
the GitLab repository in the `examples` directory. Parasite is just a rewrite
layer in front of the `testnet.aeternity.io` JSON interface. It is not suitable
for usage in production.
This is adapted from
[`examples/examples_src_ts/do_a_transfer.ts`][dat_ts]. The
difference is that `do_a_transfer.ts` is written against
[`examples/examples_html/do_a_transfer.html`][dat_html]. It contains
a lot of unnecessary detail, like doing things in response to buttons
being clicked, and filling in DOM textboxes with the results of things.
The full example has logic that resembles a register machine, rather
than a functional program. This example is closer to a functional
program.
[dat_html]: https://gitlab.com/DoctorAjayKumar/sidekick/-/blob/master/examples/examples_html/do_a_transfer.html
[dat_ts]: https://gitlab.com/DoctorAjayKumar/sidekick/-/blob/master/examples/examples_src_ts/do_a_transfer.ts
```typescript
import * as ae_node from './parasite/ae_node.js';
import * as sk from '../sidekick_dist/dist_js/sidekick.js';
async function
main()
: Promise<void>
{
//---------------------------------------------------------------
// STEP 1: MAKE A SKYLIGHT
//---------------------------------------------------------------
let skl : sk.Skylight =
await sk.start_dwim(sk.TIMEOUT_DEF_DETECT,
sk.TIMEOUT_DEF_CONNECT,
sk.TIMEOUT_DEF_ADDRESS);
//---------------------------------------------------------------
// STEP 2: GET THE USER'S ADDRESS
//---------------------------------------------------------------
let user_addr : string =
await sk.address(skl, sk.TIMEOUT_DEF_ADDRESS);
// faster/non-async:
//
// let user_addr = skl.waellet_address
//
// the non-commented code
//
// - will crash if the wallet isn't connected
// - will query the wallet for the address if the wallet *is*
// connected but the skl.waellet_address field *is not*
// populated
// - will crash if that times out after the second argument
// number of milliseconds
//
// if you know with 100% certainty that the address field will be
// populated (which we do here, because we used connect_dwim),
// then it's safe to just grab the field from the skylight
//
// The problem is that if the address field isn't populated, the
// commented code defaults to `undefined`, rather than crashing
// with an error message and callstack trace
//---------------------------------------------------------------
// STEP 3: FORM THE TRANSACTION
//
// This will be different for your application, because you're
// not using parasite (right?)
//
// You need to figure out how to make something like `tx_obj` on
// your own.
//
// `tx_obj` is just `{tx: "some_base58_garbage"}`.
//---------------------------------------------------------------
let target_addr : string =
'ak_dvNHMgVvdSgDchLsmcUpuFTbMBGfG3E5V9KZnNjLYPyEhcqnL';
// amount is in aettos
let amount : number = 1;
let endpoint : string = ae_node.URL_TESTNET;
let spendtx = {'recipient_id' : target_addr,
'amount' : amount,
'fee' : ae_node.MIN_FEE,
'sender_id' : user_addr,
'payload' : ""};
let tx_obj = await ae_node.PostSpend(endpoint, spendtx);
//---------------------------------------------------------------
// STEP 4: HAVE THE USER SIGN THE TRANSACTION
//
// There are two options:
//
// 1. `tx_sign_no_propagate` simply has the wallet sign the
// transaction, and return the signed transaction back to you
//
// 2. `tx_sign_yes_propagate` has the wallet sign the transaction
// and also propagates it into the network. It returns a more
// elaborate data structure, which is documented in somewhere
// the AWCP module documentation.
//---------------------------------------------------------------
let result =
await sk.tx_sign_no_propagate(skl,
tx_obj,
sk.NETWORK_ID_TESTNET,
sk.TIMEOUT_DEF_SIGN);
console.log(result);
}
main();
```
# Known pitfalls
- See notes in sidekick module about potential state crossups if your
document logic is multi-threaded.
- Has only been tested against Firefox-on-Linux
- Have not worked out good user idioms for handling errors
# How the release is structured and why
The idea of the release is that it contains exactly what is needed in
order to drop Sidekick into your project and start using it. No more,
no less.
A release has the following structure:
```
sidekick_dist_X-Y-Z/
dist_js/............tsc-generated human-readable JS tree
foo.js..............the actual code that is run
foo.d.ts............included so that your TypeScript code can
typecheck against sidekick
foo.js.map..........debug symbols that map foo.js to
locations in the TypeScript source
src_ts/.............included so that the browser's debugger works
foo.ts
README.txt
LICENSE.txt
```
We use semantic versioning: `X.Y.Z`
- A change in `X` means an API-breaking change
- A change in `Y` means an non-breaking API change
- A change in `Z` means no change to the API
There is no documentation included in the release. There is
autogenerated API documentation linked above, which is generated from
this file and the sources that are included in the release.
## What each file does
```
src_ts/.....................TypeScript source for sidekick library
awcp/.......................Aepp-Waellet Communication Protocol
awcp.ts.....................Protocol definition
msgq.ts.....................Block-on-raseev implementation
msgr.ts.....................Protocol implementation
helpers.ts..................what it sounds like
sidekick.ts.................top-level module
skylight.ts.................primary data structure
```
## Where is the NPM package or the webpack bundle?
There isn't one.
### Why?
Sidekick is a library that deals with cryptocurrency. The security
model is based on transparency and trust. A user must be able to
inspect code that is running on his hardware handling his money.
We don't support NPM because of the security issues that NPM
introduces. Briefly, the code can change at any time under the
developer's nose without the developer knowing. The [leftpad
debacle][lpad] and the [RIAEvangelist debacle][ria] are good examples
of the types of vulnerabilities that package managers like NPM
enable.
[lpad]: https://archive.ph/Qsh7j
[ria]: https://archive.ph/OF5I9
We don't use something like webpack because that introduces an opaque
rewrite using an untrusted tool. Webpack could plausibly alter the
runtime behavior of the program, and we would have no way to detect
that. Even if we trusted webpack, how do we obtain webpack? NPM.
So.
It is debatable whether or not we should trust the TypeScript
compiler (TSC). On the whole, TSC probably makes Sidekick more
secure, simply by virtue of increasing overall code quality and
eliminating the largest categories of potential bugs. Moreover, the
output that TSC produces is human-readable, and has a very
straightforward mapping to the original source code. A human can
easily read the TSC-generated JavaScript tree, even without the aid
of the source map, and have a high degree of faith that the code is
trustworthy and that TSC is behaving as promised.
# How to obtain the source tree
The source tree is included in your release. You can clone the
git repository with
```
git clone https://gitlab.com/DoctorAjayKumar/sidekick.git
```
# Repo file tree (non-exhaustive)
```
examples/...................Examples
contract-examples/..........Example Sophia smart contracts
examples_html/..............Low-budget example interfaces
do_a_transfer.html......Send money to an arbitrary address
hello.html..............Hello world
ide.html................Play around with smart contracts
examples_src_ts/............TypeScript source for each example
parasite/...................JavaScript shim that does what
your backend code ordinarily
would do
ae_compiler.ts..............Talk to a remote Sophia
compiler JSON HTTP interface
ae_node.ts..................Talk to a node (for forming
transactions, querying chain,
etc)
net.ts......................Network helper functions
do_a_transfer.ts........Send money to an arbitrary address
ide.ts..................Play around with smart contracts
tsconfig.json...............Examples-specific tsc configuration
src_ts/.....................TypeScript source for sidekick library
awcp/.......................Aepp-Waellet Communication Protocol
awcp.ts.....................Protocol definition
msgq.ts.....................Block-on-raseev implementation
msgr.ts.....................Protocol implementation
helpers.ts..................what it sounds like
sidekick.ts.................top-level module
skylight.ts.................primary data structure
LICENSE.txt.................MIT License
Makefile....................Makefile
make........................Equivalent to `make build`
make build..................Run tsc
make clean..................rm -r dist_js sidekick_dist docs \
examples/examples_dist_js
make dist...................Build a release tarball directory
make jsdoc..................Build the HTML documentation
make build_examples.........cd examples && tsc
make serve_examples.........cd examples && python3 -m \
http.server 8001
README.txt..................This file
STYLE_GUIDE.md..............Explains why the code looks so weird
TODO.md.....................what it sounds like
tsconfig.json...............Configuration file for tsc
```
# How to build a release
You will need the TypeScript compiler installed. See prereqs section.
make dist
## Prereqs for building source files
If you want to edit and rebuild the source files, you need TypeScript
installed, and you should update npm.
npm install -g typescript
npm install -g npm
## Protip: avoid using `sudo npm install -g`
If you want to avoid using sudo
```
mkdir ~/.npm-packages
npm config set prefix "${HOME}/.npm-packages"
```
Edit `~/.bashrc` or `~/.zshrc` with
```
NPM_PACKAGES="${HOME}/.npm-packages"
export PATH=$NPM_PACKAGES/bin:$PATH
```
Source: https://github.com/sindresorhus/guides/blob/main/npm-global-without-sudo.md
# How to build and view documentation
```
make build_docs
make serve_docs
```
# awcp (package documentation)
Please click the word "(Documentation)" in the sidebar
+396 -18
View File
@@ -10,12 +10,77 @@
* ```
*
* You then communicate with the wallet by sending messages back over the
* `EventTarget`
* `EventTarget`. You should probably use the [sidekick
* library](https://github.com/aeternity/Vanillae/tree/master/sidekick) to do
* this.
*
* Keep in mind that these messages are not secret. Any browser extension or
* foreign page script can intercept these messages. Imagine as an (imperfect)
* analogy that you work in an office. Everyone's mail is dumped on the floor
* in the middle of the office, and you are responsible for picking out which
* letters are addressed to you. Anyone else can pick up letters addressed to
* you, and read them.
*
* This module defines the shape of the messages that are sent. There are several
* layers to the onion, each corresponding to natural branch points in the
* protocol.
*
* @example
* ```ts
* // this is a aepp-to-wallet call which is sent to `window` by say sidekick
* // layer 2 layer 3 layer 4
* // EventData_A2W<RpcCall<"connection.open", Params_A2W_connection_open>>)
* // layer 2: who is the message for
* {type : "to_waellet",
* // layer 3: json rpc
* data : {jsonrpc : "2.0",
* id : "ske-connect-1",
* method : "connection.open",
* // layer 4: AWCP-specific semantics
* params : {name : "sidekick examples",
* version : 1}}}
*
*
* // this is the associated wallet-to-aepp response which is sent to `window`
* // by Superhero
* // layer 2 layer 3 layer 4
* // EventData_W2A<RpcResp_ok<"connection.open", Result_W2A_connection_open>>)
* // layer 2: who is the message for
* {type : "to_aepp",
* // layer 3: json rpc
* data : {jsonrpc : "2.0",
* id : "ske-connect-1",
* method : "connection.open",
* // layer 4: AWCP-specific semantics
* result : {id : "mnhmmkepfddpifjkamaligfeemcbhdne",
* name : "Superhero",
* networkId : "ae_mainnet",
* origin : "chrome-extension://mnhmmkepfddpifjkamaligfeemcbhdne",
* type : "extension"}}}
* ```
*
* @example
* ```ts
* // this is a waellet-to-aepp cast (RPC verbiage: "notification"). It does
* // not require a response.
*
* // layer 2 layer 3 layer 4
* // EventData_W2A<RpcCast<"connection.announcePresence", Params_W2A_connection_announcePresence>>)
* // layer 2: who is the message for
* {type : "to_aepp",
* // layer 3: json rpc
* data : {jsonrpc : "2.0",
* method : "connection.announcePresence",
* // layer 4: AWCP-specific semantics
* params : {id : "{aee9e933-52b6-410a-8c3f-99c6be596b4e}",
* name : "Superhero",
* networkId : "ae_mainnet",
* origin : "moz-extension://ee425d81-d5b2-44b6-9406-4da31b019e7c",
* type : "extension"}}}
* ```
*
*
*
* 1. The `MessageEvent` layer. This is what is actually sent as an event.
* This is an opaque object that is built into every runtime's standard
* library: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
@@ -26,8 +91,8 @@
* 2. The `EventData` layer. This is what goes in `message_event.data`. The
* structure that goes in here is one of
*
* 1. `EventData_W2A`: waellet-to-aepp
* 2. `EventData_A2W`: aepp-to-waellet
* 1. {@link EventData_W2A}: waellet-to-aepp
* 2. {@link EventData_A2W}: aepp-to-waellet
*
* This layer corresponds to the "am I supposed to pay attention to this
* event?" branch point.
@@ -108,7 +173,7 @@
* 2. more accurately represent how it is used in practice
*
* The only difference here is that the `params` field of requests is
* non-optional, and must be an object (the RPC standard allows arrays).
* non-optional, and __must be an object__ (the RPC standard allows arrays).
*
* ## Requests
*
@@ -125,8 +190,8 @@
* 1. __DO__ have an `id` field
* 2. __AND__ __DO__ require a response.
*
* The `RpcCall` and `RpcResp` data structures each have an `id_n` type
* parameter.
* The {@link RpcCall} and {@link RpcResp} data structures each have an `id_n`
* type parameter.
*
* The purpose of this is to notate (and possibly enforce) at the type level
* the constraint that, given a call with say `id = 7`, the response must also
@@ -135,14 +200,14 @@
* # Links
*
* - `MessageEvent`s: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
* - JSON RPC 2.0 https://www.jsonrpc.org/specification
* - JSON RPC 2.0: https://www.jsonrpc.org/specification
*
* @module
*/
// TODONE: enumerate RPC errors
// TODO: examples
// TODO: transaction.sign and propagate
// TODO: constants for method names
@@ -205,7 +270,7 @@ export {
RpcCall_A2W_tx_sign_noprop,
RpcResp_W2A_tx_sign_noprop,
EventData_A2W_tx_sign_noprop,
EventData_W2A_tx_sign_noprop
EventData_W2A_tx_sign_noprop,
// message.sign
Params_A2W_msg_sign,
Result_W2A_msg_sign,
@@ -216,9 +281,6 @@ export {
};
// TODO: Give examples for everything
// TODO: Add back in transaction.sign and propagate
//=============================================================================
// LAYER 2: WHO IS THIS MESSAGE FOR
//
@@ -230,12 +292,16 @@ export {
* This is the data that is sent from the wallet to the aepp through the Event
* bus
*
* @example
* (layer 2)
*
* @example
* ```ts
* // layer 2: who is the message for
* {type : "to_aepp",
* // layer 3: JSON RPC
* data : {jsonrpc : "2.0",
* method : "connection.announcePresence",
* // layer 4: AWCP-specific semantics
* params : {id : "{aee9e933-52b6-410a-8c3f-99c6be596b4e}",
* name : "Superhero",
* networkId : "ae_mainnet",
@@ -256,10 +322,13 @@ type EventData_W2A
*
* @example
* ```ts
* // layer 2: who is the message for
* {type : "to_waellet",
* // layer 3: json rpc
* data : {jsonrpc : "2.0",
* id : "ske-connect-1",
* method : "connection.open",
* // layer 4: AWCP-specific semantics
* params : {name : "sidekick examples",
* version : 1}}}
* ```
@@ -370,6 +439,8 @@ const ERROR_CODE_RpcMethodNotFoundError = -32601;
/**
* Error data inside the `error` field of a `RpcResp_Err`
*
* (layer 3)
*
* @example
* ```ts
* {code : 4,
@@ -394,10 +465,14 @@ type RpcError
* the server that do not need a response. An example is the wallet announcing
* it exists.
*
* (layer 3)
*
* @example
* ```ts
* //layer 3: JSON RPC
* {jsonrpc : "2.0",
* method : "connection.announcePresence",
* // layer 4: AWCP-specific semantics
* params : {id : "{aee9e933-52b6-410a-8c3f-99c6be596b4e}",
* name : "Superhero",
* networkId : "ae_mainnet",
@@ -413,10 +488,22 @@ type RpcCast
params : params_t};
/**
* This type is used for requests from the client to the server that require a
* response.
*
* (layer 3)
*
* @example
* ```ts
* // layer 3: json rpc
* {jsonrpc : "2.0",
* id : "ske-connect-1",
* method : "connection.open",
* // layer 4: AWCP-specific semantics
* params : {name : "sidekick examples",
* version : 1}}}
* ```
*/
type RpcCall
<method_s extends string,
@@ -435,6 +522,24 @@ type RpcCall
/**
* This is the shape of unsuccessful responses
*
* @example
* From a page script, using sidekick, I asked for the user's address.
* Superhero popped up the little dialog thing asking if I wanted to connect,
* and I hit "deny". This is what was sent back. Code `4` corresponds to
* {@link ERROR_CODE_RpcRejectedByUserError}.
*
* @example
* ```ts
* // layer 3: RPC
* {jsonrpc : "2.0",
* id : "ske-address-1",
* method : "address.subscribe",
* // layer 4: AWCP semantics
* error : {code : 4,
* data : {},
* message : "Operation rejected by user"}}
* ```
*/
type RpcResp_error
<method_s extends string>
@@ -447,6 +552,20 @@ type RpcResp_error
/**
* This is the shape of successful responses
*
* @example
* ```ts
* // layer 3: RPC
* {jsonrpc : "2.0",
* id : "ske-connect-1",
* method : "connection.open",
* // layer 4: AWCP-specific semantics
* result : {id : "mnhmmkepfddpifjkamaligfeemcbhdne",
* name : "Superhero",
* networkId : "ae_mainnet",
* origin : "chrome-extension://mnhmmkepfddpifjkamaligfeemcbhdne",
* type : "extension"}}
* ```
*/
type RpcResp_ok
<method_s extends string,
@@ -460,6 +579,34 @@ type RpcResp_ok
/**
* This is the shape of generic responses
*
* @example
* Successful response ({@link RpcResp_ok})
* ```ts
* // layer 3: RPC
* {jsonrpc : "2.0",
* id : "ske-connect-1",
* method : "connection.open",
* // layer 4: AWCP-specific semantics
* result : {id : "mnhmmkepfddpifjkamaligfeemcbhdne",
* name : "Superhero",
* networkId : "ae_mainnet",
* origin : "chrome-extension://mnhmmkepfddpifjkamaligfeemcbhdne",
* type : "extension"}}
* ```
*
* @example
* Unsuccessful response ({@link RpcResp_error})
* ```ts
* // layer 3: RPC
* {jsonrpc : "2.0",
* id : "ske-address-1",
* method : "address.subscribe",
* // layer 4: AWCP semantics
* error : {code : 4,
* data : {},
* message : "Operation rejected by user"}}
* ```
*/
type RpcResp
<method_s extends string,
@@ -471,6 +618,34 @@ type RpcResp
/**
* Most generic possible response
*
* @example
* Successful response ({@link RpcResp_ok})
* ```ts
* // layer 3: RPC
* {jsonrpc : "2.0",
* id : "ske-connect-1",
* method : "connection.open",
* // layer 4: AWCP-specific semantics
* result : {id : "mnhmmkepfddpifjkamaligfeemcbhdne",
* name : "Superhero",
* networkId : "ae_mainnet",
* origin : "chrome-extension://mnhmmkepfddpifjkamaligfeemcbhdne",
* type : "extension"}}
* ```
*
* @example
* Unsuccessful response ({@link RpcResp_error})
* ```ts
* // layer 3: RPC
* {jsonrpc : "2.0",
* id : "ske-address-1",
* method : "address.subscribe",
* // layer 4: AWCP semantics
* error : {code : 4,
* data : {},
* message : "Operation rejected by user"}}
* ```
*/
type RpcResp_Any = RpcResp<string, any>;
@@ -487,9 +662,6 @@ type RpcResp_Any = RpcResp<string, any>;
// use which I did not encounter in the wild.
//=============================================================================
// TODO: need to do more experimentation with superhero to see what sorts of
// messages it sends back to the other requests
//----------------------------------------------------------------------------
// connection.announcePresence
//----------------------------------------------------------------------------
@@ -568,6 +740,12 @@ type EventData_W2A_connection_announcePresence
* Parameters of aepp-to-waellet "connection.open" call
*
* (layer 4)
*
* @example
* ```ts
* {name : "sidekick examples",
* version : 1}
* ```
*/
type Params_A2W_connection_open
= {name : string,
@@ -581,6 +759,15 @@ type Params_A2W_connection_open
* Same as `Params_W2A_connection_open` empirically
*
* (layer 4)
*
* @example
* ```ts
* {id : "{aee9e933-52b6-410a-8c3f-99c6be596b4e}",
* name : "Superhero",
* networkId : "ae_uat",
* origin : "moz-extension://ee425d81-d5b2-44b6-9406-4da31b019e7c",
* type : "extension"}
* ```
*/
type Result_W2A_connection_open
= Params_W2A_connection_announcePresence;
@@ -591,6 +778,16 @@ type Result_W2A_connection_open
* Shape of aepp-to-waellet "connection.open" RPC call
*
* (layer 3)
* @example
* ```ts
* // layer 3: json rpc
* {jsonrpc : "2.0",
* id : "ske-connect-1",
* method : "connection.open",
* // layer 4: AWCP-specific semantics
* params : {name : "sidekick examples",
* version : 1}}}
* ```
*/
type RpcCall_A2W_connection_open
= RpcCall<"connection.open",
@@ -602,6 +799,22 @@ type RpcCall_A2W_connection_open
* Shape of waellet-to-aepp "connection.open" RPC response
*
* (layer 3)
*
* @example
* ```json
* {
* "jsonrpc": "2.0",
* "id": "ske-connect-1",
* "method": "connection.open",
* "result": {
* "id": "mnhmmkepfddpifjkamaligfeemcbhdne",
* "name": "Superhero",
* "networkId": "ae_mainnet",
* "origin": "chrome-extension://mnhmmkepfddpifjkamaligfeemcbhdne",
* "type": "extension"
* }
* }
* ```
*/
type RpcResp_W2A_connection_open
= RpcResp<"connection.open",
@@ -613,6 +826,22 @@ type RpcResp_W2A_connection_open
* The actual aepp-to-waellet "connection.open" event data passed over the message bus
*
* (layer 2)
*
* @example
* ```json
* {
* "type": "to_waellet",
* "data": {
* "jsonrpc": "2.0",
* "id": "ske-connect-1",
* "method": "connection.open",
* "params": {
* "name": "sidekick examples",
* "version": 1
* }
* }
* }
* ```
*/
type EventData_A2W_connection_open
= EventData_A2W<RpcCall_A2W_connection_open>;
@@ -623,6 +852,24 @@ type EventData_A2W_connection_open
* The actual waellet-to-aepp "connection.open" event data passed over the message bus
*
* (layer 2)
*
* @example
* ```json
* {
* "type": "to_aepp",
* "data": {
* "jsonrpc": "2.0",
* "id": "ske-connect-1",
* "method": "connection.open",
* "result": {
* "id": "mnhmmkepfddpifjkamaligfeemcbhdne",
* "name": "Superhero",
* "networkId": "ae_mainnet",
* "origin": "chrome-extension://mnhmmkepfddpifjkamaligfeemcbhdne",
* "type": "extension"
* }
* }
* }
*/
type EventData_W2A_connection_open
= EventData_W2A<RpcResp_W2A_connection_open>;
@@ -638,6 +885,14 @@ type EventData_W2A_connection_open
* Parameter type of aepp-to-waellet "address.subscribe" call
*
* (layer 4)
*
* @example
* ```json
* {
* "type": "subscribe",
* "value": "connected"
* }
* ```
*/
type Params_A2W_address_subscribe
= {type : "subscribe",
@@ -651,11 +906,33 @@ type Params_A2W_address_subscribe
* (layer 4)
*
* @example
* This is if the user only has a single keypair
* ```typescript
* {subscription : ["connected"],
* address : {current : {"ak_2Wsa8iAmAm917evwDEZjouvPUXKx2nUv5Uz8e8oNXTDfDXnMRN": {}},
* connected : {}}}
* ```
*
* @example
* This is if the user has many keypairs. The currently selected one is under
* `current`. Craig, I agree this is stupid, but that's how it works.
* ```json
* {
* "subscription": [
* "connected"
* ],
* "address": {
* "current": {
* "ak_25C3xaAGQddyKAnaLLMjAhX24xMktH2NNZxY3fMaZQLMGED2Nf": {}
* },
* "connected": {
* "ak_BMtPGuqDhWLnMVL4t6VFfS32y2hd8TSYwiYa2Z3VdmGzgNtJP": {},
* "ak_25BqQuiVCasiqTkXHEffq7XCsuYEtgjNeZFeVFbuRtJkfC9NyX": {},
* "ak_4p6gGoCcwQzLXd88KhdjRWYgd4MfTsaCeD8f99pzZhJ6vzYYV": {}
* }
* }
* }
* ```
*/
type Result_W2A_address_subscribe
= {subscription : ["connected"],
@@ -668,6 +945,19 @@ type Result_W2A_address_subscribe
* Shape of aepp-to-waellet "address.subscribe" RPC call
*
* (layer 3)
*
* @example
* ```json
* {
* "jsonrpc": "2.0",
* "id": "ske-address-1",
* "method": "address.subscribe",
* "params": {
* "type": "subscribe",
* "value": "connected"
* }
* }
* ```
*/
type RpcCall_A2W_address_subscribe
= RpcCall<"address.subscribe",
@@ -679,6 +969,52 @@ type RpcCall_A2W_address_subscribe
* Result of waellet-to-aepp "address.subscribe" response
*
* (layer 3)
*
* @example
* Case where the wallet only has one keypair
* ```json
* {
* "jsonrpc": "2.0",
* "id": "ske-address-1",
* "method": "address.subscribe",
* "result": {
* "subscription": [
* "connected"
* ],
* "address": {
* "current": {
* "ak_BMtPGuqDhWLnMVL4t6VFfS32y2hd8TSYwiYa2Z3VdmGzgNtJP": {}
* },
* "connected": {}
* }
* }
* }
* ```
*
* @example
* Case of many keypairs
* ```json
* {
* "jsonrpc": "2.0",
* "id": "ske-address-1",
* "method": "address.subscribe",
* "result": {
* "subscription": [
* "connected"
* ],
* "address": {
* "current": {
* "ak_25C3xaAGQddyKAnaLLMjAhX24xMktH2NNZxY3fMaZQLMGED2Nf": {}
* },
* "connected": {
* "ak_BMtPGuqDhWLnMVL4t6VFfS32y2hd8TSYwiYa2Z3VdmGzgNtJP": {},
* "ak_25BqQuiVCasiqTkXHEffq7XCsuYEtgjNeZFeVFbuRtJkfC9NyX": {},
* "ak_4p6gGoCcwQzLXd88KhdjRWYgd4MfTsaCeD8f99pzZhJ6vzYYV": {}
* }
* }
* }
* }
* ```
*/
type RpcResp_W2A_address_subscribe
= RpcResp<"address.subscribe",
@@ -690,6 +1026,22 @@ type RpcResp_W2A_address_subscribe
* Actual aepp-to-waellet "address.subscribe" event data sent over the message bus
*
* (layer 2)
*
* @example
* ```json
* {
* "type": "to_waellet",
* "data": {
* "jsonrpc": "2.0",
* "id": "ske-address-1",
* "method": "address.subscribe",
* "params": {
* "type": "subscribe",
* "value": "connected"
* }
* }
* }
* ```
*/
type EventData_A2W_address_subscribe
= EventData_A2W<RpcCall_A2W_address_subscribe>;
@@ -700,6 +1052,30 @@ type EventData_A2W_address_subscribe
* Actual waellet-to-aepp "address.subscribe" event data sent over the message bus
*
* (layer 2)
*
* @example
* This is the case where the wallet only has one keypair:
* ```json
* {
* "type": "to_aepp",
* "data": {
* "jsonrpc": "2.0",
* "id": "ske-address-1",
* "method": "address.subscribe",
* "result": {
* "subscription": [
* "connected"
* ],
* "address": {
* "current": {
* "ak_BMtPGuqDhWLnMVL4t6VFfS32y2hd8TSYwiYa2Z3VdmGzgNtJP": {}
* },
* "connected": {}
* }
* }
* }
* }
* ```
*/
type EventData_W2A_address_subscribe
= EventData_W2A<RpcResp_W2A_address_subscribe>;
@@ -713,6 +1089,8 @@ type EventData_W2A_address_subscribe
/**
* Parameters for "transaction.sign" (do not propagate)
*
* If `returnSigned` is `false`, then Superhero will propagate the transaction.
*
* (layer 4)
*/
type Params_A2W_tx_sign_noprop
@@ -809,7 +1187,7 @@ type Result_W2A_msg_sign
* (layer 3)
*/
type RpcCall_A2W_msg_sign
= RpcCall<"message.sign"
= RpcCall<"message.sign",
Params_A2W_msg_sign>;
+1 -71
View File
@@ -1,73 +1,3 @@
# Sidekick
Sidekick is a simple JavaScript library for talking to an Aeternity
browser wallet extension such as Jaeck Russell or Superhero from the document
context of a webpage.
# Build Prereqs
Assuming Ubuntu 18.04. Adapt these instructions for your own system.
You need
- `npm` to build TypeScript (and TypeDoc if you want to build the
documentation)
- `tsc` to compile
- [jex](../utils/jex/) to facilitate the build
Steps:
sudo snap refresh
sudo snap install node --channel 18/stable
npm install -g typescript
## Protip: avoid using `sudo npm install -g`
If you want to avoid using sudo
```
mkdir ~/.npm-packages
npm config set prefix "${HOME}/.npm-packages"
```
Edit `~/.bashrc` or `~/.zshrc` with
```
NPM_PACKAGES="${HOME}/.npm-packages"
export PATH=$NPM_PACKAGES/bin:$PATH
```
# Build Steps
## 1. Build dependencies
The examples require `parasite` as a dependency, but sidekick itself does not.
```
~/src/vanillae $ cd libs/awcp
~/src/vanillae/libs/awcp $ jex dwim+
~/src/vanillae/libs/awcp $ cd ../parasite
~/src/vanillae/libs/parasite $ jex dwim+
```
## 2. Build sidekick
```
~/src/vanillae/libs/parasite $ cd ../../sidekick
~/src/vanillae/sidekick $ jex dwim+
```
## 3. Build examples
Note the `-`, not the `+`. The difference is that `-` just builds the project,
but does not package it.
```
~/src/vanillae/sidekick $ cd examples
~/src/vanillae/sidekick/examples $ jex dwim-
~/src/vanillae/sidekick/examples $ python3 -m http.server 8000
```
Navigate to `http://localhost:8000/` in your browser to see the examples
The examples are the best documentation, for now.
Click "sidekick" in the sidebar or hamburger menu
+1 -1
View File
@@ -2,4 +2,4 @@
{realm, local}.
{name, sidekick}.
{version, "0.2.0"}.
{deps, ["local-awcp-0.1.0"]}.
{deps, ["local-awcp-0.2.0"]}.
+16 -109
View File
@@ -1,124 +1,31 @@
// tomorrow:
// message signing
// examples
// documentation
// project organization
/**
* # How to use this library
* # tl;dr
*
* This is a library for communicating with a browser wallet extension such as
* Superhero
* 1. {@link detect} the wallet
* 2. {@link connect} to the wallet
* 3. Get the wallet's {@link address}
*
* ## Step 0: Include `sidekick`
* From there you can do one of two things
*
* ```
* import * as sk from './path/to/sidekick.js';
* ```
* 1. Have the wallet sign transactions ({@link tx_sign_noprop})
* 2. Have the wallet sign arbitrary messages ({@link msg_sign})
*
* ## Step 1: Make a `Logger`
*
* All of the entrypoints in sidekick require passing in a `Logger`. The idea
* is that you can pass in custom logging hooks to log potential errors.
*
* There are two built-in loggers exported by this module:
*
* 1. `let my_logger = sk.wsl();`: does nothing
* 2. `let my_logger = sk.cl();`: console logger
* 3. `let my_logger = new sk.HttpLogger('https://foo.bar/baz')`: sends JSON to
* the given endpoint in a POST request, in the following form
*
* ```
* {level : 'debug' | 'info' | 'warning' | 'error',
* message : string,
* data : object}
* ```
* 4. `let my_logger = new sk.SeqLogger([my_logger1, my_logger2]);`: a helper
* for composing several loggers sequentially.
* 5. You can define anything that satisfies the `Logger` interface and pass
* that in instead.
*
* ```
* interface Logger {
* debug : (message : string, data : object) => Promise<void>;
* info : (message : string, data : object) => Promise<void>;
* warning : (message : string, data : object) => Promise<void>;
* error : (message : string, data : object) => Promise<void>;
* }
* ```
*
* ## Step 2: Detect the wallet
*
* ```
* // timeout error message logger
* let maybe_detected = await sk.detect(sk.TIMEOUT_DEF_DETECT, 'detect: timeout', my_logger);
* ```
*
* Function:
*
* ```
* async function
* detect
* (timeout_ms : number,
* timeout_msg : string,
* logger : Logger)
* : Promise<Safe<awcp.Params_W2A_connection_announcePresence, SkTimeoutError>>
* ```
*
* This returns some garbage that doesn't matter in a `Safe` type.
* The `Safe` type does matter
*
* ```
* type Safe<ok_t, err_t>
* = Ok<ok_t>
* | Error<err_t>;
*
* type Ok<ok_t>
* = {ok : true,
* result : ok_t};
*
* type Error<err_t>
* = {ok : false,
* error : err_t};
* ```
*
* The motivation here is that when talking to the wallet, there are many
* possible sources of errors. For instance, if you ask the wallet to sign a
* transaction, the transaction might be malformed, maybe the user declines,
* maybe it times out, whatever. All you care about is "did it work?" and you
* don't want to deal with try/catch bullshit.
*
* The most straightforward way to extract the return value is with branching:
*
* ```
* if (maybe_detected.ok) {
* // ok is true in this case, so the field `result` exists
* let awcp_crap = maybe_detected.result;
* }
* else {
* // ok is false in this case, so the field `error` exists
* let the_error = maybe_detected.error;
* }
* ```
*
* ## Step 3: Connect to the wallet
*
*
*
* ## Step 4: Get the user address
*
* ## Step 5: Sign a transaction
* Forming the transactions and propagating them into the network is your
* problem.
*
* You need a {@link Logger} for most calls. Probably you want {@link cl}. You
* can write your own if you want but why would you complicate your life like
* that.
*
* @module
*/
// TODONE: add standardized logging interface
// TODONE: logging hooks
// TODO: invoice
// TODO: get connect done
// TODO: make the message queue for responses a map, fill the message queue properly
// TODO: get it working with superhero
// TODONE: invoice
// TODONE: get connect done
// TODONE: make the message queue for responses a map, fill the message queue properly
// TODONE: get it working with superhero
// TODO: jrx
// like: console, http, etc
+9 -2
View File
@@ -38,6 +38,8 @@ help() ->
% TODONE: jex fulldist
% TODONE: jex install
% TODO: jex get_mindist PKG
% TODO: jex dwim++ = install
% TODO: --name option for docs
% TODO: use less than full qualified names (not priority)
% TODO: make fulldist for arbitrary installed package (requires storing jex.eterms, not hard but also not a priority)
@@ -55,8 +57,9 @@ help_screen() ->
" dwim- build project but don't make a release (init, pull, build)\n"
" dwim+ build and make a minimal release (init, pull, build, mindist, push)\n"
" dwim++ build and make a full release (init, pull, build, mindist, push, mkdocs, pushdocs)\n"
" ls list installed packages\n"
" install synonym for dwim++\n"
" install [TARBALL_PATH] install the given package\n"
" ls list installed packages\n"
" viewdocs [PKG [PORT]] view package docs for PKG in browser\n"
" get_mindist [PKG] get the mindist tarball for an installed package\n"
"\n"
@@ -99,8 +102,9 @@ help_screen() ->
dispatch(["dwim-"]) -> dwim(minus);
dispatch(["dwim+"]) -> dwim(plus);
dispatch(["dwim++"]) -> dwim(plus_plus);
dispatch(["ls"]) -> ls();
dispatch(["install"]) -> install();
dispatch(["install", Path]) -> install(Path);
dispatch(["ls"]) -> ls();
dispatch(["viewdocs"]) -> viewdocs();
dispatch(["viewdocs", Pkg]) -> viewdocs(Pkg);
dispatch(["viewdocs", Pkg, Port]) -> viewdocs(Pkg, Port);
@@ -567,6 +571,9 @@ srsly_readme_path() ->
%% jex install TARBALL_PATH
%%-----------------------------------------------------------------------------
install() ->
dwim(plus_plus).
install(TarballPath) ->
case file_exists(TarballPath) of
false -> error({file_dne, TarballPath});