diff --git a/libs/awcp/README.md b/libs/awcp/README.md index 5257acb..d42c369 100644 --- a/libs/awcp/README.md +++ b/libs/awcp/README.md @@ -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 | -| 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` - - 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 { - 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 - - - - - Hello world: Sidekick - - -

Sidekick Example: Hello World

- -

Check the console

- - - -``` - -The important lines are - -```html - -``` - -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 -{ - //--------------------------------------------------------------- - // 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 diff --git a/libs/awcp/src/awcp.ts b/libs/awcp/src/awcp.ts index 75a2d13..9b1a687 100644 --- a/libs/awcp/src/awcp.ts +++ b/libs/awcp/src/awcp.ts @@ -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>) + * // 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>) + * // 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>) + * // 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 @@ -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 ; @@ -487,9 +662,6 @@ type RpcResp_Any = RpcResp; // 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; @@ -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; @@ -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; @@ -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; @@ -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>; diff --git a/sidekick/README.md b/sidekick/README.md index 286083d..acf5c7c 100644 --- a/sidekick/README.md +++ b/sidekick/README.md @@ -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 diff --git a/sidekick/jex.eterms b/sidekick/jex.eterms index 1e1ea93..880af19 100644 --- a/sidekick/jex.eterms +++ b/sidekick/jex.eterms @@ -2,4 +2,4 @@ {realm, local}. {name, sidekick}. {version, "0.2.0"}. -{deps, ["local-awcp-0.1.0"]}. +{deps, ["local-awcp-0.2.0"]}. diff --git a/sidekick/src/sidekick.ts b/sidekick/src/sidekick.ts index 31f2c06..c61e586 100644 --- a/sidekick/src/sidekick.ts +++ b/sidekick/src/sidekick.ts @@ -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; - * info : (message : string, data : object) => Promise; - * warning : (message : string, data : object) => Promise; - * error : (message : string, data : object) => Promise; - * } - * ``` - * - * ## 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> - * ``` - * - * This returns some garbage that doesn't matter in a `Safe` type. - * The `Safe` type does matter - * - * ``` - * type Safe - * = Ok - * | Error; - * - * type Ok - * = {ok : true, - * result : ok_t}; - * - * type Error - * = {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 diff --git a/utils/jex/src/jex.erl b/utils/jex/src/jex.erl index 8d4eff4..3e5a492 100644 --- a/utils/jex/src/jex.erl +++ b/utils/jex/src/jex.erl @@ -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});