Pharpend/develop (#1)
* move stuff in here * reorganizing because zx needs to feel special * add base58/base64 explainer draft
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
*.swp
|
||||
*.swo
|
||||
*.beam
|
||||
dist_js
|
||||
sidekick_dist
|
||||
docs
|
||||
examples/examples_dist_js
|
||||
www_tree
|
||||
prose
|
||||
*.hi
|
||||
*.o
|
||||
sidekick_mindist
|
||||
sidekick_fulldist
|
||||
dist
|
||||
jx_mindist
|
||||
*jx_include*
|
||||
jex_mindist
|
||||
erl_crash.dump
|
||||
@@ -0,0 +1,16 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2022 Peter Harpending
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
all: prepare build
|
||||
|
||||
deploy: prepare build mindist push
|
||||
|
||||
prepare:
|
||||
jx pull -f
|
||||
|
||||
build:
|
||||
jx build -f
|
||||
|
||||
mindist:
|
||||
jx mindist -f
|
||||
|
||||
push:
|
||||
jx push -f
|
||||
@@ -0,0 +1,490 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{type, library}.
|
||||
{realm, local}.
|
||||
{name, awcp}.
|
||||
{version, "0.1.0"}.
|
||||
{deps, []}.
|
||||
@@ -0,0 +1,724 @@
|
||||
/**
|
||||
* # AWCP: aepp-waellet communication protocol
|
||||
*
|
||||
* Suppose you are the aepp and you want to communicate with a waellet. What
|
||||
* you do is pick an `EventTarget` (typically `window`), and listen to its
|
||||
* `MessageEvent`s, via something like
|
||||
*
|
||||
* ```typescript
|
||||
* window.addEventListener('message', my_listener);
|
||||
* ```
|
||||
*
|
||||
* You then communicate with the wallet by sending messages back over the
|
||||
* `EventTarget`
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* The `MessageEvent` has a field called `data`, which corresponds to the
|
||||
* next layer.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* This layer corresponds to the "am I supposed to pay attention to this
|
||||
* event?" branch point.
|
||||
*
|
||||
* Those data structures mentioned above have two fields.
|
||||
*
|
||||
* 1. `type` is a string which is either `"to_aepp"` or `"to_waellet"`
|
||||
* 2. `data` contains the next layer
|
||||
*
|
||||
* ```typescript
|
||||
* type EventData_W2A
|
||||
* <t extends any>
|
||||
* = {type : "to_aepp",
|
||||
* data : t};
|
||||
* ```
|
||||
*
|
||||
* ``` typescript
|
||||
* type EventData_A2W
|
||||
* <t extends any>
|
||||
* = {type : "to_waellet",
|
||||
* data : t};
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* 3. We're at `message_event.data.data`. The idiom here is "JSON RPC", which
|
||||
* is sort of a poor man's HTTP.
|
||||
*
|
||||
* In general, the wallet is the server and the aepp is the client.
|
||||
*
|
||||
* If you are the aepp, usually you are handling a response to a request
|
||||
* you sent to the waellet. For instance, you formed a transaction and
|
||||
* sent it to the waellet to sign, and the waellet is sending you back
|
||||
* either the signed transaction or an error (e.g. user rejected the
|
||||
* transaction).
|
||||
*
|
||||
* The exception to this pattern is the wallet notifying you that it
|
||||
* exists, which is the only time the waellet sends a request (a "cast", or
|
||||
* a "notification") to the aepp. __In no event does the aepp send a
|
||||
* response to the waellet.__
|
||||
*
|
||||
* I am not 100% sure what RPC stands for, but it will be helpful to think
|
||||
* about it as "remote procedure call". More below. This layer roughly
|
||||
* corresponds to the "given that I am supposed to pay attention to this
|
||||
* event, what am I supposed to do with this information?"
|
||||
*
|
||||
* All requests have a `method` field (a string) and a `params` field (an
|
||||
* object). There are two types of requests:
|
||||
*
|
||||
* 1. "casts" (the RPC standard calls these "notifications"). These do not
|
||||
* need a response. This is only used for the waellet announcing it
|
||||
* exists.
|
||||
*
|
||||
* 2. "calls". These have an `id` field, and get a response. These are
|
||||
* used when the aepp is requesting the waellet to do something. The
|
||||
* response will have the same `id` field and the same `method` field.
|
||||
*
|
||||
* 4. So far nothing we've talked about is specific to Aeternity, Vanillae,
|
||||
* JR, or sidekick. This fourth layer is the actual semantics of the
|
||||
* messaging protocol between the aepp and the waellet.
|
||||
*
|
||||
* By analogy, the first two layers are developing something like TCP. The
|
||||
* third layer is developing HTTP. And this layer is the actual routing
|
||||
* table of your website, which carries with it the expected semantics of
|
||||
* how the website is supposed to behave.
|
||||
*
|
||||
* This module DOES __NOT__ exhaustively define all of the communication
|
||||
* protocol that occurs in the SDK, only the subset that I have encountered
|
||||
* in practice.
|
||||
*
|
||||
* The first layer is defined by the runtime, not here. So we're starting with
|
||||
* layer 2.
|
||||
*
|
||||
* # Notes on JSON RPC 2.0
|
||||
*
|
||||
* I have subtly changed the RPC protocol to
|
||||
*
|
||||
* 1. improve it in such a way that it is easier to use in code
|
||||
* 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).
|
||||
*
|
||||
* ## Requests
|
||||
*
|
||||
* I have adapted the verbiage here, borrowing from Erlang, to make a
|
||||
* distinction between
|
||||
*
|
||||
* 1. casts (RPC calls these "notifications"): these
|
||||
*
|
||||
* 1. do __NOT__ have an `id` field
|
||||
* 2. __AND__ do __NOT__ require a response.
|
||||
*
|
||||
* 2. calls: these
|
||||
*
|
||||
* 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 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
|
||||
* have `id = 7`.
|
||||
*
|
||||
* # Links
|
||||
*
|
||||
* - `MessageEvent`s: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
|
||||
* - JSON RPC 2.0 https://www.jsonrpc.org/specification
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
// TODO: TS code style guide
|
||||
// TODO: annotate everything with examples
|
||||
// TODONE: enumerate RPC errors
|
||||
// TODO: move Safe in here
|
||||
// TODO: constants for method names
|
||||
|
||||
|
||||
//=============================================================================
|
||||
// IMPORTS
|
||||
//=============================================================================
|
||||
|
||||
//import type {
|
||||
// ERROR_TYPE_RpcInvalidTransactionError,
|
||||
// ERROR_TYPE_RpcBroadcastError,
|
||||
// ERROR_TYPE_RpcRejectedByUserError,
|
||||
// ERROR_TYPE_RpcUnsupportedProtocolError,
|
||||
// ERROR_TYPE_RpcConnectionDenyError,
|
||||
// ERROR_TYPE_RpcNotAuthorizeError,
|
||||
// ERROR_TYPE_RpcPermissionDenyError,
|
||||
// ERROR_TYPE_RpcInternalError,
|
||||
// ERROR_TYPE_RpcMethodNotFoundError,
|
||||
//} from './errcode.js';
|
||||
|
||||
|
||||
|
||||
|
||||
//=============================================================================
|
||||
// EXPORTS
|
||||
//=============================================================================
|
||||
|
||||
|
||||
export {
|
||||
// error code constants
|
||||
ERROR_CODE_RpcInvalidTransactionError,
|
||||
ERROR_CODE_RpcBroadcastError,
|
||||
ERROR_CODE_RpcRejectedByUserError,
|
||||
ERROR_CODE_RpcUnsupportedProtocolError,
|
||||
ERROR_CODE_RpcConnectionDenyError,
|
||||
ERROR_CODE_RpcNotAuthorizeError,
|
||||
ERROR_CODE_RpcPermissionDenyError,
|
||||
ERROR_CODE_RpcInternalError,
|
||||
ERROR_CODE_RpcMethodNotFoundError,
|
||||
// Layer 2: events
|
||||
EventData_W2A,
|
||||
EventData_A2W,
|
||||
// Layer 3: RPC
|
||||
RpcError,
|
||||
RpcCast,
|
||||
RpcCall,
|
||||
RpcResp_error,
|
||||
RpcResp_ok,
|
||||
RpcResp,
|
||||
RpcResp_Any,
|
||||
// Layer 4: specific semantics
|
||||
// connection.announcePresence
|
||||
Params_W2A_connection_announcePresence,
|
||||
RpcCast_W2A_connection_announcePresence,
|
||||
EventData_W2A_connection_announcePresence,
|
||||
// connection.open
|
||||
Params_A2W_connection_open,
|
||||
Result_W2A_connection_open,
|
||||
RpcCall_A2W_connection_open,
|
||||
RpcResp_W2A_connection_open,
|
||||
EventData_A2W_connection_open,
|
||||
EventData_W2A_connection_open,
|
||||
// address.subscribe
|
||||
Params_A2W_address_subscribe,
|
||||
Result_W2A_address_subscribe,
|
||||
RpcCall_A2W_address_subscribe,
|
||||
RpcResp_W2A_address_subscribe,
|
||||
EventData_A2W_address_subscribe,
|
||||
EventData_W2A_address_subscribe,
|
||||
//// transaction.sign (propagate)
|
||||
//Params_A2W_tx_sign_yesprop,
|
||||
//Result_W2A_tx_sign_yesprop,
|
||||
//RpcCall_A2W_tx_sign_yesprop,
|
||||
//RpcResp_W2A_tx_sign_yesprop,
|
||||
//EventData_A2W_tx_sign_yesprop,
|
||||
//EventData_W2A_tx_sign_yesprop,
|
||||
// transaction.sign (do not propagate)
|
||||
Params_A2W_tx_sign_noprop,
|
||||
Result_W2A_tx_sign_noprop,
|
||||
RpcCall_A2W_tx_sign_noprop,
|
||||
RpcResp_W2A_tx_sign_noprop,
|
||||
EventData_A2W_tx_sign_noprop,
|
||||
EventData_W2A_tx_sign_noprop
|
||||
};
|
||||
|
||||
|
||||
|
||||
//=============================================================================
|
||||
// LAYER 2: WHO IS THIS MESSAGE FOR
|
||||
//
|
||||
// The first layer is defined by the runtime, not here. So we're starting with
|
||||
// layer 2.
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* This is the data that is sent from the wallet to the aepp through the Event
|
||||
* bus
|
||||
*/
|
||||
type EventData_W2A
|
||||
<t extends any>
|
||||
= {type : "to_aepp",
|
||||
data : t};
|
||||
|
||||
|
||||
/**
|
||||
* This is the data that is sent from the aepp to the wallet through the Event
|
||||
* bus
|
||||
*/
|
||||
type EventData_A2W
|
||||
<t extends any>
|
||||
= {type : "to_waellet",
|
||||
data : t};
|
||||
|
||||
|
||||
|
||||
|
||||
//=============================================================================
|
||||
// LAYER 3: JSON RPC 2.0
|
||||
//
|
||||
// It should be noted in general that the id field exists as a type parameter
|
||||
// so that we can use the type system to denote ID matches in callbacks.
|
||||
//
|
||||
// Remember, in TypeScript's type system, values are valid types.
|
||||
//
|
||||
// So `(_arg0: Foo<3, string>) => Bar<3, number>` is a valid type
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* `const ERROR_CODE_RpcInvalidTransactionError = 2;`
|
||||
*
|
||||
* See https://github.com/aeternity/aepp-sdk-js/blob/1065da9a46b8dbfe60a2c3e5646e7422ee7e495e/src/aepp-wallet-communication/schema.ts#L92
|
||||
*/
|
||||
const ERROR_CODE_RpcInvalidTransactionError = 2;
|
||||
|
||||
/**
|
||||
* `const ERROR_CODE_RpcBroadcastError = 3;`
|
||||
*
|
||||
* See
|
||||
* https://github.com/aeternity/aepp-sdk-js/blob/1065da9a46b8dbfe60a2c3e5646e7422ee7e495e/src/aepp-wallet-communication/schema.ts#L108
|
||||
*/
|
||||
const ERROR_CODE_RpcBroadcastError = 3;
|
||||
|
||||
/**
|
||||
* `const ERROR_CODE_RpcRejectedByUserError = 4;`
|
||||
*
|
||||
* See https://github.com/aeternity/aepp-sdk-js/blob/1065da9a46b8dbfe60a2c3e5646e7422ee7e495e/src/aepp-wallet-communication/schema.ts#L122
|
||||
*/
|
||||
const ERROR_CODE_RpcRejectedByUserError = 4;
|
||||
|
||||
|
||||
/**
|
||||
* `const ERROR_CODE_RpcUnsupportedProtocolError = 5;`
|
||||
*
|
||||
* See https://github.com/aeternity/aepp-sdk-js/blob/1065da9a46b8dbfe60a2c3e5646e7422ee7e495e/src/aepp-wallet-communication/schema.ts#L140
|
||||
*/
|
||||
const ERROR_CODE_RpcUnsupportedProtocolError = 5;
|
||||
|
||||
|
||||
/**
|
||||
* This error occurs when the user rejects your attempt to connect. (I think)
|
||||
*
|
||||
* The error name here is ungrammatical but following the lead of the SDK.
|
||||
*
|
||||
* `const ERROR_CODE_RpcConnectionDenyError = 9;`
|
||||
*
|
||||
* See https://github.com/aeternity/aepp-sdk-js/blob/1065da9a46b8dbfe60a2c3e5646e7422ee7e495e/src/aepp-wallet-communication/schema.ts#L155
|
||||
*/
|
||||
const ERROR_CODE_RpcConnectionDenyError = 9;
|
||||
|
||||
/**
|
||||
* This error occurs when you are not connected to the wallet.
|
||||
*
|
||||
* The error name here is ungrammatical but following the lead of the SDK.
|
||||
*
|
||||
* `const ERROR_CODE_RpcNotAuthorizeError = 10;`
|
||||
*
|
||||
* See https://github.com/aeternity/aepp-sdk-js/blob/1065da9a46b8dbfe60a2c3e5646e7422ee7e495e/src/aepp-wallet-communication/schema.ts#L171
|
||||
*/
|
||||
const ERROR_CODE_RpcNotAuthorizeError = 10;
|
||||
|
||||
|
||||
/**
|
||||
* This error occurs when you are not `address.subscribe`d to the wallet (I think?)
|
||||
*
|
||||
* The error name here is ungrammatical but following the lead of the SDK.
|
||||
*
|
||||
* `const ERROR_CODE_RpcPermissionDenyError = 11;`
|
||||
*
|
||||
* See https://github.com/aeternity/aepp-sdk-js/blob/1065da9a46b8dbfe60a2c3e5646e7422ee7e495e/src/aepp-wallet-communication/schema.ts#L186
|
||||
*/
|
||||
const ERROR_CODE_RpcPermissionDenyError = 11;
|
||||
|
||||
/**
|
||||
* This is the general "something went wrong, i dunno" negative error.
|
||||
*
|
||||
* See https://github.com/aeternity/aepp-sdk-js/blob/1065da9a46b8dbfe60a2c3e5646e7422ee7e495e/src/aepp-wallet-communication/schema.ts#L196-L209
|
||||
*/
|
||||
const ERROR_CODE_RpcInternalError = 12;
|
||||
|
||||
|
||||
/**
|
||||
* This is presumably the equivalent of the HTTP 404 error
|
||||
*
|
||||
* See https://github.com/aeternity/aepp-sdk-js/blob/1065da9a46b8dbfe60a2c3e5646e7422ee7e495e/src/aepp-wallet-communication/schema.ts#L211-L224
|
||||
*/
|
||||
const ERROR_CODE_RpcMethodNotFoundError = -32601;
|
||||
|
||||
|
||||
/**
|
||||
* Error data inside the `error` field of a RpcResp_Err
|
||||
*/
|
||||
type RpcError
|
||||
= {code : number,
|
||||
message : string,
|
||||
data? : any};
|
||||
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requests
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This type is used for "notifications", i.e. messages sent from the client to
|
||||
* the server that do not need a response. An example is the wallet announcing
|
||||
* it exists.
|
||||
*/
|
||||
type RpcCast
|
||||
<method_s extends string,
|
||||
params_t extends object>
|
||||
= {jsonrpc : "2.0",
|
||||
method : method_s,
|
||||
params : params_t};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This type is used for requests from the client to the server that require a
|
||||
* response.
|
||||
*/
|
||||
type RpcCall
|
||||
<method_s extends string,
|
||||
params_t extends object>
|
||||
= {jsonrpc : "2.0",
|
||||
id : number | string,
|
||||
method : method_s,
|
||||
params : params_t};
|
||||
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Responses
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This is the shape of unsuccessful responses
|
||||
*/
|
||||
type RpcResp_error
|
||||
<method_s extends string>
|
||||
= {jsonrpc : "2.0",
|
||||
id : number | string,
|
||||
method : method_s,
|
||||
error : RpcError};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This is the shape of successful responses
|
||||
*/
|
||||
type RpcResp_ok
|
||||
<method_s extends string,
|
||||
result_t extends any>
|
||||
= {jsonrpc : "2.0",
|
||||
id : number | string,
|
||||
method : method_s,
|
||||
result : result_t};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This is the shape of generic responses
|
||||
*/
|
||||
type RpcResp
|
||||
<method_s extends string,
|
||||
result_t extends any>
|
||||
= RpcResp_ok<method_s, result_t>
|
||||
| RpcResp_error<method_s>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Most generic possible response
|
||||
*/
|
||||
type RpcResp_Any = RpcResp<string, any>;
|
||||
|
||||
|
||||
|
||||
|
||||
//=============================================================================
|
||||
// LAYER 4: VANILLAE-SPECIFIC MESSAGE PROTOCOL
|
||||
//
|
||||
// https://github.com/aeternity/aepp-sdk-js/blob/a435e9df5c94004bcd16326b26c38a9c0b284279/src/aepp-wallet-communication/schema.ts#L32-L42
|
||||
//
|
||||
// Only the request/responses that I have actually encountered in practice are
|
||||
// enumerated here. There are many more things that the SDK code appears to
|
||||
// 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
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Waellet-to-aepp parameters of "connection.announcePresence" cast
|
||||
*
|
||||
* (layer 4)
|
||||
*/
|
||||
type Params_W2A_connection_announcePresence
|
||||
= {id : string,
|
||||
name : string,
|
||||
origin : string,
|
||||
type : "window" | "extension"};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Shape of the waellet-to-aepp "connection.announcePresence" RPC cast
|
||||
*
|
||||
* (layer 3)
|
||||
*/
|
||||
type RpcCast_W2A_connection_announcePresence
|
||||
= RpcCast<"connection.announcePresence",
|
||||
Params_W2A_connection_announcePresence>;
|
||||
|
||||
|
||||
/**
|
||||
* The actual waellet-to-aepp event passed when the wallet announces it exists
|
||||
*
|
||||
* (layer 2)
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```json
|
||||
* {
|
||||
* "type": "to_aepp",
|
||||
* "data": {
|
||||
* "jsonrpc": "2.0",
|
||||
* "method": "connection.announcePresence",
|
||||
* "params": {
|
||||
* "id": "{aee9e933-52b6-410a-8c3f-99c6be596b4e}",
|
||||
* "name": "Superhero",
|
||||
* "networkId": "ae_mainnet",
|
||||
* "origin": "moz-extension://ee425d81-d5b2-44b6-9406-4da31b019e7c",
|
||||
* "type": "extension"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
type EventData_W2A_connection_announcePresence
|
||||
= EventData_W2A<RpcCast_W2A_connection_announcePresence>
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// connection.open
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parameters of aepp-to-waellet "connection.open" call
|
||||
*
|
||||
* (layer 4)
|
||||
*/
|
||||
type Params_A2W_connection_open
|
||||
= {name : string,
|
||||
version : 1,
|
||||
networkId? : string};
|
||||
|
||||
|
||||
/**
|
||||
* Result type of "connection.open" call
|
||||
*
|
||||
* Same as `Params_W2A_connection_open` empirically
|
||||
*
|
||||
* (layer 4)
|
||||
*/
|
||||
type Result_W2A_connection_open
|
||||
= Params_W2A_connection_announcePresence;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Shape of aepp-to-waellet "connection.open" RPC call
|
||||
*
|
||||
* (layer 3)
|
||||
*/
|
||||
type RpcCall_A2W_connection_open
|
||||
= RpcCall<"connection.open",
|
||||
Params_A2W_connection_open>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Shape of waellet-to-aepp "connection.open" RPC response
|
||||
*
|
||||
* (layer 3)
|
||||
*/
|
||||
type RpcResp_W2A_connection_open
|
||||
= RpcResp<"connection.open",
|
||||
Result_W2A_connection_open>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The actual aepp-to-waellet "connection.open" event data passed over the message bus
|
||||
*
|
||||
* (layer 2)
|
||||
*/
|
||||
type EventData_A2W_connection_open
|
||||
= EventData_A2W<RpcCall_A2W_connection_open>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The actual waellet-to-aepp "connection.open" event data passed over the message bus
|
||||
*
|
||||
* (layer 2)
|
||||
*/
|
||||
type EventData_W2A_connection_open
|
||||
= EventData_W2A<RpcResp_W2A_connection_open>;
|
||||
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// address.subscribe
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parameter type of aepp-to-waellet "address.subscribe" call
|
||||
*
|
||||
* (layer 4)
|
||||
*/
|
||||
type Params_A2W_address_subscribe
|
||||
= {type : "subscribe",
|
||||
value : "connected"};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Result type of waellet-to-aepp "address.subscribe response
|
||||
*
|
||||
* (layer 4)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* {subscription : ["connected"],
|
||||
* address : {current : {"ak_2Wsa8iAmAm917evwDEZjouvPUXKx2nUv5Uz8e8oNXTDfDXnMRN": {}},
|
||||
* connected : {}}}
|
||||
* ```
|
||||
*/
|
||||
type Result_W2A_address_subscribe
|
||||
= {subscription : ["connected"],
|
||||
address : {current : object,
|
||||
connected : object}};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Shape of aepp-to-waellet "address.subscribe" RPC call
|
||||
*
|
||||
* (layer 3)
|
||||
*/
|
||||
type RpcCall_A2W_address_subscribe
|
||||
= RpcCall<"address.subscribe",
|
||||
Params_A2W_address_subscribe>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Result of waellet-to-aepp "address.subscribe" response
|
||||
*
|
||||
* (layer 3)
|
||||
*/
|
||||
type RpcResp_W2A_address_subscribe
|
||||
= RpcResp<"address.subscribe",
|
||||
Result_W2A_address_subscribe>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Actual aepp-to-waellet "address.subscribe" event data sent over the message bus
|
||||
*
|
||||
* (layer 2)
|
||||
*/
|
||||
type EventData_A2W_address_subscribe
|
||||
= EventData_A2W<RpcCall_A2W_address_subscribe>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Actual waellet-to-aepp "address.subscribe" event data sent over the message bus
|
||||
*
|
||||
* (layer 2)
|
||||
*/
|
||||
type EventData_W2A_address_subscribe
|
||||
= EventData_W2A<RpcResp_W2A_address_subscribe>;
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// transaction.sign (do not propagate)
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parameters for "transaction.sign" (do not propagate)
|
||||
*
|
||||
* (layer 4)
|
||||
*/
|
||||
type Params_A2W_tx_sign_noprop
|
||||
= {tx : string,
|
||||
returnSigned : true,
|
||||
networkId : string}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Success result type for "transaction.sign" (do not propagate)
|
||||
*
|
||||
* (layer 4)
|
||||
*/
|
||||
type Result_W2A_tx_sign_noprop
|
||||
= {signedTransaction : string};
|
||||
|
||||
|
||||
/**
|
||||
* Request type for "transaction.sign" (do not propagate)
|
||||
*
|
||||
* (layer 3)
|
||||
*/
|
||||
type RpcCall_A2W_tx_sign_noprop
|
||||
= RpcCall<"transaction.sign",
|
||||
Params_A2W_tx_sign_noprop>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Response type for "transaction.sign" (do not propagate)
|
||||
*
|
||||
* (layer 3)
|
||||
*/
|
||||
type RpcResp_W2A_tx_sign_noprop
|
||||
= RpcResp<"transaction.sign",
|
||||
Result_W2A_tx_sign_noprop>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Event data for aepp-to-waellet "transaction.sign" (do not propagate) message
|
||||
*
|
||||
* (layer 2)
|
||||
*/
|
||||
type EventData_A2W_tx_sign_noprop
|
||||
= EventData_A2W<RpcCall_A2W_tx_sign_noprop>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Event data for aepp-to-waellet "transaction.sign" (do not propagate) response
|
||||
*
|
||||
* (layer 2)
|
||||
*/
|
||||
type EventData_W2A_tx_sign_noprop
|
||||
= EventData_W2A<RpcResp_W2A_tx_sign_noprop>;
|
||||
@@ -0,0 +1,16 @@
|
||||
{"compilerOptions" : {"target" : "es2022",
|
||||
"strict" : true,
|
||||
"esModuleInterop" : true,
|
||||
"skipLibCheck" : true,
|
||||
"forceConsistentCasingInFileNames" : true,
|
||||
"noImplicitAny" : true,
|
||||
"strictNullChecks" : true,
|
||||
"strictPropertyInitialization" : true,
|
||||
"sourceMap" : true,
|
||||
"outDir" : "dist",
|
||||
"declaration" : true},
|
||||
"$schema" : "https://json.schemastore.org/tsconfig",
|
||||
"display" : "Recommended",
|
||||
"include" : ["src/**/*"],
|
||||
"exclude" : ["src/jx_include"],
|
||||
"composite" : true}
|
||||
@@ -0,0 +1,6 @@
|
||||
*.swp
|
||||
*.swo
|
||||
jex_mindist
|
||||
dist
|
||||
src/jex_include
|
||||
erl_crash.dump
|
||||
@@ -0,0 +1,16 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2022 Peter Harpending
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
all: pull build
|
||||
deploy: pull build mindist push
|
||||
|
||||
pull:
|
||||
jx pull -f
|
||||
|
||||
build:
|
||||
jx build -f
|
||||
|
||||
mindist:
|
||||
jx mindist -f
|
||||
|
||||
push:
|
||||
jx push -f
|
||||
@@ -0,0 +1,5 @@
|
||||
{type, library}.
|
||||
{realm, local}.
|
||||
{name, parasite}.
|
||||
{version, "0.1.0"}.
|
||||
{deps, []}.
|
||||
@@ -0,0 +1,96 @@
|
||||
/*******************************************************************
|
||||
** Compiler API
|
||||
**
|
||||
** The manner in which this module is laid out differs meaningfully
|
||||
** from the `ae_node` module.
|
||||
**
|
||||
** - ae_node basically exposes a subset of the node interface as
|
||||
** functions. It only black-boxes away the networking aspects.
|
||||
**
|
||||
** - part of the reason for this is that the data structures that get
|
||||
** sent to the node tend to be pretty involved (lots of fields)
|
||||
**
|
||||
** - in general, the node interface is significantly more complicated
|
||||
** than the compiler interface
|
||||
**
|
||||
** - by contrast, the data structures that get sent to the compiler
|
||||
** tend to be pretty simple and only have a small number of fields
|
||||
**
|
||||
** - the manner in which this manifests is that ae_node functions
|
||||
** tend to have a weird esoteric data structure as the input,
|
||||
** because otherwise there would be too many parameters.
|
||||
**
|
||||
** here, there are few parameters, and they are passed directly
|
||||
**
|
||||
** - in general, the compiler has fewer things it can do, and the
|
||||
** things it does tend to depend on less information. so this
|
||||
** interface is significantly simpler than the node interface
|
||||
*******************************************************************/
|
||||
|
||||
import * as net from './net.js';
|
||||
|
||||
const URL_COMPILER = 'https://compiler.aepps.com';
|
||||
// endpoints
|
||||
const EPT_CompileContract = URL_COMPILER + '/compile';
|
||||
const EPT_EncodeCalldata = URL_COMPILER + '/encode-calldata';
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// HELPERS
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// make a CompileOpts data structure
|
||||
function
|
||||
compile_options(filename: string)
|
||||
: object
|
||||
{
|
||||
return {"backend" : "fate",
|
||||
"file_system" : {},
|
||||
"src_file" : filename};
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// API CALLS
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// send back compile response
|
||||
async function
|
||||
CompileContract(code : string,
|
||||
filename : string)
|
||||
: Promise<Response>
|
||||
{
|
||||
let send_obj =
|
||||
{"code" : code,
|
||||
"options" : compile_options(filename)};
|
||||
|
||||
let response = await net.post_json_response(EPT_CompileContract, send_obj);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function
|
||||
EncodeCalldata(code : string,
|
||||
filename : string,
|
||||
function_name : string,
|
||||
function_args : Array<string>)
|
||||
: Promise<Response>
|
||||
{
|
||||
let send_obj =
|
||||
{"source" : code,
|
||||
"options" : compile_options(filename),
|
||||
"function" : function_name,
|
||||
"arguments" : function_args};
|
||||
|
||||
let response = await net.post_json_response(EPT_EncodeCalldata, send_obj);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export {
|
||||
CompileContract,
|
||||
EncodeCalldata
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/********************************************************************
|
||||
** Node API: functions for talking to an Aeternity node
|
||||
**
|
||||
** In the future, everything that this module does will be replaced
|
||||
** by the backend.
|
||||
**
|
||||
** Functions should be sorted in alphabetical order.
|
||||
**
|
||||
** Names are what they are in the documentation
|
||||
**
|
||||
** Useful links:
|
||||
**
|
||||
** - HTML API docs : https://api-docs.aeternity.io/
|
||||
** - YAML API docs : https://github.com/aeternity/aeternity/blob/master/apps/aehttp/priv/swagger.yaml
|
||||
**
|
||||
********************************************************************/
|
||||
|
||||
|
||||
import * as net from './net.js'
|
||||
import * as ae_compiler from './ae_compiler.js'
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// CONSTANTS
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
export const MIN_FEE = 16660000000000;
|
||||
export const MIN_CONTRACT_FEE = 79080000000000;
|
||||
export const MIN_GAS_PRICE = 1000000000;
|
||||
export const URL_MAINNET = "https://mainnet.aeternity.io/v2";
|
||||
export const URL_TESTNET = "https://testnet.aeternity.io/v2";
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// CANONICAL TYPES ("MODELS") FROM THE DOCUMENTATION
|
||||
//
|
||||
// All of these names are as given in the documentation except `Error`
|
||||
// (which is a reserve term in JS), renamed to `ErrorReason`
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// Error
|
||||
//
|
||||
// Docs: https://api-docs.aeternity.io/#/definitions/Error
|
||||
// Docs: https://github.com/aeternity/aeternity/blob/v6.4.0/apps/aehttp/priv/swagger.yaml#L3168-L3172
|
||||
type ErrorReason = {reason: string};
|
||||
|
||||
type Tx = {tx: string};
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// FUNCTIONS
|
||||
//
|
||||
// The names here follow their names in the documentation
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// GetAccountNextNonce: /accounts/{pubkey}/next-nonce
|
||||
//
|
||||
// Docs: https://api-docs.aeternity.io/#/account/GetAccountNextNonce
|
||||
//
|
||||
// > Get an account's next nonce; This is computed according to
|
||||
// > whatever is the current account nonce and what transactions are
|
||||
// > currently present in the transaction pool
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
type GetAccountNextNonce_params = {pubkey : string,
|
||||
strategy? : "max" | "continuity"};
|
||||
|
||||
type GetAccountNextNonce_ret = {next_nonce: string};
|
||||
|
||||
|
||||
async function
|
||||
GetAccountNextNonce(endpoint_url : string,
|
||||
params : GetAccountNextNonce_params)
|
||||
: Promise< GetAccountNextNonce_ret
|
||||
| ErrorReason>
|
||||
{
|
||||
let pubkey = params.pubkey;
|
||||
let url = `${endpoint_url}/accounts/${pubkey}/next-nonce`;
|
||||
|
||||
// if the "strategy" field is present, add it as a ?strategy=x
|
||||
// option
|
||||
//
|
||||
// note if the field is absent from `params`, then
|
||||
// `params.strategy` will be `undefined`, which in js whacko
|
||||
// world is "falsy"
|
||||
let strategy = params.strategy;
|
||||
if (strategy)
|
||||
{
|
||||
let addon = `?strategy=${strategy}`;
|
||||
url += addon;
|
||||
}
|
||||
|
||||
// irrespective of the response code, this is what we return
|
||||
// so branching is gay
|
||||
let ret = await net.get_json(url);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// PostContractCreate
|
||||
//
|
||||
// Docs: https://api-docs.aeternity.io/#/contract/PostContractCreate
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// > Get a contract_create transaction object
|
||||
|
||||
|
||||
type ContractCreateTx = {owner_id : string,
|
||||
nonce? : number,
|
||||
code : string,
|
||||
vm_version : number,
|
||||
abi_version : number,
|
||||
deposit : number,
|
||||
amount : number,
|
||||
gas : number,
|
||||
gas_price : number,
|
||||
fee : number,
|
||||
ttl? : number,
|
||||
call_data : string};
|
||||
|
||||
|
||||
|
||||
async function
|
||||
PostContractCreate(endpoint_url : string,
|
||||
body_obj : ContractCreateTx)
|
||||
: Promise<Response>
|
||||
{
|
||||
// console.log('body_obj', body_obj);
|
||||
let url = `${endpoint_url}/debug/contracts/create`;
|
||||
let ret = await net.post_json_response(url, body_obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function
|
||||
create_contract(whoami : string,
|
||||
code : string,
|
||||
filename : string,
|
||||
init_args : Array<string>)
|
||||
: Promise<Response>
|
||||
{
|
||||
let code_resp = await ae_compiler.CompileContract(code, filename);
|
||||
let code_json = await code_resp.json();
|
||||
let bytecode = code_json.bytecode;
|
||||
|
||||
let calldata_resp = await ae_compiler.EncodeCalldata(code, filename, "init", init_args);
|
||||
// assert(calldata_resp.ok);
|
||||
let calldata_json = await calldata_resp.json();
|
||||
let calldata = calldata_json.calldata;
|
||||
|
||||
let cctx: ContractCreateTx =
|
||||
{owner_id : whoami,
|
||||
code : bytecode,
|
||||
vm_version : 7,
|
||||
abi_version : 3,
|
||||
deposit : 0,
|
||||
amount : 0,
|
||||
gas : 25000,
|
||||
gas_price : 1*MIN_GAS_PRICE,
|
||||
fee : 1*MIN_CONTRACT_FEE,
|
||||
call_data : calldata};
|
||||
|
||||
let ret = await PostContractCreate(URL_TESTNET, cctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// PostSpend: /debug/transactions/spend
|
||||
//
|
||||
// Docs:
|
||||
// - Input type : https://api-docs.aeternity.io/#/definitions/SpendTx
|
||||
// - Return type : https://api-docs.aeternity.io/#/definitions/Tx
|
||||
// - Function : https://api-docs.aeternity.io/#/transaction/PostSpend
|
||||
//
|
||||
// > Get a spend transaction object
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// SpendTx
|
||||
//
|
||||
// Docs: https://api-docs.aeternity.io/#/definitions/SpendTx
|
||||
// Docs: https://github.com/aeternity/aeternity/blob/v6.4.0/apps/aehttp/priv/swagger.yaml#L2187-L2209
|
||||
//-------------------------------------------------------------------
|
||||
type SpendTx =
|
||||
{recipient_id : string,
|
||||
amount : number,
|
||||
fee : number,
|
||||
ttl? : number,
|
||||
sender_id : string,
|
||||
nonce? : number,
|
||||
payload : string};
|
||||
|
||||
|
||||
|
||||
async function
|
||||
PostSpend(endpoint_url : string,
|
||||
body_obj : SpendTx)
|
||||
: Promise<Tx | ErrorReason>
|
||||
{
|
||||
let url = `${endpoint_url}/debug/transactions/spend`;
|
||||
let ret = await net.post_json(url, body_obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// PostTransaction
|
||||
//
|
||||
// Docs: https://api-docs.aeternity.io/#/contract/PostTransaction
|
||||
//
|
||||
// > Post a new transaction
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
async function
|
||||
PostTransaction(endpoint_url : string,
|
||||
body_obj : Tx)
|
||||
: Promise<Response>
|
||||
{
|
||||
// console.log('body_obj', body_obj);
|
||||
let url = `${endpoint_url}/transactions`;
|
||||
let ret = await net.post_json_response(url, body_obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// GetTransactionInfoByHash
|
||||
//
|
||||
// Docs: https://api-docs.aeternity.io/#/contract/GetTransactionInfoByHash
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
async function
|
||||
GetTransactionInfoByHash(endpoint_url : string,
|
||||
hash : string)
|
||||
: Promise<Response>
|
||||
{
|
||||
// console.log('body_obj', body_obj);
|
||||
let url = `${endpoint_url}/transactions/${hash}/info`;
|
||||
let ret = await net.get_json_response(url);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// EXPORTS
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// type exports
|
||||
export type {
|
||||
ErrorReason,
|
||||
Tx,
|
||||
SpendTx,
|
||||
ContractCreateTx
|
||||
};
|
||||
|
||||
export {
|
||||
GetAccountNextNonce,
|
||||
PostContractCreate,
|
||||
create_contract,
|
||||
PostSpend,
|
||||
PostTransaction
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
//-------------------------------------------------------------------
|
||||
// Common networking functions
|
||||
//
|
||||
// Mozilla fetch docs : https://developer.mozilla.org/en-US/docs/Web/API/fetch
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
/* pf = pretty format
|
||||
*/
|
||||
function pf(x : any) : string
|
||||
{
|
||||
return JSON.stringify(x, undefined, 4);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// FUNCTIONS
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// GET request with return type of JSON
|
||||
async function
|
||||
get_json(url: string)
|
||||
: Promise<any>
|
||||
{
|
||||
let response = await fetch(url);
|
||||
let ret = await response.json();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// GET request with return type of JSON
|
||||
async function
|
||||
get_json_response(url: string)
|
||||
: Promise<Response>
|
||||
{
|
||||
let response = await fetch(url);
|
||||
//let ret = await response.json();
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// POST request with content type of json and return type of JSON
|
||||
async function
|
||||
post_json(url : string,
|
||||
body_obj : any)
|
||||
: Promise<any>
|
||||
{
|
||||
let body_str = pf(body_obj);
|
||||
let req_opts = {method : 'POST',
|
||||
body : body_str,
|
||||
headers : {"Content-Type": "application/json"}};
|
||||
let response = await fetch(url, req_opts);
|
||||
let ret = await response.json();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
async function
|
||||
post_json_response(url : string,
|
||||
body_obj : any)
|
||||
: Promise<Response>
|
||||
{
|
||||
let body_str = pf(body_obj);
|
||||
let req_opts = {method : 'POST',
|
||||
body : body_str,
|
||||
headers : {"Content-Type": "application/json"}};
|
||||
let response = await fetch(url, req_opts);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// EXPORTS
|
||||
//-------------------------------------------------------------------
|
||||
export {
|
||||
get_json,
|
||||
get_json_response,
|
||||
post_json,
|
||||
post_json_response
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{"compilerOptions" : {"target" : "es2022",
|
||||
"strict" : true,
|
||||
"esModuleInterop" : true,
|
||||
"skipLibCheck" : true,
|
||||
"forceConsistentCasingInFileNames" : true,
|
||||
"noImplicitAny" : true,
|
||||
"strictNullChecks" : true,
|
||||
"strictPropertyInitialization" : true,
|
||||
"sourceMap" : true,
|
||||
"outDir" : "dist",
|
||||
"declaration" : true},
|
||||
"$schema" : "https://json.schemastore.org/tsconfig",
|
||||
"display" : "Recommended",
|
||||
"include" : ["src/**/*"],
|
||||
"exclude" : ["src/jx_include"],
|
||||
"composite" : true}
|
||||
Reference in New Issue
Block a user