diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..b2bff96d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Radix DLT Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at hello@radixdlt.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md new file mode 100644 index 00000000..29b5f851 --- /dev/null +++ b/CONTRIBUTION.md @@ -0,0 +1,50 @@ +# Contribution + +## Commits + +The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of. This convention dovetails with SemVer, by describing the features, fixes, and breaking changes made in commit messages. + +The commit message should be structured as follows: + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +1. The commit contains the following structural elements, to communicate intent to the consumers of your library: + +1. fix: a commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning). + +1. feat: a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in Semantic Versioning). + +1. BREAKING CHANGE: a commit that has a footer BREAKING CHANGE:, or appends a ! after the type/scope, introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type. + +1. types other than fix: and feat: are allowed, for example @commitlint/config-conventional (based on the Angular convention) recommends build:, chore:, ci:, docs:, style:, refactor:, perf:, test:, and others. + footers other than BREAKING CHANGE: may be provided and follow a convention similar to git trailer format. + +1. Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE). A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g., feat(parser): add ability to parse arrays. + +### Commit types + +| Type | Title | Description | +| ---------- | ------------------------ | ----------------------------------------------------------------------------------------------------------- | +| `feat` | Features | A new feature | +| `fix` | Bug Fixes | A bug Fix | +| `docs` | Documentation | Documentation only changes | +| `style` | Styles | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) | +| `refactor` | Code Refactoring | A code change that neither fixes a bug nor adds a feature | +| `perf` | Performance Improvements | A code change that improves performance | +| `test` | Tests | Adding missing tests or correcting existing tests | +| `build` | Builds | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) | +| `ci` | Continuous Integrations | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) | +| `chore` | Chores | Other changes that don't modify src or test files | +| `revert` | Reverts | Reverts a previous commit | + +[Read more](https://www.conventionalcommits.org/en/v1.0.0/#summary). + +## Change Log + +Every release is documented on the GitHub Releases page. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..4ac2a5d1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2022 Radix Publishing Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index c60a1c0b..e387c9c5 100644 --- a/README.md +++ b/README.md @@ -1,613 +1,32 @@ -[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) +# Radix Logo Radix dApp Toolkit: Tools for building dApps on Radix -- [What is Radix dApp Toolkit?](#what-is-radix-dapp-toolkit) - - [Resources](#resources) - - [Building a dApp frontend](#building-a-dapp-frontend) -- [Installation](#installation) -- [Usage](#usage) - - [Getting started](#getting-started) - - [Login requests](#login-requests) - - [User authentication](#user-authentication) - - [Handle user authentication](#handle-user-authentication) - - [User authentication management](#user-authentication-management) - - [Wallet data requests](#wallet-data-requests) - - [Trigger wallet data request programmatically](#trigger-wallet-data-request-programmatically) - - [Change requested data](#change-requested-data) - - [Data request builder](#data-request-builder) - - [`DataRequestBuilder.persona()`](#datarequestbuilderpersona) - - [`DataRequestBuilder.accounts()`](#datarequestbuilderaccounts) - - [`OneTimeDataRequestBuilderItem.accounts()`](#onetimedatarequestbuilderitemaccounts) - - [`DataRequestBuilder.personaData()`](#datarequestbuilderpersonadata) - - [`OneTimeDataRequestBuilderItem.personaData()`](#onetimedatarequestbuilderitempersonadata) - - [`DataRequestBuilder.config(input: DataRequestState)`](#datarequestbuilderconfiginput-datarequeststate) - - [Handle connect responses](#handle-connect-responses) - - [One Time Data Request](#one-time-data-request) - - [Data Requests Sandbox](#data-requests-sandbox) - - [State changes](#state-changes) - - [Transaction requests](#transaction-requests) - - [Build transaction manifest](#build-transaction-manifest) - - [sendTransaction](#sendtransaction) - - [State Entity Details - dApp account](#state-entity-details---dapp-account) -- [ROLA (Radix Off-Ledger Authentication)](#rola-radix-off-ledger-authentication) -- [√ Connect Button](#-connect-button) - - [Styling](#styling) - - [Themes](#themes) - - [Modes](#modes) - - [CSS variables](#css-variables) - - [Compact mode](#compact-mode) - - [Storybook - Connect Button Playground](#storybook---connect-button-playground) -- [Setting up your dApp Definition](#setting-up-your-dapp-definition) -- [Data storage](#data-storage) -- [Examples](#examples) -- [License](#license) +[![npm version](https://badge.fury.io/js/@radixdlt%2Fradix-dapp-toolkit.svg)](https://badge.fury.io/js/@radixdlt%2Fradix-dapp-toolkit) -# What is Radix dApp Toolkit? +# Radix dApp Toolkit - v2 Monorepo -Radix dApp Toolkit (RDT) is a TypeScript library that automates getting users logged in to your dApp using a Persona, maintains a browser session for that login, and provides a local cache of data the user has given permission to your app to access associated with their Persona. It also provides an interface to request accounts and personal data from the user's wallet, either as a permission for ongoing access or as a one-time request, as well as to submit transaction manifest stubs for the user to review, sign, and submit in their wallet. +Look for dApp toolkit and related packages under the [/packages](/packages/) directory. Example applications are under the [/examples](/examples/) directory. -The current version only supports desktop browser webapps with requests made via the Radix Wallet Connector browser extension. It is intended to later add support for mobile browser webapps using deep linking with the same essential interface. +[Apache 2.0 License](LICENSE) -**RDT is composed of:** +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Contribution Guidelines](CONTRIBUTION.md) +- [EULA](RADIX-SOFTWARE-EULA) -- **√ Connect Button** – A framework agnostic web component that keeps a minimal internal state and have properties are pushed to it. +This is merging [Radix dApp Toolkit](https://github.com/radixdlt/radix-dapp-toolkit), [√ Connect Button](https://github.com/radixdlt/connect-button), [Wallet Request SDK](https://github.com/radixdlt/wallet-sdk), [Radix Connect Schemas](https://github.com/radixdlt/radix-connect-schemas) into one mono-repository. -- **Tools** – Abstractions over lower level APIs for developers to build their radix dApps at lightning speed. +## Versions In This Repository -- **State management** – Handles wallet responses, caching and provides data to √ Connect button. +- [main](https://github.com/radixdlt/radix-dapp-toolkit/commits/main) - This is all of the current work, which is against v2 of RDT right now -## Resources +Most PRs should be made to **main**. -### [Building a dApp frontend](https://docs.radixdlt.com/docs/building-a-frontend-dapp) +## Important -# Installation +By contributing or commenting on issues in this repository, whether you've read them or not, you're agreeing to the [Contributor Code of Conduct](CODE_OF_CONDUCT.md). Much like traffic laws, ignorance doesn't grant you immunity. -**Using NPM** +## Development -```bash -npm install @radixdlt/radix-dapp-toolkit -``` - -**Using Yarn** - -```bash -yarn add @radixdlt/radix-dapp-toolkit -``` - -# Usage - -## Getting started - -Add the `` element in your HTML code and instantiate `RadixDappToolkit`. - -```typescript -import { RadixDappToolkit, RadixNetwork } from '@radixdlt/radix-dapp-toolkit' - -const rdt = RadixDappToolkit({ - dAppDefinitionAddress: - 'account_tdx_e_128uml7z6mqqqtm035t83alawc3jkvap9sxavecs35ud3ct20jxxuhl', - networkId: RadixNetwork.RCnetV3, - applicationName: 'Radix Web3 dApp', - applicationVersion: '1.0.0', -}) -``` - -**Input** - -- **requires** dAppDefinitionAddress - Specifies the dApp that is interacting with the wallet. Used in dApp verification process on the wallet side. [Read more](#setting-up-your-dapp-definition) -- **requires** networkId - Target radix network ID. -- _optional_ applicationName - Your dApp name. It's only used for statistics purposes on gateway side -- _optional_ applicationVersion - Your dApp version. It's only used for statistics purposes on gateway side - -## Login requests - -The user's journey on your dApp always always starts with connecting their wallet and logging in with a Persona. The "Connect" button always requests a Persona login from the user's wallet. - -The default behavior is to request the login alone, but you may also choose to add additional requests for account information or personal data to get at the time of login. This is useful if there is information that you know your dApp always needs to be able to function. You can also however choose to keep the login simple and make other requests later, as needed. Doing it this way allows your dApp to provide a helpful description in its UI of what a given piece of requested information is needed for, such as "please share all of your accounts that you want to use with this dApp" or "providing your email address will let us keep you informed of new features". - -The Persona the user logs in with sets the context for all ongoing account and personal data requests for that session. The Radix Wallet keeps track of what permissions the user has provided for each dApp and each Persona they've used with that dApp. RDT automatically keeps track of the currently logged in Persona so that requests to the wallet are for the correct Persona. - -After login, RDT also provides your dApp with a local cache of all account information and personal data that a user has given permission to share with your dApp for their chosen Persona. - -For a pure frontend dApp (where you have no backend or user database), there is typically no reason for a Persona login to be verified and the login process is completely automated by RDT. - -### User authentication - -For a full-stack dApp there is also the user authentication flow. Typically, a full-stack dApp would request a persona together with a proof of ownership, which is then verified on the dApp backend using ROLA verification. - -**What is a proof of ownership?** - -A signature produced by the wallet used to verify that the wallet is in control of a persona or account. - -```typescript -// Signed challenge -{ - type: 'persona' | 'account' - challenge: string - proof: { - publicKey: string - signature: string - curve: 'curve25519' | 'secp256k1' - } - address: string -} -``` - -The signature is composed of: - -| | | -| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| **prefix** | "R" (as in ROLA) in ascii encoding | -| **challenge** | 32 random bytes provided by the dApp | -| **length of dApp definition address** | String length of the dApp definition address | -| **dApp definition address** | The dApp definition address of the requesting dApp | -| **origin** | The origin of the dApp (e.g. `https://dashboard.radixdlt.com`). This is a value that is added to the wallet data request by the Connector extension. | - -**Challenge** - -In order to request a persona or account with proof of ownership a challenge is needed. - -A challenge is a random 32 bytes hex encoded string that looks something like: `4ccb0555d6b4faad0d7f5ed40bf4e4f0665c8ba35929c638e232e09775d0fa0e` - -**Why do we need a challenge?** - -The challenge plays an important role in the authentication flow, namely preventing replay attacks from bad actors. The challenge ensures that an authentication request payload sent from the client can only be used once. After a challenge is claimed by a request, the subsequent requests can no longer be resolved successfully with the same payload. As a security best practice, a stored challenge should have a short expiration time. In this case, just enough time for a user to interact with the wallet. - -**Request persona with proof** - -In order to request a proof, it is required to provide a function to RDT that produces a challenge. - -```typescript -// type requestChallengeFromDappBackendFn = () => Promise - -rdt.walletApi.provideChallengeGenerator(requestChallengeFromDappBackendFn) - -rdt.walletApi.setRequestData(DataRequestBuilder.persona.withProof()) - -// handle the wallet response -rdt.walletApi.dataRequestControl(async (walletData) => { - const personaProof = walletData.proofs.find( - (proof) => proof.type === 'persona', - ) - if (personaProof) await handleLogin(personaProof) -}) -``` - -### Handle user authentication - -A typical full stack dApp will require the user to provide proof of ownership. After sending a data request and getting the proof from the wallet, you need authenticate the user through ROLA on the dApp backend. - -Use `walletApi.dataRequestControl` to provide a callback function that intercepts the RDT data request response flow. If no error has been thrown inside of the callback function the RDT flow will proceed as usual. - -```typescript -rdt.walletApi.dataRequestControl(async (walletData) => { - const personaProof = walletData.proofs.find( - (proof) => proof.type === 'persona', - ) - if (personaProof) await handleLogin(personaProof) -}) -``` - -Throwing an error inside of `walletApi.dataRequestControl` callback will prevent RDT from getting into a logged in state. A full stack dApp may wish to do this to prevent RDT from treating the user as logged in because the ROLA authentication check failed, or for other application-specific reasons why a given user should not be allowed to login. - -```typescript -rdt.walletApi.dataRequestControl(async (walletData) => { - throw new Error('something bad happened...') -}) -``` - -See [ROLA example](https://github.com/radixdlt/rola-examples) for an end-to-end implementation. - -### User authentication management - -After a successful ROLA verification it is up to the dApp's business logic to handle user authentication session in order to keep the user logged-in between requests. Although RDT is persisting state between page reloads, it is not aware of user authentication. The dApp logic needs to control the login state and sign out a user when needed. - -**Expired user auth session** - -If a user's auth session has expired it is recommended to logout the user in RDT as well. The dApp needs to call the `disconnect` method in order to but the user in a **not connected** state. - -```typescript -rdt.disconnect() -``` - -The `disconnect` method resets the RDT state, to login anew, a wallet data request needs to be triggered. - -## Wallet data requests - -For your dApp to access data from a user's wallet, whether account information or personal data, a request must be sent to the wallet. By default, the request will be "ongoing", meaning that the user will be asked for permission to share the information whenever they login to your dApp with their current Persona. A request may also be "one time" if it is for transient use and you do not require the permission to be retained by the user's wallet. - -There are two ways to trigger a data request: - -1. As part of the login request when the user clicks the √ Connect button's "Connect" -2. Programmatically through the walletApi.sendRequest method - -#### Trigger wallet data request programmatically - -```typescript -const result = await rdt.walletApi.sendRequest() - -if (result.isErr()) return handleException() - -// { -// persona?: Persona, -// accounts: Account[], -// personaData: WalletDataPersonaData[], -// proofs: SignedChallenge[], -// } -const walletData = result.value -``` - -### Change requested data - -By default, a data request requires a Persona to set its context and so if the user is not already logged in, the data request will include a request for login. - -Use `walletApi.setRequestData` together with `DataRequestBuilder` to change the wallet data request. - -```typescript -rdt.walletApi.setRequestData( - DataRequestBuilder.persona().withProof(), - DataRequestBuilder.accounts().exactly(1), - DataRequestBuilder.personaData().fullName().emailAddresses(), -) -``` - -### Data request builder - -The `DataRequestBuilder` and `OneTimeDataRequestBuilder` is there to assist you in constructing a wallet data request. - -#### `DataRequestBuilder.persona()` - -```typescript -withProof: (value?: boolean) => PersonaRequestBuilder -``` - -Example: Request persona with proof of ownership - -```typescript -rdt.walletApi.setRequestData(DataRequestBuilder.persona().withProof()) -``` - -#### `DataRequestBuilder.accounts()` - -```typescript -atLeast: (n: number) => AccountsRequestBuilder -exactly: (n: number) => AccountsRequestBuilder -withProof: (value?: boolean) => AccountsRequestBuilder -reset: (value?: boolean) => AccountsRequestBuilder -``` - -Example: Request at least 1 account with proof of ownership - -```typescript -rdt.walletApi.setRequestData( - DataRequestBuilder.accounts().atLeast(1).withProof(), -) -``` - -#### `OneTimeDataRequestBuilderItem.accounts()` - -```typescript -atLeast: (n: number) => OneTimeAccountsRequestBuilder -exactly: (n: number) => OneTimeAccountsRequestBuilder -withProof: (value?: boolean) => OneTimeAccountsRequestBuilder -``` - -Example: Exactly 2 accounts - -```typescript -rdt.walletApi.sendOneTimeRequest( - OneTimeDataRequestBuilder.accounts().exactly(2), -) -``` - -#### `DataRequestBuilder.personaData()` - -```typescript -fullName: (value?: boolean) => PersonaDataRequestBuilder -emailAddresses: (value?: boolean) => PersonaDataRequestBuilder -phoneNumbers: (value?: boolean) => PersonaDataRequestBuilder -reset: (value?: boolean) => PersonaDataRequestBuilder -``` - -Example: Request full name and email address - -```typescript -rdt.walletApi.setRequestData( - DataRequestBuilder.personaData().fullName().emailAddresses(), -) -``` - -#### `OneTimeDataRequestBuilderItem.personaData()` - -```typescript -fullName: (value?: boolean) => PersonaDataRequestBuilder -emailAddresses: (value?: boolean) => PersonaDataRequestBuilder -phoneNumbers: (value?: boolean) => PersonaDataRequestBuilder -``` - -Example: Request phone number - -```typescript -rdt.walletApi.sendOneTimeRequest( - OneTimeDataRequestBuilder.personaData().phoneNumbers(), -) -``` - -#### `DataRequestBuilder.config(input: DataRequestState)` - -Use this method if you prefer to provide a raw data request object. - -Example: Request at least 1 account and full name. - -```typescript -rdt.walletApi.setRequestData( - DataRequestBuilder.config({ - personaData: { fullName: true }, - accounts: { numberOfAccounts: { quantifier: 'atLeast', quantity: 1 } }, - }), -) -``` - -### Handle connect responses - -Add a callback function to `provideConnectResponseCallback` that emits a wallet response. - -```typescript -rdt.walletApi.provideConnectResponseCallback((result) => { - if (result.isErr()) { - // handle connect error - } -}) -``` - -### One Time Data Request - -One-time data requests do not have a Persona context, and so will always result in the Radix Wallet asking the user to select where to draw personal data from. The wallet response from a one time data request is meant to be discarded after usage. A typical use case would be to populate a web-form with user data. - -```typescript -const result = rdt.walletApi.sendOneTimeRequest( - OneTimeDataRequestBuilder.accounts().exactly(1), - OneTimeDataRequestBuilder.personaData().fullName(), -) - -if (result.isErr()) return handleException() - -// { -// accounts: Account[], -// personaData: WalletDataPersonaData[], -// proofs: SignedChallenge[], -// } -const walletData = result.value -``` - -### Data Requests Sandbox - -Play around with the different data requests in - -- [Stokenet sandbox environment](https://stokenet-sandbox.radixdlt.com/) -- [Mainnet sandbox environment](https://sandbox.radixdlt.com/) - -## State changes - -Listen to wallet data changes by subscribing to `walletApi.walletData$`. - -```typescript -const subscription = rdt.walletApi.walletData$.subscribe((walletData) => { - // { - // persona?: Persona, - // accounts: Account[], - // personaData: WalletDataPersonaData[], - // proofs: SignedChallenge[], - // } - doSomethingWithAccounts(walletData.accounts) -}) -``` - -When your dApp is done listening to state changes remember to unsubscribe in order to prevent memory leaks. - -```typescript -subscription.unsubscribe() -``` - -Get the latest wallet data by calling `walletApi.getWalletData()`. - -```typescript -// { -// persona?: Persona, -// accounts: Account[], -// personaData: WalletDataPersonaData[], -// proofs: SignedChallenge[], -// } -const walletData = rdt.walletApi.getWalletData() -``` - -## Transaction requests - -Your dApp can send transactions to the user's Radix Wallet for them to review, sign, and submit them to the Radix Network. - -Radix transactions are built using "transaction manifests", that use a simple syntax to describe desired behavior. See [documentation on transaction manifest commands here](https://docs.radixdlt.com/docs/transaction-manifest). - -It is important to note that what your dApp sends to the Radix Wallet is actually a "transaction manifest stub". It is completed before submission by the Radix Wallet. For example, the Radix Wallet will automatically add a command to lock the necessary amount of network fees from one of the user's accounts. It may also add "assert" commands to the manifest according to user desires for expected returns. - -**NOTE:** Information will be provided soon on a ["comforming" transaction manifest stub format](https://docs.radixdlt.com/docs/conforming-transaction-manifest-types) that ensures clear presentation and handling in the Radix Wallet. - -### Build transaction manifest - -We recommend using template strings for constructing simpler transaction manifests. If your dApp is sending complex manifests a manifest builder can be found in [TypeScript Radix Engine Toolkit](https://github.com/radixdlt/typescript-radix-engine-toolkit#building-manifests) - -### sendTransaction - -This sends the transaction manifest stub to a user's Radix Wallet, where it will be completed, presented to the user for review, signed as required, and submitted to the Radix network to be processed. - -```typescript -type SendTransactionInput = { - transactionManifest: string - version?: number - blobs?: string[] - message?: string - onTransactionId?: (transactionId: string) => void -} -``` - -- **requires** transactionManifest - specify the transaction manifest -- **optional** version - specify the version of the transaction manifest -- **optional** blobs - used for deploying packages -- **optional** message - message to be included in the transaction -- **optional** onTransactionId - provide a callback that emits a transaction ID - -
- -sendTransaction example - -```typescript -const result = await rdt.walletApi.sendTransaction({ - transactionManifest: '...', -}) - -if (result.isErr()) { - // code to handle the exception -} - -const transactionIntentHash = result.value.transactionIntentHash -``` - -
- -## State Entity Details - dApp account - -Right after RDT is instantiated it'll trigger Gateway request to find information about provided dApp definition account. Response of that request is available through `dAppDefinitionAccount` object on rdt instance. You can access it either through `entityDetails$` observable or `entityDetails` getter. It's worth remembering that `entityDetails` property will be undefined before response has come back from the gateway - -dAppDefinitionAccount usage example - -```typescript -rdt.dAppDefinitionAccount.entityDetails$.subscribe((details) => - console.log(details), -) -// OR -console.log(rdt.dAppDefinitionAccount.entityDetails) -``` - - - -# ROLA (Radix Off-Ledger Authentication) - -ROLA is method of authenticating something claimed by the user connected to your dApp with the Radix Wallet. It uses the capabilities of the Radix Network to make this possible in a way that is decentralized and flexible for the user. - -ROLA is intended for use in the server backend portion of a Full Stack dApp. It runs "off-ledger" alongside backend business and user management logic, providing reliable authentication of claims of user control using "on-ledger" data from the Radix Network. - -The primary use for ROLA is to authenticate the user's Persona login with the user's control of account(s) on Radix. Let's say that Alice is subscribed to an online streaming service on the Radix network called Radflix, which requires a subscription badge to enter the website. Alice logs in with her Persona to Radflix and now needs to prove that she owns an account that contains a Radflix subscription badge. By using Rola we can verify that Alice is the owner of the account that contains the Radflix subscription badge. Once we have verified that Alice is the owner of the account, we can then use the account to check for the Radflix subscription badge and verify that Alice has a valid subscription. - -**Read more** - -- [ROLA example](https://github.com/radixdlt/rola-examples) -- [Full-stack dApp](https://docs.radixdlt.com/docs/building-a-full-stack-dapp) - -# √ Connect Button - -Provides a consistent and delightful user experience between radix dApps. Although complex by itself, RDT is off-loading the developer burden of having to handle the logic of all its internal states. - -Just add the HTML element in your code, and you're all set. - -```html - -``` - -## Styling - -Configure the √ Connect Button to fit your dApp's branding. - -### Themes - -Available themes: - -- `radix-blue` (default) -- `black` -- `white-with-outline` -- `white` - -```typescript -rdt.buttonApi.setTheme('black') -``` - -### Modes - -Available modes: - -- `light` (default) -- `dark` - -```typescript -rdt.buttonApi.setMode('dark') -``` - -### CSS variables - -There are three CSS variables available: - -- `--radix-connect-button-width` (default 138px) -- `--radix-connect-button-height` (default 42px) -- `--radix-connect-button-border-radius` (default 0px) - -```css -body { - --radix-connect-button-width: 200px; - --radix-connect-button-height: 42px; - --radix-connect-button-border-radius: 12px; -} -``` - -### Compact mode - -Setting `--radix-connect-button-width` below `138px` will enable compact mode. - -### Storybook - Connect Button Playground - -Play around with the different configurations on the -[storybook environment](https://connect-button-storybook.radixdlt.com/) - -# Setting up your dApp Definition - -A dApp Definition account should be created after you’ve built your dApp’s components and resources, and created a website front end for it. dApp Definition account is a special account on the Radix Network with some metadata set on it that does some nice things, like: - -- Provides the necessary unique identifier (the dApp Definition’s address) that the Radix Wallet needs to let users login to your dApp and save sharing preferences for it. - -- Defines things like name, description, and icon so the Radix Wallet can inform users what they are interacting with. - -- Lets you link together everything associated with your dApp – like websites, resources, and components – so that the Radix Wallet knows what they all belong to. - -Creating a dApp Definition for your dApp will provide the necessary information for clients like the Radix Wallet to let users interact with your dApp in a way that is easy, safe, and informative. It also acts as a hub that connects all your dApp pieces together. - -You can read more about dApp Definitions [here](https://docs.radixdlt.com/docs/metadata-for-verification). - -You can read more about how to set proper metadata on dApp definition account [here](https://docs.radixdlt.com/docs/dapp-definition-setup). - -# Data storage - -To provide a consistent user experience RDT stores data to the browser’s local storage. This will enable state rehydration and keep state between page reloads. - -To understand which wallet responses that get stored we need to understand the difference between one-time and regular data requests. - -One-time data requests do not register the dApp in the wallet and the connect button does not display that data in the UI. The data is meant to be used temporarily by the dApp and discarded thereafter. - -A user connecting her wallet will be the first user flow in the majority of dApps. The connect flow is a bit different from subsequent data request flows. Its purpose is to provide the dApp with a minimal amount of user data in order for the user to be able to use the dApp, e.g. the minimal amount of data for a DEX dApp is an account. - -RDT handles writing and reading data to the browser’s local storage so that it will be persisted between page loads. The dApp frontend logic can at any time ask RDT to provide the stored data by subscribing to the `walletApi.walletData$` observable or calling `walletApi.getWalletData`. One time data requests or requests that can not be resolved by the internal state are sent as data requests to the wallet. - -# Examples - -The `examples` directory contains a react dApp that consumes RDT. Its main purpose is to be used by us internally for debugging but can also serve as a source of inspiration. - -# License - -The Radix Dapp Toolkit binaries are licensed under the [Radix Software EULA](http://www.radixdlt.com/terms/genericEULA). - -The Radix Dapp Toolkit code is released under [Apache 2.0 license](LICENSE). - - Copyright 2023 Radix Publishing Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. - - You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - - See the License for the specific language governing permissions and limitations under the License. +1. `cd` to the repository root +2. `npm install` to install all dependencies +3. `npm run test` will run the test suite +4. `npm run dev` will start the dApp toolkit development servers diff --git a/docs/radix-logo.png b/docs/radix-logo.png new file mode 100644 index 00000000..b546d0bb Binary files /dev/null and b/docs/radix-logo.png differ diff --git a/docs/wallet-request-sdk.md b/docs/wallet-request-sdk.md new file mode 100644 index 00000000..3b6230d4 --- /dev/null +++ b/docs/wallet-request-sdk.md @@ -0,0 +1,643 @@ +# Radix Logo Wallet Request SDK - Handles communication with the Radix Wallet + +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +This is a TypeScript developer SDK that facilitates communication with the Radix Wallet for two purposes: **requesting various forms of data from the wallet** and **sending transactions to the wallet**. + +- [ Wallet Request SDK - Handles communication with the Radix Wallet](#-wallet-request-sdk---handles-communication-with-the-radix-wallet) +- [⬇️ Getting Wallet Data](#️-getting-wallet-data) + - [💶 Accounts](#-accounts) + - [Request](#request) + - [Response](#response) + - [ℹ️ Persona Data](#ℹ️-persona-data) + - [Request](#request-1) + - [Response](#response-1) + - [🗑️ Reset](#️-reset) + - [Request](#request-2) + - [Response](#response-2) + - [🛂 Auth](#-auth) + - [Request](#request-3) + - [Response](#response-3) +- [💸 Send transaction](#-send-transaction) + - [Build transaction manifest](#build-transaction-manifest) + - [sendTransaction](#sendtransaction) + - [Errors](#errors) +- [License](#license) + +# ⬇️ Getting Wallet Data + +**About oneTime VS ongoing requests** + +There are two types of data requests: `oneTime` and `ongoing`. + +**OneTime** data requests will **always** result in the Radix Wallet asking for the user's permission to share the data with the dApp. + +```typescript +type WalletUnauthorizedRequestItems = { + discriminator: 'unauthorizedRequest' + oneTimeAccounts?: AccountsRequestItem + oneTimePersonaData?: PersonaDataRequestItem +} +``` + +**Ongoing** data requests will only result in the Radix Wallet asking for the user's permission the first time. If accepted, the Radix Wallet will automatically respond to future data requests of this type with the current data. The user's permissions for ongoing data sharing with a given dApp can be managed or revoked by the user at any time in the Radix Wallet. + +```typescript +type WalletAuthorizedRequestItems = { + discriminator: 'authorizedRequest' + auth: AuthRequestItem + reset?: ResetRequestItem + oneTimeAccounts?: AccountsRequestItem + oneTimePersonaData?: PersonaDataRequestItem + ongoingAccounts?: AccountsRequestItem + ongoingPersonaData?: PersonaDataRequestItem +} +``` + +The user's ongoing data sharing permissions are associated with a given Persona (similar to a login) in the Radix Wallet. This means that in order to request `ongoing` data, a `identityAddress` must be included. + +Typically the dApp should begin with a `login` request which will return the `identityAddress` for the user's chosen Persona, which can be used for further requests (perhaps while the user has a valid session) + +## 💶 Accounts + +This request type is for getting one or more Radix accounts managed by the user's Radix Wallet app. You may specify the number of accounts desired, and if you require proof of ownership of the account. + +### Request + +```typescript +type NumberOfValues = { + quantifier: 'exactly' | 'atLeast' + quantity: number +} +``` + +```typescript +type AccountsRequestItem = { + challenge?: Challenge + numberOfAccounts: NumberOfValues +} +``` + +### Response + +```typescript +type Account = { + address: string + label: string + appearanceId: number +} +``` + +```typescript +type AccountProof = { + accountAddress: string + proof: Proof +} +``` + +```typescript +type Proof = { + publicKey: string + signature: string + curve: 'curve25519' | 'secp256k1' +} +``` + +```typescript +type AccountsRequestResponseItem = { + accounts: Account[] + challenge?: Challenge + proofs?: AccountProof[] +} +``` + +
+ +ongoingAccounts example + +```typescript +const result = await sdk.request({ + discriminator: 'authorizedRequest', + auth: { discriminator: 'loginWithoutChallenge' }, + ongoingAccounts: { + numberOfAccounts: { quantifier: 'atLeast', quantity: 1 }, + }, +}) + +if (result.isErr()) { + // code to handle the exception +} + +// { +// discriminator: "authorizedRequest", +// auth: { +// discriminator: loginWithoutChallenge, +// persona: Persona +// }, +// ongoingAccounts: { +// accounts: Account[] +// } +// } +const value = result.value +``` + +
+ +
+ +oneTimeAccounts example + +```typescript +const result = await walletSdk.request({ + discriminator: 'unauthorizedRequest', + oneTimeAccounts: { numberOfAccounts: { quantifier: 'atLeast', quantity: 1 } }, +}) + +if (result.isErr()) { + // code to handle the exception +} + +// { +// discriminator: "unauthorizedRequest", +// oneTimeAccounts: { +// accounts: Account[] +// } +// } +const value = result.value +``` + +
+ +
+ +with proof of ownership example + +```typescript +// hex encoded 32 random bytes +const challenge = [...crypto.getRandomValues(new Uint8Array(32))] + .map((item) => item.toString(16).padStart(2, '0')) + .join('') + +const result = await sdk.request({ + discriminator: 'authorizedRequest', + auth: { discriminator: 'loginWithoutChallenge' }, + ongoingAccounts: { + challenge, + numberOfAccounts: { quantifier: 'atLeast', quantity: 1 }, + }, + oneTimeAccounts: { + challenge, + numberOfAccounts: { quantifier: 'atLeast', quantity: 1 }, + }, +}) + +if (result.isErr()) { + // code to handle the exception +} + +// { +// discriminator: "authorizedRequest", +// auth: { +// discriminator: loginWithoutChallenge, +// persona: Persona +// }, +// ongoingAccounts: { +// accounts: Account[], +// challenge, +// proofs: AccountProof[] +// }, +// oneTimeAccounts: { +// accounts: Account[], +// challenge, +// proofs: AccountProof[] +// } +// } +const value = result.value +``` + +
+ +## ℹ️ Persona Data + +This request type is for a list of personal data fields associated with the user's selected Persona. + +### Request + +```typescript +type PersonaDataRequestItem = { + isRequestingName?: boolean() + numberOfRequestedEmailAddresses?: NumberOfValues + numberOfRequestedPhoneNumbers?: NumberOfValues +} +``` + +### Response + +```typescript +type PersonaDataRequestResponseItem = { + name?: { + variant: 'eastern' | 'western' + family: string + given: string + } + emailAddresses?: NumberOfValues + phoneNumbers?: NumberOfValues +} +``` + +
+ +ongoingPersonaData example + +```typescript +const result = await sdk.request({ + discriminator: 'authorizedRequest', + auth: { discriminator: 'loginWithoutChallenge' }, + ongoingPersonaData: { + isRequestingName: true, + numberOfRequestedEmailAddresses: { quantifier: 'atLeast', quantity: 1 }, + numberOfRequestedPhoneNumbers: { quantifier: 'exactly', quantity: 1 }, + }, +}) + +if (result.isErr()) { + // code to handle the exception +} + +// { +// discriminator: "authorizedRequest", +// auth: { +// discriminator: loginWithoutChallenge, +// persona: Persona +// }, +// ongoingPersonaData: { +// name: { +// variant: 'western', +// given: 'John', +// family: 'Conner' +// }, +// emailAddresses: ['jc@resistance.ai'], +// phoneNumbers: ['123123123'] +// } +// } + +const value = result.value +``` + +
+ +
+ +oneTimePersonaData example + +```typescript +const result = await sdk.request({ + discriminator: 'unauthorizedRequest', + oneTimePersonaData: { + isRequestingName: true, + }, +}) + +if (result.isErr()) { + // code to handle the exception +} + +// { +// discriminator: "unauthorizedRequest", +// oneTimePersonaData: { +// name: { +// variant: 'eastern', +// given: 'Jet', +// family: 'Li' +// } +// } +// } + +const value = result.value +``` + +
+ +## 🗑️ Reset + +You can send a reset request to ask the user to provide new values for ongoing accounts and/or persona data. + +### Request + +```typescript +type ResetRequestItem = { + accounts: boolean + personaData: boolean +} +``` + +### Response + +A Reset request has no response. + +
+ +reset example + +```typescript +const result = await sdk.request({ + discriminator: 'authorizedRequest', + auth: { discriminator: 'loginWithoutChallenge' }, + reset: { accounts: true, personaData: true }, +}) + +if (result.isErr()) { + // code to handle the exception +} +``` + +
+ +## 🛂 Auth + +Sometimes your dApp may want a more personalized, consistent user experience and the Radix Wallet is able to login users with a Persona. + +For a pure frontend dApp without any server backend, you may simply want to request such a login from the users's wallet so that the wallet keeps track of data sharing preferences for your dApp and they don't have to re-select that data each time they connect. + +If your dApp does have a server backend and you are keeping track of users to personalize their experience, a Persona-based login provides strong proof of user identity, and the ID returned from the wallet provides a unique index for that user. + +Once your dApp has a given `identityAddress`, it may be used for future requests for data that the user has given "ongoing" permission to share. + +```typescript +type Persona = { + identityAddress: string + label: string +} +``` + +**Login** + +This request type results in the Radix Wallet asking the user to select a Persona to login to this dApp (or suggest one already used in the past there), and providing cryptographic proof of control. + +```typescript +// Hex encoded 32 random bytes +type Challenge = string +``` + +This proof comes in the form of a signed "challenge" against an on-ledger Identity component. For each Persona a user creates in the Radix Wallet, the wallet automatically creates an associated on-ledger Identity (which contains none of the personal data held in the wallet). This Identity includes a public key in its metadata, and the signature on the challenge uses the corresponding private key. ROLA (Radix Off-Ledger Authentication) may be used in your dApp backend to check if the login challenge is correct against on-ledger state. + +```typescript +type Proof = { + publicKey: string + signature: string + curve: 'curve25519' | 'secp256k1' +} +``` + +The on-ledger address of this Identity will be the `identityAddress` used to identify that user – in future queries, or perhaps in your dApp's own user database. + +If you are building a pure frontend dApp where the login is for pure user convenience, you may safely ignore the challenge and simply keep track of the `identityAddress` in the user's session for use in data requests that require it. + +**usePersona** + +If you have already identified the user via a login (perhaps for a given active session), you may specify a `identityAddress` directly without requesting a login from the wallet. + +
+ +login example + +```typescript +const result = await sdk.request({ + discriminator: 'authorizedRequest', + auth: { discriminator: 'loginWithoutChallenge' }, +}) + +if (result.isErr()) { + // code to handle the exception +} + +// { +// discriminator: "authorizedRequest", +// auth: { +// discriminator: 'loginWithoutChallenge', +// persona: Persona +// }, +// } +const value = result.value +``` + +
+
+ +login with challenge example + +```typescript +// hex encoded 32 random bytes +const challenge = [...crypto.getRandomValues(new Uint8Array(32))] + .map((item) => item.toString(16).padStart(2, '0')) + .join('') + +const result = await sdk.request({ + discriminator: 'authorizedRequest', + auth: { + discriminator: 'loginWithChallenge', + challenge, + }, +}) + +if (result.isErr()) { + // code to handle the exception +} + +// { +// discriminator: "authorizedRequest", +// auth: { +// discriminator: 'loginWithChallenge', +// persona: Persona, +// challenge: Challenge, +// proof: Proof +// }, +// } +const value = result.value +``` + +
+
+ +usePersona example + +```typescript +const result = await sdk.request({ + discriminator: 'authorizedRequest', + auth: { + discriminator: 'usePersona', + identityAddress: + 'identity_tdx_c_1p35qpky5sczp5t4qkhzecz3nm8tcvy4mz4997mqtuzlsvfvrwm', + }, +}) + +if (result.isErr()) { + // code to handle the exception +} + +// { +// discriminator: "authorizedRequest", +// auth: { +// discriminator: usePersona, +// persona: Persona +// }, +// } +const value = result.value +``` + +
+ +### Request + +```typescript +type AuthRequestItem = AuthUsePersonaRequestItem | AuthLoginRequestItem +``` + +```typescript +type AuthUsePersonaRequestItem = { + discriminator: 'usePersona' + identityAddress: string +} +``` + +```typescript +type AuthLoginRequestItem = + | AuthLoginWithoutChallengeRequestItem + | AuthLoginWithChallengeRequestItem +``` + +```typescript +type AuthLoginWithoutChallengeRequestItem = { + discriminator: 'loginWithoutChallenge' +} +``` + +```typescript +type AuthLoginWithChallengeRequestItem = { + discriminator: 'loginWithChallenge' + challenge: Challenge +} +``` + +### Response + +```typescript +type AuthRequestResponseItem = + | AuthUsePersonaRequestResponseItem + | AuthLoginRequestResponseItem +``` + +```typescript +type AuthUsePersonaRequestResponseItem = { + discriminator: 'usePersona' + persona: Persona +} +``` + +```typescript +type AuthLoginRequestResponseItem = + | AuthLoginWithoutChallengeResponseRequestItem + | AuthLoginWithChallengeRequestResponseItem +``` + +```typescript +type AuthLoginWithoutChallengeRequestResponseItem = { + discriminator: 'loginWithoutChallenge' + persona: Persona +} +``` + +```typescript +type AuthLoginWithChallengeRequestResponseItem = { + discriminator: 'loginWithChallenge' + persona: Persona + challenge: Challenge + proof: Proof +} +``` + +# 💸 Send transaction + +Your dApp can send transactions to the user's Radix Wallet for them to review, sign, and submit them to the Radix Network. + +Radix transactions are built using "transaction manifests", that use a simple syntax to describe desired behavior. See [documentation on transaction manifest commands here](https://docs-babylon.radixdlt.com/main/scrypto/transaction-manifest/intro.html). + +It is important to note that what your dApp sends to the Radix Wallet is actually a "transaction manifest stub". It is completed before submission by the Radix Wallet. For example, the Radix Wallet will automatically add a command to lock the necessary amount of network fees from one of the user's accounts. It may also add "assert" commands to the manifest according to user desires for expected returns. + +**NOTE:** Information will be provided soon on a ["comforming" transaction manifest stub format](https://docs-babylon.radixdlt.com/main/standards/comforming-transactions.html) that ensures clear presentation and handling in the Radix Wallet. + +## Build transaction manifest + +We recommend using template strings for constructing simpler transaction manifests. If your dApp is sending complex manifests a manifest builder can be found in [TypeScript Radix Engine Toolkit](https://github.com/radixdlt/typescript-radix-engine-toolkit#building-manifests) + +## sendTransaction + +This sends the transaction manifest stub to a user's Radix Wallet, where it will be completed, presented to the user for review, signed as required, and submitted to the Radix network to be processed. + +```typescript +type SendTransactionInput = { + transactionManifest: string + version: number + blobs?: string[] + message?: string +} +``` + +- **requires** transactionManifest - specify the transaction manifest +- **requires** version - specify the version of the transaction manifest +- **optional** blobs - used for deploying packages +- **optional** message - message to be included in the transaction + +
+ +sendTransaction example + +```typescript +const result = await sdk.sendTransaction({ + version: 1, + transactionManifest: '...', +}) + +if (result.isErr()) { + // code to handle the exception +} + +const transactionIntentHash = result.value.transactionIntentHash +``` + +
+ +## Errors + +| Error type | Description | Message | +| :----------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | +| rejectedByUser | User has rejected the request in the wallet | | +| missingExtension | Connector extension is not detected | | +| canceledByUser | User has canceled the request | | +| walletRequestValidation | SDK has constructed an invalid request | | +| walletResponseValidation | Wallet sent an invalid response | | +| wrongNetwork | Wallet is currently using a network with a network ID that does not match the one specified in request from Dapp (inside metadata) | "Wallet is using network ID: \(currentNetworkID), request sent specified network ID: \(requestFromP2P.requestFromDapp.metadata.networkId)." | +| failedToPrepareTransaction | Failed to get Epoch for Transaction Header | | +| failedToCompileTransaction | Failed to compile TransactionIntent or any other later form to SBOR using EngineToolkit | | +| failedToSignTransaction | Failed to sign any form of the transaction either with keys for accounts or with notary key, or failed to convert the signature to by EngineToolkit require format | | +| failedToSubmitTransaction | App failed to submit the transaction to Gateway for some reason | | +| failedToPollSubmittedTransaction | App managed to submit transaction but got error while polling it | "TXID: " | +| submittedTransactionWasDuplicate | App submitted a transaction and got informed by Gateway it was duplicated | "TXID: " | +| submittedTransactionHasFailedTransactionStatus | App submitted a transaction to Gateway and polled transaction status telling app it was a failed transaction | "TXID: " | +| submittedTransactionHasRejectedTransactionStatus | App submitted a transaction to Gateway and polled transaction status telling app it was a rejected transaction | "TXID: " | + +# License + +The Wallet SDK binaries are licensed under the [Radix Software EULA](http://www.radixdlt.com/terms/genericEULA). + +The Wallet SDK code is released under [Apache 2.0 license](LICENSE). + + Copyright 2023 Radix Publishing Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. + + You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + See the License for the specific language governing permissions and limitations under the License. diff --git a/examples/simple-dapp/public/.well-known/radix.json b/examples/simple-dapp/public/.well-known/radix.json index 2a1b0c64..4eb9817d 100644 --- a/examples/simple-dapp/public/.well-known/radix.json +++ b/examples/simple-dapp/public/.well-known/radix.json @@ -1,8 +1,6 @@ { "callbackPath": "/connect", "dApps": [ - { - "dAppDefinitionAddress": "account_tdx_2_12yf9gd53yfep7a669fv2t3wm7nz9zeezwd04n02a433ker8vza6rhe" - } + {} ] } \ No newline at end of file diff --git a/examples/simple-dapp/vite.config.ts b/examples/simple-dapp/vite.config.ts index 1bef5d60..4965dc5b 100644 --- a/examples/simple-dapp/vite.config.ts +++ b/examples/simple-dapp/vite.config.ts @@ -19,7 +19,12 @@ fs.writeFileSync( ), ) -export default defineConfig({ +const plugins = [] + +if (process.env.NGROK_AUTH_TOKEN) // @ts-ignore - plugins: [ngrok(process.env.NGROK_AUTH_TOKEN)], + plugins.push(ngrok(process.env.NGROK_AUTH_TOKEN)) + +export default defineConfig({ + plugins, }) diff --git a/package-lock.json b/package-lock.json index 172b7b03..9b7f5366 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,10 @@ { - "name": "radix-connect-web", + "name": "radix-dapp-toolkit", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "radix-connect-web", + "name": "radix-dapp-toolkit", "workspaces": [ "examples/*", "packages/*" @@ -14,7 +14,7 @@ "@commitlint/config-conventional": "^17.4.2", "husky": "^8.0.3", "prettier": "^3.2.5", - "turbo": "latest" + "turbo": "^2.0.4" }, "engines": { "node": ">=18" @@ -30784,26 +30784,26 @@ } }, "node_modules/turbo": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.12.5.tgz", - "integrity": "sha512-FATU5EnhrYG8RvQJYFJnDd18DpccDjyvd53hggw9T9JEg9BhWtIEoeaKtBjYbpXwOVrJQMDdXcIB4f2nD3QPPg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.0.4.tgz", + "integrity": "sha512-Ilme/2Q5kYw0AeRr+aw3s02+WrEYaY7U8vPnqSZU/jaDG/qd6jHVN6nRWyd/9KXvJGYM69vE6JImoGoyNjLwaw==", "dev": true, "bin": { "turbo": "bin/turbo" }, "optionalDependencies": { - "turbo-darwin-64": "1.12.5", - "turbo-darwin-arm64": "1.12.5", - "turbo-linux-64": "1.12.5", - "turbo-linux-arm64": "1.12.5", - "turbo-windows-64": "1.12.5", - "turbo-windows-arm64": "1.12.5" + "turbo-darwin-64": "2.0.4", + "turbo-darwin-arm64": "2.0.4", + "turbo-linux-64": "2.0.4", + "turbo-linux-arm64": "2.0.4", + "turbo-windows-64": "2.0.4", + "turbo-windows-arm64": "2.0.4" } }, "node_modules/turbo-darwin-64": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.12.5.tgz", - "integrity": "sha512-0GZ8reftwNQgIQLHkHjHEXTc/Z1NJm+YjsrBP+qhM/7yIZ3TEy9gJhuogDt2U0xIWwFgisTyzbtU7xNaQydtoA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.0.4.tgz", + "integrity": "sha512-x9mvmh4wudBstML8Z8IOmokLWglIhSfhQwnh2gBCSqabgVBKYvzl8Y+i+UCNPxheCGTgtsPepTcIaKBIyFIcvw==", "cpu": [ "x64" ], @@ -30814,9 +30814,9 @@ ] }, "node_modules/turbo-darwin-arm64": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.12.5.tgz", - "integrity": "sha512-8WpOLNNzvH6kohQOjihD+gaWL+ZFNfjvBwhOF0rjEzvW+YR3Pa7KjhulrjWyeN2yMFqAPubTbZIGOz1EVXLuQA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.0.4.tgz", + "integrity": "sha512-/B1Ih8zPRGVw5vw4SlclOf3C/woJ/2T6ieH6u54KT4wypoaVyaiyMqBcziIXycdObIYr7jQ+raHO7q3mhay9/A==", "cpu": [ "arm64" ], @@ -30827,9 +30827,9 @@ ] }, "node_modules/turbo-linux-64": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.12.5.tgz", - "integrity": "sha512-INit73+bNUpwqGZCxgXCR3I+cQsdkQ3/LkfkgSOibkpg+oGqxJRzeXw3sp990d7SCoE8QOcs3iw+PtiFX/LDAA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.0.4.tgz", + "integrity": "sha512-6aG670e5zOWu6RczEYcB81nEl8EhiGJEvWhUrnAfNEUIMBEH1pR5SsMmG2ol5/m3PgiRM12r13dSqTxCLcHrVg==", "cpu": [ "x64" ], @@ -30840,9 +30840,9 @@ ] }, "node_modules/turbo-linux-arm64": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.12.5.tgz", - "integrity": "sha512-6lkRBvxtI/GQdGtaAec9LvVQUoRw6nXFp0kM+Eu+5PbZqq7yn6cMkgDJLI08zdeui36yXhone8XGI8pHg8bpUQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.0.4.tgz", + "integrity": "sha512-AXfVOjst+mCtPDFT4tCu08Qrfv12Nj7NDd33AjGwV79NYN1Y1rcFY59UQ4nO3ij3rbcvV71Xc+TZJ4csEvRCSg==", "cpu": [ "arm64" ], @@ -30853,9 +30853,9 @@ ] }, "node_modules/turbo-windows-64": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.12.5.tgz", - "integrity": "sha512-gQYbOhZg5Ww0bQ/bC0w/4W6yQRwBumUUnkB+QPo15VznwxZe2a7bo6JM+9Xy9dKLa/kn+p7zTqme4OEp6M3/Yg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.0.4.tgz", + "integrity": "sha512-QOnUR9hKl0T5gq5h1fAhVEqBSjpcBi/BbaO71YGQNgsr6pAnCQdbG8/r3MYXet53efM0KTdOhieWeO3KLNKybA==", "cpu": [ "x64" ], @@ -30866,9 +30866,9 @@ ] }, "node_modules/turbo-windows-arm64": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.12.5.tgz", - "integrity": "sha512-auvhZ9FrhnvQ4mgBlY9O68MT4dIfprYGvd2uPICba/mHUZZvVy5SGgbHJ0KbMwaJfnnFoPgLJO6M+3N2gDprKw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.0.4.tgz", + "integrity": "sha512-3v8WpdZy1AxZw0gha0q3caZmm+0gveBQ40OspD6mxDBIS+oBtO5CkxhIXkFJJW+jDKmDlM7wXDIGfMEq+QyNCQ==", "cpu": [ "arm64" ], diff --git a/package.json b/package.json index 5d56525b..47f58d0b 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "radix-connect-web", + "name": "radix-dapp-toolkit", "private": true, "scripts": { "build": "turbo build", @@ -16,12 +16,12 @@ "@commitlint/config-conventional": "^17.4.2", "husky": "^8.0.3", "prettier": "^3.2.5", - "turbo": "latest" + "turbo": "^2.0.4" }, "engines": { "node": ">=18" }, - "packageManager": "npm@9.6.7", + "packageManager": "npm@9.9.3", "workspaces": [ "examples/*", "packages/*" diff --git a/packages/connect-button/README.md b/packages/connect-button/README.md index e68ae718..30484bc1 100644 --- a/packages/connect-button/README.md +++ b/packages/connect-button/README.md @@ -1,35 +1,23 @@ +# Radix Logo √ Connect Button + [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) The √ Connect Button is a framework agnostic web component to help developers connect users and their Radix Wallet to their dApps. It appears as a consistent, Radix-branded UI element that helps users identify your dApp website as a Radix dApp. When used with [Radix dApp Toolkit](https://github.com/radixdlt/radix-dapp-toolkit) it is compatible with the Radix Wallet – and it automatically provides a consistent user experience for users to connect with their wallet and see the current status of the connection between dApp and Radix Wallet. -- [Installation](#installation) +- [ √ Connect Button](#--connect-button) - [Usage](#usage) - [Getting started](#getting-started) - - [Properties](#properties) - - [Events](#events) + - [Setting properties programmatically](#setting-properties-programmatically) + - [Events](#events) - [License](#license) -# Installation - -**Using NPM** - -```bash -npm install @radixdlt/connect-button -``` - -**Using Yarn** - -```bash -yarn add @radixdlt/connect-button -``` - # Usage ## Getting started -Add the `` element in your HTML code and call the configuration function. +Add the `` element in your HTML code and instantiate [Radix Dapp Toolkit](../dapp-toolkit/README.md). ```html @@ -44,7 +32,9 @@ Add the `` element in your HTML code and call the config ``` -### Properties +## Setting properties programmatically + +To manually set properties on the √ Connect Button. ```typescript const radixConnectButton = document.querySelector('radix-connect-button')! @@ -95,7 +85,7 @@ type ConnectButtonProperties = { - activeTab - changes active tab inside popover - mode - set styling of a popover -### Events +## Events ```typescript type ConnectButtonEvents = { diff --git a/packages/connect-button/package.json b/packages/connect-button/package.json index deb5e4eb..caf23d67 100644 --- a/packages/connect-button/package.json +++ b/packages/connect-button/package.json @@ -17,7 +17,7 @@ "Alex Stelea ", "Dawid Sowa " ], - "bugs": "https://github.com/radixdlt/connect-button/issues", + "bugs": "https://github.com/radixdlt/radix-dapp-toolkit/issues", "license": "SEE LICENSE IN RADIX-SOFTWARE-EULA", "type": "module", "main": "./dist/connect-button.js", @@ -82,4 +82,4 @@ "url": "git+https://github.com/radixdlt/radix-dapp-toolkit.git", "directory": "packages/connect-button" } -} \ No newline at end of file +} diff --git a/packages/dapp-toolkit/README.md b/packages/dapp-toolkit/README.md index fd2c81f8..5c85cd3e 100644 --- a/packages/dapp-toolkit/README.md +++ b/packages/dapp-toolkit/README.md @@ -1,5 +1,8 @@ +# Radix Logo Radix dApp toolkit + [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) +- [ Radix dApp toolkit](#-radix-dapp-toolkit) - [What is Radix dApp Toolkit?](#what-is-radix-dapp-toolkit) - [Resources](#resources) - [Building a dApp frontend](#building-a-dapp-frontend) @@ -67,12 +70,6 @@ The current version only supports desktop browser webapps with requests made via npm install @radixdlt/radix-dapp-toolkit ``` -**Using Yarn** - -```bash -yarn add @radixdlt/radix-dapp-toolkit -``` - # Usage ## Getting started diff --git a/turbo.json b/turbo.json index e62b2a86..5b6a338d 100644 --- a/turbo.json +++ b/turbo.json @@ -1,14 +1,22 @@ { "$schema": "https://turbo.build/schema.json", - "globalDependencies": ["**/.env.*local"], - "pipeline": { + "globalDependencies": [ + "**/.env.*local" + ], + "tasks": { "build": { "cache": false, - "dependsOn": ["^build"], - "outputs": ["dist/**"] + "dependsOn": [ + "^build" + ], + "outputs": [ + "dist/**" + ] }, "lint": { - "dependsOn": ["^lint"] + "dependsOn": [ + "^lint" + ] }, "dev": { "cache": false, @@ -16,8 +24,12 @@ }, "deploy": { "cache": false, - "dependsOn": ["^build"], - "outputs": ["dist/**"] + "dependsOn": [ + "^build" + ], + "outputs": [ + "dist/**" + ] }, "test": { "cache": false