diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..e061aeb3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,10 @@ + + +--- + +Before we can merge this PR, please make sure that all the following items have been checked off: + +- [ ] Linked to an issue with discussion and accepted design OR have an explanation in the PR that describes this work. +- [ ] Added **one** line describing your change in [`CHANGELOG.md`](https://github.com/manta-network/sdk/blob/main/CHANGELOG.md) and added the appropriate `L-` label to the PR. +- [ ] Re-reviewed `Files changed` in the GitHub PR explorer. + diff --git a/.github/RELEASE_TEMPLATE.md b/.github/RELEASE_TEMPLATE.md new file mode 100644 index 00000000..5f1c0e3f --- /dev/null +++ b/.github/RELEASE_TEMPLATE.md @@ -0,0 +1,8 @@ +**Each reviewer needs to check that these conditions are met before approving the PR.** + +- [ ] Checked that the release is on the correct branch name of the form `release-vX.Y.Z` and the PR title matches `Release vX.Y.Z` +- [ ] Added the `L-skip` label and the relevant `release` label to this PR +- [ ] Updated the [`CHANGELOG.md`](https://github.com/manta-network/sdk/blob/main/CHANGELOG.md) +- [ ] Updated the version number in the following files: + - [ ] `manta-js/package/package.json` + diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..5cae6448 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,27 @@ +changelog: + exclude: + labels: + - L-skip + categories: + - title: Added + labels: + - L-added + - title: Changed + labels: + - L-changed + - title: Deprecated + labels: + - L-deprecated + - title: Removed + labels: + - L-removed + - title: Fixed + labels: + - L-fixed + - title: Security + labels: + - L-security + - title: Other Unsorted Updates + labels: + - "*" + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..14bba8fc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,18 @@ +name: Release +on: + push: + branches: + - 'main' +jobs: + release-on-push: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: rymndhng/release-on-push-action@v0.27.0 + with: + bump_version_scheme: norelease + tag_prefix: v + use_github_release_notes: true + max_commits: 128 + diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +node_modules diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..311d90fb --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "printWidth": 80, + "singleQuote": true, + "trailingComma": "all", + "proseWrap": "never", + "overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }], + "importOrderSeparation": true +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..4383d399 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Added + +- [\#114](https://github.com/Manta-Network/sdk/pull/114) Refactor SDK logic && Add build SBT posts method, and getIdentityProof && Add docs and examples on how to connect Manta Wallet && Add how to use SDK docs and examples +- [\#108](https://github.com/Manta-Network/sdk/pull/108) Added initial sync method. +- [\#105](https://github.com/Manta-Network/sdk/pull/105) Save while syncing. +- [\#85](https://github.com/Manta-Network/sdk/pull/85) Key-dependent signer function APIs. +- [\#71](https://github.com/Manta-Network/sdk/pull/71) Update to dense pull ledger diff +- [\#51](https://github.com/Manta-Network/sdk/pull/51) Update to MantaPay v1 + +### Changed +- [\#103](https://github.com/Manta-Network/sdk/pull/103) Update manta-rs to v0.5.12. +- [\#102](https://github.com/Manta-Network/sdk/pull/102) Include sink accounts in ToPublic. Removes `assetIdToUInt8Array` function as it encodes values < 255 incorrectly, now changed to use polkadot.js utility function. +- [\#88](https://github.com/Manta-Network/sdk/pull/88) Update API wording to be consistent with company-wide language. + +### Deprecated + +### Removed + +### Fixed + +### Security diff --git a/README.md b/README.md index 244efcaf..9b32847d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The SDK includes Javascript bindings to the Wallet API. ## Javascript Wallet Bindings -- [`wallet`](wallet) +- [`wallet`](./manta-js/package/src/wallet) The core logic of the wallet is written in Rust and built from [`manta-rs`](https://github.com/manta-network/manta-rs). To integrate it into Javascript we use the `wasm-bindgen` crate to build the WASM Foreign-Function Interface and then call the WASM functions from Javascript. diff --git a/manta-js/CHANGELOG.md b/manta-js/CHANGELOG.md deleted file mode 100644 index 4be2aae5..00000000 --- a/manta-js/CHANGELOG.md +++ /dev/null @@ -1,36 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -### Added -- [\#108](https://github.com/Manta-Network/sdk/pull/108) Added initial sync method. -- [\#105](https://github.com/Manta-Network/sdk/pull/105) Save while syncing. -- [\#85](https://github.com/Manta-Network/sdk/pull/85) Key-dependent signer function APIs. - -### Changed -- [\#103](https://github.com/Manta-Network/sdk/pull/103) Update manta-rs to v0.5.12. - -### Deprecated - -### Removed - -### Fixed - -### Security - -## [0.0.0] 2022-12-21 - -### Added -- [\#51](https://github.com/Manta-Network/manta-signer/pull/51) Initial version of manta.js -## [2.0.0] 2023-2-24 - -### Changed -- [\#86](https://github.com/Manta-Network/manta-signer/pull/86) Allow the PrivateWallet class to be initialized without polkadot.js api being connected to a node - -## [3.0.0] 2023-3-7 - -### Changed -[\#102](https://github.com/Manta-Network/sdk/pull/102) Include sink accounts in ToPublic. Removes `assetIdToUInt8Array` function as it encodes values < 255 incorrectly, now changed to use polkadot.js utility function. \ No newline at end of file diff --git a/manta-js/README.md b/manta-js/README.md index 7d87fa03..c6a5981e 100644 --- a/manta-js/README.md +++ b/manta-js/README.md @@ -1,280 +1,30 @@ -# Manta JS +# Manta SDK -This package implements a Javascript SDK for connecting with the Manta Network. +## Quick Start -## Installation +- ### If you want to connect Manta Wallet in your dApp, build private-related transactions. + - Docs: [how-to-use-manta-private-wallet-in-dapp](./docs/how-to-use-manta-private-wallet-in-dapp.md) + - Example: [extension-example](./examples/extension-example) + - Online Website: [https://2076b1.csb.app](https://2076b1.csb.app) + - Manta Wallet Download: [Chrome WebStore](https://chrome.google.com/webstore/detail/enabgbdfcbaehmbigakijjabdpdnimlg), [Beta Packages](https://github.com/Manta-Network/manta-extension/actions) +- ### If you want to test the SDK or integrate the SDK into your wallet + - Docs: [how-to-use-manta-js-sdk](./docs/how-to-use-manta-js-sdk.md) + - Example: [sdk-example](./examples/sdk-example) +- ### If you want to test zkSBT related functions + - Docs: [how-to-mint-zk-sbt](./docs/how-to-mint-zk-sbt.md) -```sh -yarn install manta.js -``` - -> If using sdk in a node.js environment please go to [Node Specific](#node-specific) - -### Local Development - -1. `git clone https://github.com/Manta-Network/sdk.git` -2. `cd sdk/manta-js/package` -3. `yarn` -4. `yarn build-all` (Note: go to [Node Specific](#node-specific) if running in node.js environment) -5. add `"manta.js": "file:/{LOCAL PATH OF sdk/manta-js/package}` to your project's package.json -6. `yarn upgrade manta.js` in your project's directory - -# Usage - -All methods are called through the `MantaPrivateWallet` class. - -`manta-signer` must be installed and running. - -> If running `manta-signer` on dev mode, you should use the following features: `features=unsafe-disable-cors,disable-restart`. - -Refer to `/examples` for more thorough examples, and how to run them. - -## Node Specific - -If running in node.js the wasm module assumes browser DOM exists, you must export Web API functions from node in your project as seen below. - -Node supported package is exported using the following path: `manta.js/node` see code snippit below. +## How to build -This node package is only compatible with node16 and up, if using typescript you must set ```"moduleResolution": "node16"``` in `tsconfig.json` or as a compiler flag +``` sh +# If wasm-pack is not installed, please install wasm-pack first, https://rustwasm.github.io/wasm-pack/installer/ +curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -```javascript -import { MantaPrivateWallet, Environment, Network } from 'manta.js/node' -import fetch from 'node-fetch'; - -// @ts-ignore -global.fetch = fetch; -// @ts-ignore -global.Headers = fetch.Headers; -// @ts-ignore -global.Request = fetch.Request; -// @ts-ignore -global.Response = fetch.Response; -``` - -## Initialization - -The `Environment` flag specifies whether to connect to a local node, or the use an actual node from the network. - -The `Network` flag specifies which network to connect to, either `Dolphin`, `Calamari` or `Manta`. - -To switch between environments and networks, a new `MantaPrivateWallet` instance should be created. - -```javascript -import { MantaPrivateWallet, Environment, Network } from 'manta.js'; - -const prodEnvironment = sdk.Environment.Production; -const dolphinNetwork = sdk.Network.Dolphin; - -const privateWalletConfig: PrivateWalletConfig = { - environment: prodEnvironment, - network: dolphinNetwork, -}; - -const privateWallet = await MantaPrivateWallet.init(privateWalletConfig); +cd ./package +yarn +yarn build-browser ``` -`PrivateWalletConfig` has several optional arguments: - -- `loggingEnabled`, whether or not non-error logging to console should occur, set by default to `false`. -- `maxSendersPullSize`, set by default to `4096`. -- `maxReceiversPullSize`, set by default to `4096`. -- `pullCallback`, callback function after a pull has occured, set by default to `null`. -- `errorCallback`, callback function after an error has occured, set by default to `null`. - -## Transacting - -After initialization of the `MantaPrivateWallet` class, `initalWalletSync()` must be called before any transactions are made. - -After every single transaction, to get the latest data from the ledger, `walletSync()` must be called. - -A PolkadotJS `Signer` and public PolkadotJS `Address` should be provided to every function that requires transacting. Below is an example of how to get these values, this example assumes that the Polkadot JS extension is installed and contains an existing account. - -### Polkadot JS Transaction Parameters - -```javascript -import { web3Accounts, web3Enable, web3FromSource } from '@polkadot/extension-dapp'; - -// Get Polkadot JS Signer and Polkadot JS account address. -const getPolkadotSignerAndAddress = async () => { - const extensions = await web3Enable('Polkadot App'); - if (extensions.length === 0) { - throw new Error("Polkadot browser extension missing. https://polkadot.js.org/extension/"); - } - const allAccounts = await web3Accounts(); - let account = allAccounts[0]; - - const injector = await web3FromSource(account.meta.source); - const polkadotSigner = injector.signer; - const polkadotAddress = account.address; - return { - polkadotSigner, - polkadotAddress - } -} -``` - -Below is an example of how to transact using fungible tokens, there are four main methods that `manta-pay` provides: -- `toPrivateSend(asset, amount, polkadotSigner, polkadotAddress)` -- `privateTransferSend(asset, amount, receiver, polkadotSigner, polkadotAddress)` -- `toPublicSend(asset, amount, polkadotSigner, polkadotAddress)` -- `publicTransfer(asset, amount, destinationAddress, polkadotSigner, polkadotAddress)` - -> This example assumes the `polkadotAddress` already has associated public funds. - -### To Private - -This example converts 10 public DOL tokens to 10 private DOL tokens. - -```javascript -// DOL token -const assetId = new BN("1"); -const amount = new BN("10000000000000000000"); - -// Sync with most recent ledger state. -await privateWallet.initialWalletSync(); - -// Get private address -const privateAddress = await privateWallet.getPrivateAddress(); - -// Get private balance of DOL for given private address -const privateBalance = await privateWallet.getPrivateBalance(assetId); - -// Privatize 10 DOL to 10 pDOL -await privateWallet.toPrivateSend(assetId, amount, polkadotSigner, polkadotAddress); - -// Sync to get latest data after the transaction and check that it was successful. -await privateWallet.walletSync(); - -// The private balance of pDOL should be incremented by 10 units. -const newPrivateBalance = await mantaSdk.getPrivateBalance(assetId); -``` - -### Private Transfer - -This example transfers 10 private private pDOL to another address. - -```javascript -// DOL token -const assetId = new BN("1"); -const amount = new BN("10000000000000000000"); - -// Sync with most recent ledger state. -await privateWallet.initialWalletSync(); - -// Get private address -const privateAddress = await privateWallet.getPrivateAddress(); - -// Private Transfer of 10 pDOL to another private address -const examplePrivateAddress = "3UG1BBvv7viqwyg1QKsMVarnSPcdiRQ1aL2vnTgwjWYX"; -await privateWallet.privateTransferSend(assetId, amount, examplePrivateAddress, polkadotSigner, polkadotAddress); - -// Sync to get latest data after transaction and check that it was successful. -await privateWallet.walletSync(); - -// The private balance of pDOL should decrease by 10 units. -const newPrivateBalance = await privateWallet.getPrivateBalance(assetId); -``` - -### To Public - -This example converts 5 private pDOL to 5 public DOL. - -```javascript -// DOL token -const assetId = new BN("1"); -const amount = new BN("5000000000000000000"); - -// Sync with most recent ledger state. -await privateWallet.initialWalletSync(); - -// Get private address -const privateAddress = await privateWallet.getPrivateAddress(); - -// Get private balance of DOL for given private address -const privateBalance = await privateWallet.getPrivateBalance(assetId); - -// Convert 5 pDOL back to DOL -await privateWallet.toPublicSend(assetId, amount, polkadotSigner, polkadotAddress); - -// Sync to get latest data after transaction and check that it was successful. -await privateWallet.walletSync(); - -// The private balance of pDOL should decrease by 5 units. -const newPrivateBalance = await privateWallet.getPrivateBalance(assetId); -``` - -### Manta Utilities - -There also exists a `MantaUtilities` class with additional functions. Mainly for interacting publicly with the Manta ecosystem. This example demonstrates these functions. This example assumes the `MantaPrivateWallet` class has already been initialized, as well as `polkadotAddress` and `polkadotSigner`. - -```javascript -import { MantaUtilities } from "manta.js"; - -// Get signer version, signer must be running. -const signerVersion = await MantaUtilities.getSignerVersion(); - -// DOL token -const assetId = new BN("1"); - -// Get public balance of DOL for `polkadotAddress`. -const oldPublicBalance = await MantaUtilities.getPublicBalance(privateWallet.api, assetId, polkadotAddress); - -// Public transfer of 5 DOL to `destinationAddress`. -const destinationAddress = "dmyhNmYL13N7ZKcVYqBQhvrk5kSfrKZUmrjX9vAaM4846bWKR"; -const amount = new BN("5000000000000000000"); -await MantaUtilities.publicTransfer(privateWallet.api, assetId, amount, destinationAddress, polkadotAddress, polkadotSigner); - -// Public balance should now be 5 DOL less that `oldPublicBalance`. -const newPublicBalance = await MantaUtilities.getPublicBalance(privateWallet.api, assetId, polkadotAddress); -``` - -### Sign and manually send transaction - -In some cases you may not want to send transaction to the ledger through manta.js, thus you can get sign result after manta-signer has signed the transaction and send the transaction yourself. This is done by using the `toPrivateBuild`, `privateTransferBuild`, `publicTransferBuild` functions. - -This example returns the signed transaction of `toPrivate` for 10 DOL. - -```javascript -const assetId = new BN("1"); -const amount = new BN("10000000000000000000"); - -const env = sdk.Environment.Development; -const net = sdk.Network.Dolphin; -const privateWallet = await sdk.init(env,net); - -const privateAddress = await privateWallet.privateAddress(); -console.log("The private address is: ", privateAddress); - -await privateWallet.initalWalletSync(); - -const initalPrivateBalance = await privateWallet.privateBalance(assetId); -console.log("The inital private balance is: ", initalPrivateBalance.toString()); - -const signResult = await privateWallet.toPrivateBuild(assetId, amount, polkadotSigner, polkadotAddress); - -console.log("The result of the signing: ", JSON.stringify(signResult.transactions)); -``` - -This can also be done for all other transaction types: - -```javascript -const toPrivateSignResult = await privateWallet.toPrivateBuild(assetId, amount, polkadotSigner, polkadotAddress); -const toPublicSignResult = await privateWallet.toPublicBuild(assetId, amount, polkadotSigner, polkadotAddress); -const privateTransferSignResult = await privateWallet.privateTransferBuild(assetId, amount, privateAddress, polkadotSigner, polkadotAddress); -``` - -Then you can use the signResult to submit transaction by your self. Here is an example on how to verify the `toPrivateBuild` sign result is valid: - -Copy the transaction to polkadot.js `Extrinsic` decode: - -![extrinsic decode](./doc/to_private_decode.png) - -Switch to `Submission`: - -![extrinsic decode](./doc/to_private_extrinsic.png) - -Then submit transaction. - -![extrinsic decode](./doc/to_private_submit.png) +## Related links -You should see your extrinsic show up on polkadot.js explorer. Then you will notice an increase in your private balance. +- [manta-extension](https://github.com/manta-Network/manta-extension) Manta Wallet Extension +- [manta-rs](https://github.com/Manta-Network/manta-rs) \ No newline at end of file diff --git a/manta-js/docs/how-to-mint-zk-sbt.md b/manta-js/docs/how-to-mint-zk-sbt.md new file mode 100644 index 00000000..aea051a4 --- /dev/null +++ b/manta-js/docs/how-to-mint-zk-sbt.md @@ -0,0 +1,3 @@ +# How to mint zkSBT + +Coming Soon... \ No newline at end of file diff --git a/manta-js/docs/how-to-use-manta-js-sdk.md b/manta-js/docs/how-to-use-manta-js-sdk.md new file mode 100644 index 00000000..0a8a4f26 --- /dev/null +++ b/manta-js/docs/how-to-use-manta-js-sdk.md @@ -0,0 +1,250 @@ +# How to use manta.js SDK + +The SDK is no longer directly provided to the dApp side, but allows the wallet to integrate the SDK (may include the Node side, the wallet app, the extension wallet, etc.), because the SDK needs the user's seed phrase and the logic of accessing the wallet data. + +## Examples + +- [sdk-example](../examples/sdk-example) Check out this example to get a quicker understanding of how to use the manta.js SDK + + +## Some key Class introductions + +- [BaseWallet.ts](../package/src/BaseWallet.ts) The basic instance of all wallets, which handles some common logic +``` typescript +export type Network = 'Dolphin' | 'Calamari' | 'Manta'; + +export type SaveStorageStateToLocal = ( + palletName: PalletName, + network: Network, + data: any, +) => Promise; + +export type GetStorageStateFromLocal = ( + palletName: PalletName, + network: Network, +) => Promise; + +export interface IBaseWallet { + api: ApiPromise; + apiEndpoint: string | string[]; + apiTimeout: number; + wasm: any; + loggingEnabled: boolean; + fullParameters: any; + multiProvingContext: any; + saveStorageStateToLocal: SaveStorageStateToLocal; + getStorageStateFromLocal: GetStorageStateFromLocal; + walletIsBusy: boolean; + updateApi(apiEndpoint: string | string[], apiTimeout?: number): ApiPromise; + disconnectApi(): Promise; + log(message: string, name?: string): void; +} +``` +- [PrivateWallet.ts](../package/src/PrivateWallet.ts) The base class for all wallets, which handles the common functions of wallets +``` typescript +export type PalletName = 'mantaPay' | 'mantaSBT'; + +export interface IPrivateWallet { + palletName: PalletName; + baseWallet: IBaseWallet; + wasmWallet: Wallet; + ledgerApi: any; + initialSyncIsFinished: boolean; + isBindAuthorizationContext: boolean; + network: Network; + + initialSigner(): Promise; + setNetwork(network: Network): Promise; + loadUserSeedPhrase(seedPhrase: string): boolean; + loadAuthorizationContext(seedPhrase: string): boolean; + dropAuthorizationContext(): boolean; + dropUserSeedPhrase(): boolean; + initialWalletSync(): Promise; + initialNewAccountWalletSync(): Promise; + walletSync(): Promise; + getZkAddress(): Promise
; + getZkBalance(assetId: BN): Promise; + getMultiZkBalance(assetIds: BN[]): Promise; + resetState(): Promise; +} +``` +- [pallets/MantaPayWallet.ts](../package/src/pallets/MantaPayWallet.ts) Inherited PrivateWallet and implemented MantaPay related functions +``` typescript +export type SignedTransaction = { + posts: any; + transactionData: any; + transactions: SubmittableExtrinsic<'promise', any>[]; + txs: SubmittableExtrinsic<'promise', any>[]; +}; + +export interface IMantaPayWallet extends IPrivateWallet { + toPrivateBuild(assetId: BN, amount: BN): Promise; + privateTransferBuild( + assetId: BN, + amount: BN, + toZkAddress: Address, + ): Promise; + toPublicBuild( + assetId: BN, + amount: BN, + polkadotAddress: Address, + ): Promise; +} +``` +- [pallets/MantaSbtWallet.ts](../package/src/pallets/MantaSbtWallet.ts) Inherited PrivateWallet and implemented MantaSBT related functions +``` typescript +export type SignedMultiSbtPost = { + transactionDatas: any[]; + posts: any[]; +}; + +export interface IMantaSbtWallet extends IPrivateWallet { + multiSbtPostBuild( + sbtInfoList: SbtInfo[], + ): Promise; + getIdentityProof(virtualAsset: string, polkadotAddress: Address,): Promise; +} +``` +- [ledger-api/index.ts](../package/src/ledger-api/index.ts) Interface for pulling ledger data +``` typescript +export interface ILedgerApi { + api: ApiPromise; + palletName: PalletName; + loggingEnabled: boolean; + errorCallback: (err: Error) => void; + + initial_pull(checkpoint: any): any; + pull(checkpoint: any): any; +} +``` + +## How to use the SDK + +### 1. Get the BaseWallet instance +``` typescript +// indexedDB library, Switch to file storage or anything else if you need +import { get as getIdbData, set as setIdbData } from 'idb-keyval'; + +const baseWallet = await BaseWallet.init({ + apiEndpoint: 'wss://ws.calamari.systems', + loggingEnabled: true, + // You can download these files locally and then refer to the local path, But the name of the file cannot be changed + provingFilePath: 'https://media.githubusercontent.com/media/Manta-Network/manta-rs/main/manta-parameters/data/pay/proving', + parametersFilePath: 'https://raw.githubusercontent.com/Manta-Network/manta-rs/main/manta-parameters/data/pay/parameters', + // Store the wallet data locally + saveStorageStateToLocal: async ( + palletName: interfaces.PalletName, + network: interfaces.Network, + data: any, + ): Promise => { + try { + await setIdbData(`storage_state_${palletName}_${network}`, data); + } catch (ex) { + console.error(ex); + return false; + } + return true; + }, + // Read locally stored wallet data + getStorageStateFromLocal: async ( + palletName: interfaces.PalletName, + network: interfaces.Network, + ): Promise => { + let result: string; + try { + result = await getIdbData(`storage_state_${palletName}_${network}`); + } catch (ex) { + console.error(ex); + } + return result || null; + }, +}) +``` +### 2. Instantiate the MantaPay wallet +``` typescript +const mantaPayWallet = MantaPayWallet.init('Dolphin', baseWallet); +await mantaPayWallet.initialSigner(); +``` +### 3. Instantiate the MantaSBT wallet +``` typescript +const mantaSbtWallet = MantaSbtWallet.init('Dolphin', baseWallet); +await mantaSbtWallet.initialSigner(); +``` +### 4. Load the user seed phrase, there are two types of user permissions in the SDK: +- Related to the signature transaction, the user's seed phrase needs to be loaded +``` typescript +// Step 1: load user seed phrase +mantaPayWallet.loadUserSeedPhrase('User Seed Phrase'); +// Step 2: Execute signed transaction +await mantaPayWallet.privateTransferBuild(assetId: BN, amount: BN) +// Step 3: remove user seed phrase +mantaPayWallet.dropUserSeedPhrase(); +``` +- For wallet synchronization and account related, authorization_context needs to be loaded, which can always exist after initialization and does not need to be deleted +``` typescript +mantaPayWallet.loadAuthorizationContext(seedPhrase: string); +await mantaPayWallet.walletSync(); +await mantaPayWallet.getZkAddress(); +``` + +### 5. Initialize the synchronization wallet +``` typescript +// mantaPay +await mantaPayWallet.initialWalletSync(); +// mantaSbt +await mantaSbtWallet.initialWalletSync(); + +// Only for the initialization synchronization method of accounts without wallet data, it is not necessary to call initialWalletSync +await mantaPayWallet.initialNewAccountWalletSync() +await mantaSbtWallet.initialNewAccountWalletSync() + +``` +### 6. Execute signed transaction +> Executing signed transactions needs to be wrapped with loadUserSeedPhrase and dropUserSeedPhrase +``` typescript +// mantaPay +await mantaPayWallet.toPrivateBuild(assetId: BN, amount: BN); +await mantaPayWallet.privateTransferBuild(assetId: BN, amount: BN, toZkAddress: Address); +await mantaPayWallet.toPublicBuild(assetId: BN, amount: BN, polkadotAddress: Address ); + +// mantaSBT +export type SbtInfo = { + assetId: BN; + amount?: BN; +}; +await mantaSbtWallet.multiSbtPostBuild(sbtInfoList: SbtInfo[]); +await mantaSbtWallet.getIdentityProof(virtualAsset: string, polkadotAddress: Address); +``` + +### 7. Wallet synchronization +``` typescript +// Generally, the wallet is synchronized before and after the transaction, but it needs to be called after initialWalletSync/initialNewAccountWalletSync +await mantaPayWallet.walletSync(); +``` +### 8. Query balance +``` typescript +// mantaPay +await mantaPayWallet.getZkBalance(assetId: BN); +await mantaPayWallet.getMultiZkBalance(assetIds: BN[]); + +// mantaSBT +await mantaSbtWallet.getZkBalance(assetId: BN); +await mantaSbtWallet.getMultiZkBalance(assetIds: BN[]); +``` +### 9. Get zkAddress +``` typescript +await mantaPayWallet.getZkAddress(); +``` +### 10. Switch network +``` typescript +// mantaPay +await mantaPayWallet.setNetwork('Calamari'); +// mantaSBT +await mantaSbtWallet.setNetwork('Calamari'); +``` + +## Who uses the manta.js SDK +- [manta-extension](https://github.com/manta-Network/manta-extension) Manta Wallet Extension + +## Related links +- [manta-rs](https://github.com/Manta-Network/manta-rs) diff --git a/manta-js/docs/how-to-use-manta-private-wallet-in-dapp.md b/manta-js/docs/how-to-use-manta-private-wallet-in-dapp.md new file mode 100644 index 00000000..9f02f4a9 --- /dev/null +++ b/manta-js/docs/how-to-use-manta-private-wallet-in-dapp.md @@ -0,0 +1,167 @@ +# How to use Manta Wallet in dApp + +This doc mainly introduces the private-related content in Manta Wallet; some other content of Wallet Extension is similar to other Polkadot wallets, so this doc will not introduce too much. +> Note: For private transactions, it is no longer necessary to import manta.js on the dApp side + +## Examples + + - [`extension-example`](../examples/extension-example). Temporary online website: https://2076b1.csb.app + +## Initialize + +There are two ways to use Manta Wallet. + +1. Using the NPM package of [`@talismn/connect-wallets`](https://www.npmjs.com/package/@talismn/connect-wallets), Manta Wallet has not been officially launched yet, and a PR cannot be submitted to the [@talismn/connect](https://github.com/TalismanSociety/talisman-connect) project. Currently use [`manta-extension-connect`](https://www.npmjs.com/package/manta-extension-connect) instead. + +``` typescript +import { getWallets } from 'manta-extension-connect' + +const selectedWallet = getWallets().find((wallet) => wallet.extensionName === 'manta-wallet-js'); +await selectedWallet.enable('dApp name'); + +const mantaWallet = selectedWallet?.extension; +const privateWallet = mantaWallet?.privateWallet; +``` +2. Use the `privateWallet` object injected in `window` +``` typescript +const injectedMantaWallet = window.injectedWeb3['manta-wallet-js'] as InjectedWeb3; +const mantaWallet = await injectedMantaWallet.enable('dApp name'); + +const privateWallet = mantaWallet?.privateWallet; +``` + +## TypeScript type support + +Just import the [interfaces.ts](../examples/extension-example/src/interfaces.ts) file, which will be published to npm later. + +``` typescript +const privateWallet = privateWallet as InjectedPrivateWallet; +``` + +## Get zkAddress + +``` typescript +const accounts = await mantaWallet.accounts.get(); +if (!accounts || accounts.length <= 0) { + return; +} +const { address as publicAddress, zkAddress } = accounts[0]; +``` + +## Subscribe to the State of the wallet + +Through State, you can know the internal state of the private wallet, and dApp can do corresponding processing according to the state value + +``` typescript +const [walletState, setWalletState] = useState(null); +const unSub = privateWallet.subscribeWalletState(setWalletState); + +// walletState will be like: +{ + isWalletInitialized: false, // Whether the instance of mantaPay and mantaSBT has been initialized + isWalletAuthorized: false, // Whether the private wallet has been authorized and whether the auth_context has been injected + isWalletReady: false, // Whether the private wallet is ready and whether the ledger has been synchronized + isWalletBusy: false, // Is the wallet busy +} +``` + +## MantaPay related functions + +### Get token balance + +``` typescript + +const assetId = '1' +const network = 'Calamari' +// Get the token balance in MantaPay +const balance = await privateWallet.getZkBalance({ network, assetId }); +// balance will be like '100000000000' + +const assetIds = ['1', '8', '9']; +// Get multiple Token Balance in MantaPay +const balanceList = await privateWallet.getMultiZkBalance({ network, assetIds }); +// balance will be like ['100000000000', '0', '0'] +``` + +### ToPrivate +``` typescript +const amount = '100'; +const decimals = 12; + +// sync wallet is required before build +await privateWallet.walletSync(); + +// build to private transaction +const txHexList = await privateWallet.toPrivateBuild({ + assetId, + amount: new BigNumber(10).pow(decimals).times(amount).toFixed(), + polkadotAddress: publicAddress, + network, +}); +// txHexList will be like: ['0xaaaab12332131'] +``` + +### PrivateTransfer +``` typescript +const receiveZkAddress = 'gUhdkKjmbQHup8yEDjRs4kNWMWxfn18hC6ThQcFW6DW'; + +// build private transfer transaction +const txHexList = await privateWallet.privateTransferBuild({ + assetId, + amount: new BigNumber(10).pow(decimals).times(amount).toFixed(), + polkadotAddress: publicAddress, + toZkAddress: receiveZkAddress, + network, +}); +``` + +### ToPublic +``` typescript + +// build to public transaction +const txHexList = await privateWallet.toPublicBuild({ + assetId, + amount: new BigNumber(10).pow(decimals).times(amount).toFixed(), + polkadotAddress: publicAddress, + network, +}); +``` + +### signAndSend transaction + +``` typescript +// signAndSend transaction +for (let i = 0; i < txHexList.length; i++) { + const tx = api.tx(txHexList[i]); + await tx?.signAndSend(publicAddress, () => {}); +} +``` + +## MantaSBT related functions + +### Get token balance + +``` typescript +// Get the token balance in MantaSBT +const balance = await privateWallet.getZkSbtBalance({ network, assetId }); +// Get multiple Token Balance in MantaSBT +const balance = await privateWallet.getMultiZkSbtBalance({ network, assetIds }); +``` + +### build multiSBT posts +``` typescript +const sbtInfoList = [ + { assetId: '1', amount: '1' }, + { assetId: '2', amount: '1' }, +]; +const { posts, transactionDatas } = await privateWallet.multiSbtPostBuild({ sbtInfoList, network }); +``` + +### get identity proof +``` typescript +await privateWallet.getSbtIdentityProof({ + virtualAsset: '{"identifier":{...}}', + polkadotAddress: publicAddress, + network, +}); +``` diff --git a/manta-js/doc/to_private_decode.png b/manta-js/docs/images/to_private_decode.png similarity index 100% rename from manta-js/doc/to_private_decode.png rename to manta-js/docs/images/to_private_decode.png diff --git a/manta-js/doc/to_private_extrinsic.png b/manta-js/docs/images/to_private_extrinsic.png similarity index 100% rename from manta-js/doc/to_private_extrinsic.png rename to manta-js/docs/images/to_private_extrinsic.png diff --git a/manta-js/doc/to_private_submit.png b/manta-js/docs/images/to_private_submit.png similarity index 100% rename from manta-js/doc/to_private_submit.png rename to manta-js/docs/images/to_private_submit.png diff --git a/manta-js/examples/asset-webpack-ts/README.md b/manta-js/examples/asset-webpack-ts/README.md deleted file mode 100644 index a2c09bb8..00000000 --- a/manta-js/examples/asset-webpack-ts/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Manta.js Examples - -This directory contains some end to end examples of how to use manta.js. - -In order to run these examples: - -- Make sure all the dependencies are up to date, below is an example of a bash script to update all the dependencies: - -``` -#!/bin/bash -yarn -yarn upgrade manta.js -``` - -- Make sure you modify the examples to use the correct environment. They are currently set to development, which requires a local node. - -- Make sure the public polkadot JS account you are using has sufficient funds. - -- To run the examples make sure you are in the `asset-webpack-ts` directory, after installing all dependencies and call `yarn serve`. \ No newline at end of file diff --git a/manta-js/examples/asset-webpack-ts/index.ts b/manta-js/examples/asset-webpack-ts/index.ts deleted file mode 100644 index 9f92dd4b..00000000 --- a/manta-js/examples/asset-webpack-ts/index.ts +++ /dev/null @@ -1,283 +0,0 @@ -// @ts-ignore -import { - MantaPrivateWallet, - Environment, - Network, - MantaUtilities, -} from "manta-extension-sdk"; -import BN from "bn.js"; -import { - web3Accounts, - web3Enable, - web3FromSource, -} from "@polkadot/extension-dapp"; -import type { Signer as InjectSigner } from "@polkadot/api/types"; -import { get as getIdbData, set as setIdbData, del as delIdbData } from "idb-keyval"; -interface PolkadotConfig { - polkadotSigner: InjectSigner; - polkadotAddress: string; -} - -const currentNetwork = Network.Dolphin; -let currentSeedPhrase = 'spike napkin obscure diamond slice style excess table process story excuse absurd'; - -let privateWallet: MantaPrivateWallet = null; -let polkadotConfig: PolkadotConfig = null; -const assetId = new BN("1"); -const assetAmount = new BN("500000000000000000000"); - -function _log(...message: any[]) { - console.log("[INFO]: " + message.join("")); - console.log(performance.now()); -} - -// Get Polkadot JS Signer and Polkadot JS account address. -const getPolkadotSignerAndAddress = async () => { - const extensions = await web3Enable("Polkadot App"); - if (extensions.length === 0) { - throw new Error( - "Polkadot browser extension missing. https://polkadot.js.org/extension/" - ); - } - const allAccounts = await web3Accounts(); - let account = allAccounts[0]; - - const injector = await web3FromSource(account.meta.source); - const polkadotSigner = injector.signer; - const polkadotAddress = account.address; - return { - polkadotSigner, - polkadotAddress, - }; -}; - -const initWallet = async () => { - const privateWalletConfig = { - environment: Environment.Development, - network: currentNetwork, - loggingEnabled: true, - provingFilePath: - "https://media.githubusercontent.com/media/Manta-Network/manta-rs/main/manta-parameters/data/pay/proving", - parametersFilePath: - "https://raw.githubusercontent.com/Manta-Network/manta-rs/main/manta-parameters/data/pay/parameters", - requestUserSeedPhrase: async () => { - return currentSeedPhrase; - }, - saveStorageStateToLocal: async ( - network: string, - data: any - ): Promise => { - try { - await setIdbData(`storage_state_${network}`, data); - } catch (ex) { - console.error(ex); - return false; - } - return true; - }, - getStorageStateFromLocal: async (network: string): Promise => { - let result: string; - try { - result = await getIdbData(`storage_state_${network}`); - } catch (ex) { - console.error(ex); - } - return result || null; - }, - }; - privateWallet = await MantaPrivateWallet.init(privateWalletConfig); - - // just for test - // @ts-ignore - window.privateWallet = privateWallet; - - polkadotConfig = await getPolkadotSignerAndAddress(); - - initWalletData(); -}; - -const initWalletData = async () => { - // const isInitialed = (await getIdbData(`storage_state_${currentNetwork}`)); - _log("Load user mnemonic"); - await privateWallet.loadUserSeedPhrase(); - const privateAddress = await privateWallet.getZkAddress(); - _log("The zkAddress is: ", privateAddress); - - console.log('initialWalletSync'); - await privateWallet.initialWalletSync(); - - // if (isInitialed) { - // console.log('initialWalletSync'); - // await privateWallet.initialWalletSync(); - // } else { - // console.log('initialNewAccountWalletSync'); - // await privateWallet.initialNewAccountWalletSync(); - // } -} - -const queryTransferResult = async (initialPrivateBalance: BN) => { - let retryTimes = 0; - while (true) { - await new Promise((r) => setTimeout(r, 5000)); - _log("Syncing with ledger..."); - await privateWallet.walletSync(); - let newPrivateBalance = await privateWallet.getZkBalance(assetId); - _log("Private Balance after sync: ", newPrivateBalance.toString()); - - if (!initialPrivateBalance.eq(newPrivateBalance)) { - _log("Detected balance change after sync!"); - _log("Try number: ", retryTimes.toString()); - _log("Old balance: ", initialPrivateBalance.toString()); - _log("New balance: ", newPrivateBalance.toString()); - break; - } - retryTimes += 1; - if (retryTimes >= 10) { - _log("Check balance timeout"); - break; - } - } -}; - -/// Test to publicly transfer 10 DOL. -const publicTransferTest = async () => { - const destinationAddress = "5FHT5Rt1oeqAytX5KSn4ZZQdqN8oEa5Y81LZ5jadpk41bdoM"; - - const senderBalance = await MantaUtilities.getPublicBalance( - privateWallet.api, - assetId, - polkadotConfig.polkadotAddress - ); - _log("Sender Balance:" + JSON.stringify(senderBalance.toString())); - - const destinationBalance = await MantaUtilities.getPublicBalance( - privateWallet.api, - assetId, - destinationAddress - ); - _log("Destination Balance:" + JSON.stringify(destinationBalance.toString())); - - await MantaUtilities.publicTransfer( - privateWallet.api, - assetId, - assetAmount, - destinationAddress, - polkadotConfig.polkadotAddress, - polkadotConfig.polkadotSigner - ); - - await new Promise((r) => setTimeout(r, 10000)); - - const senderBalanceAfterTransfer = await MantaUtilities.getPublicBalance( - privateWallet.api, - assetId, - polkadotConfig.polkadotAddress - ); - _log( - "Sender Balance After:" + - JSON.stringify(senderBalanceAfterTransfer.toString()) - ); - - const destinationBalanceAfterTransfer = await MantaUtilities.getPublicBalance( - privateWallet.api, - assetId, - destinationAddress - ); - _log( - "Dest Balance After:" + - JSON.stringify(destinationBalanceAfterTransfer.toString()) - ); -}; - -/// Test to sign a transaction that converts 10 DOL to pDOL, -/// without publishing the transaction. -const toPrivateOnlySignTest = async () => { - await privateWallet.walletSync(); - const initialPrivateBalance = await privateWallet.getZkBalance(assetId); - _log("The initial balance is: ", initialPrivateBalance.toString()); - const signResult = await privateWallet.toPrivateBuild( - assetId, - assetAmount, - polkadotConfig.polkadotAddress - ); - _log("The result of the signing: ", signResult); - _log("Full: ", JSON.stringify(signResult.txs)); - // remove first 3 bytes of the signResult - _log( - 'For xcm remote transact payload, please use: ["0x' + - JSON.stringify(signResult.txs).slice(10) - ); -}; - -/// Test to privately transfer 10 pDOL. -const privateTransferTest = async () => { - const toPrivateTestAddress = "2JZCtGNR1iz6dR613g9p2VGHAAmXQK8xYJ117DLzs4s4"; - await privateWallet.walletSync(); - const initialPrivateBalance = await privateWallet.getZkBalance(assetId); - _log("The initial balance is: ", initialPrivateBalance.toString()); - await privateWallet.privateTransferSend( - assetId, - assetAmount, - toPrivateTestAddress, - polkadotConfig.polkadotSigner, - polkadotConfig.polkadotAddress - ); - await queryTransferResult(initialPrivateBalance); -}; - -/// Test to execute a `ToPrivate` transaction. -/// Convert 10 DOL to 10 pDOL. -const toPrivateTest = async () => { - await privateWallet.walletSync(); - const initialPrivateBalance = await privateWallet.getZkBalance(assetId); - _log("The initial balance is: ", initialPrivateBalance.toString()); - await privateWallet.toPrivateSend( - assetId, - assetAmount, - polkadotConfig.polkadotSigner, - polkadotConfig.polkadotAddress - ); - await queryTransferResult(initialPrivateBalance); -}; - -/// Test to execute a `ToPublic` transaction. -/// Convert 10 pDOL to 10 DOL. -const toPublicTest = async () => { - await privateWallet.walletSync(); - const initialPrivateBalance = await privateWallet.getZkBalance(assetId); - _log("The initial balance is: ", initialPrivateBalance.toString()); - - await privateWallet.toPublicSend( - assetId, - assetAmount, - polkadotConfig.polkadotSigner, - polkadotConfig.polkadotAddress - ); - await queryTransferResult(initialPrivateBalance); -}; - -// TODO: fix the memory is too high when regenerating the instance -const relaunch = async () => { - await privateWallet.resetState(); - await delIdbData(`storage_state_${currentNetwork}`); - currentSeedPhrase = 'must payment asthma judge tray recall another course zebra morning march engine'; - await initWalletData(); -}; - -// @ts-ignore -window.actions = { - toPrivateTest, - toPublicTest, - privateTransferTest, - publicTransferTest, - toPrivateOnlySignTest, - relaunch, -}; - -async function main() { - _log("Initial"); - await initWallet(); - _log("Initial end"); -} - -main(); diff --git a/manta-js/examples/asset-webpack-ts/sdk.d.ts b/manta-js/examples/asset-webpack-ts/sdk.d.ts deleted file mode 100644 index dc9663e9..00000000 --- a/manta-js/examples/asset-webpack-ts/sdk.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'manta-wasm-wallet-api'; \ No newline at end of file diff --git a/manta-js/examples/extension-example/.gitignore b/manta-js/examples/extension-example/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/manta-js/examples/extension-example/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/manta-js/examples/extension-example/README.md b/manta-js/examples/extension-example/README.md new file mode 100644 index 00000000..1dbe4c3c --- /dev/null +++ b/manta-js/examples/extension-example/README.md @@ -0,0 +1,15 @@ +# Manta Extension Example + +### How to run this example + +```sh +yarn +yarn dev +``` + +### Other + +```sh +# This project is created by the following script +yarn create vite demo --template react-ts +``` diff --git a/manta-js/examples/extension-example/index.html b/manta-js/examples/extension-example/index.html new file mode 100644 index 00000000..c63b4399 --- /dev/null +++ b/manta-js/examples/extension-example/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + Manta Extension Example + + +
+ + + diff --git a/manta-js/examples/extension-example/package.json b/manta-js/examples/extension-example/package.json new file mode 100644 index 00000000..d652df34 --- /dev/null +++ b/manta-js/examples/extension-example/package.json @@ -0,0 +1,27 @@ +{ + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "tsc && vite build", + "dev": "vite", + "preview": "vite preview" + }, + "dependencies": { + "@polkadot/api": "^10.1.4", + "@polkadot/extension-inject": "^0.45.3", + "@polkadot/ui-keyring": "^3.1.1", + "@polkadot/util": "^11.1.1", + "@polkadot/util-crypto": "^11.1.1", + "bignumber.js": "^9.1.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "@vitejs/plugin-react": "^3.1.0", + "typescript": "^4.9.3", + "vite": "^4.2.0" + } +} diff --git a/manta-js/examples/extension-example/public/manta.svg b/manta-js/examples/extension-example/public/manta.svg new file mode 100644 index 00000000..21d23ab9 --- /dev/null +++ b/manta-js/examples/extension-example/public/manta.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/manta-js/examples/extension-example/src/App.tsx b/manta-js/examples/extension-example/src/App.tsx new file mode 100644 index 00000000..8e182019 --- /dev/null +++ b/manta-js/examples/extension-example/src/App.tsx @@ -0,0 +1,423 @@ +import { ApiPromise, WsProvider } from '@polkadot/api'; +import BigNumber from 'bignumber.js'; +import { useCallback, useEffect, useState } from 'react'; + +import { Injected, InjectedWeb3, PrivateWalletStateInfo } from './interfaces'; + +const rpcUrl = 'wss://zenlink.zqhxuyuan.cloud:444'; +const decimals = 12; +const network = 'Calamari'; + +const assetId = '1'; +const toZkAddress = '3UG1BBvv7viqwyg1QKsMVarnSPcdiRQ1aL2vnTgwjWYX'; + +const injectedWeb3 = window.injectedWeb3 + ? (window.injectedWeb3['manta-wallet-js'] as InjectedWeb3) + : null; + +let isConnecting = false; + +export default function App() { + const [isInjected] = useState(!!injectedWeb3); + const [injected, setInjected] = useState(null); + const [api, setApi] = useState(null); + const [publicAddress, setPublicAddress] = useState(null); + const [zkAddress, setZkAddress] = useState(null); + const [balance, setBalance] = useState(null); + const [publicBalance, setPublicBalance] = useState(null); + const [toPrivateAmount, setToPrivateAmount] = useState('100'); + const [privateTransferAmount, setPrivateTransferAmount] = + useState('5'); + const [toPublicAmount, setToPublicAmount] = useState('5'); + const [startAssetId, setStartAssetId] = useState('1'); + const [virtualAsset, setVirtualAsset] = useState( + '{"identifier":{"is_transparent":false,"utxo_commitment_randomness":[218,12,198,205,243,45,111,55,97,232,107,40,237,202,174,102,12,100,161,170,141,2,173,101,117,161,177,116,146,37,81,31]},"asset":{"id":[82,77,144,171,218,215,31,37,190,239,170,153,12,42,235,151,22,238,79,66,34,183,22,37,117,55,167,12,74,225,51,45],"value":1}}', + ); + const [result, setResult] = useState(); + const [operating, setOperating] = useState(false); + const [receiveZkAddress, setReceiveZkAddress] = useState(toZkAddress); + const [stateInfo, setStateInfo] = useState( + null, + ); + + const onConnect = useCallback(async () => { + const injected = await injectedWeb3?.enable('Manta Wallet Demo'); + if (!injected) { + return; + } + + const accounts = await injected.accounts.get(); + if (!accounts || accounts.length <= 0) { + return; + } + setPublicAddress(accounts[0].address); + // @ts-ignore + setZkAddress(accounts[0].zkAddress); + setInjected(injected); + + const provider = new WsProvider(rpcUrl); + const api = await ApiPromise.create({ provider }); + api.setSigner(injected.signer); + setApi(api); + localStorage.setItem('connected', '1'); + }, [setInjected]); + + const syncWallet = useCallback(async () => { + return await injected?.privateWallet.walletSync(); + }, [injected]); + + const fetchBalance = useCallback(async () => { + const balance = await injected?.privateWallet.getZkBalance({ + network, + assetId, + }); + if (!balance) { + return null; + } + return new BigNumber(balance) + .div(new BigNumber(10).pow(decimals)) + .toFixed(); + }, [injected]); + + const fetchPublicBalance = useCallback(async () => { + const accountInfo = await api?.query.system.account(publicAddress); + const result = accountInfo as { data?: { free?: any } }; + const balanceString = result?.data?.free?.toString(); + if (!balanceString) { + return null; + } + return balanceString + ? new BigNumber(balanceString) + .div(new BigNumber(10).pow(decimals)) + .toFixed() + : '0'; + }, [api, publicAddress]); + + const updateBalance = useCallback(async () => { + const result = await Promise.all([fetchBalance(), fetchPublicBalance()]); + setBalance(result[0] ?? '-'); + setPublicBalance(result[1] ?? '-'); + }, [fetchBalance, fetchPublicBalance, setBalance, setPublicBalance]); + + const queryResult = useCallback( + async (originBalance: string) => { + const maxTimes = 12; + let times = 1; + const func = async () => { + await syncWallet(); + const newBalance = await fetchBalance(); + if (originBalance !== newBalance) { + setResult('Transaction successful'); + updateBalance(); + setOperating(false); + } else if (times > 12) { + setResult('Timeout'); + setOperating(false); + updateBalance(); + } else { + setResult(`Querying... times: ${times}/${maxTimes}`); + times += 1; + setTimeout(func, 5000); + } + }; + func(); + }, + [setResult, fetchBalance, setOperating, syncWallet, updateBalance], + ); + + const sendTransaction = useCallback( + async (func: () => Promise, checkResult = true) => { + if (!publicAddress) { + setResult('publicAddress is empty'); + return; + } + try { + setOperating(true); + setResult('Signing'); + await syncWallet(); + const response = await func(); + console.log(response); + if (!checkResult) { + setOperating(false); + setResult(response); + } else { + if (!response || response.length <= 0) { + setOperating(false); + setResult('Response is empty'); + return; + } + setResult('Sign Successful'); + for (let i = 0; i < response.length; i++) { + const tx = api?.tx(response[i]); + await tx?.signAndSend(publicAddress, () => {}); + } + const originBalance = await fetchBalance(); + queryResult(originBalance ?? '0'); + } + } catch (ex) { + setOperating(false); + setResult(ex); + console.error(ex); + } + }, + [publicAddress, api, setResult, fetchBalance, setOperating, syncWallet], + ); + + const toPrivateTransaction = useCallback(async () => { + sendTransaction(async () => { + const response = await injected?.privateWallet.toPrivateBuild({ + assetId, + amount: new BigNumber(10) + .pow(decimals) + .times(toPrivateAmount) + .toFixed(), + polkadotAddress: publicAddress!, + network, + }); + return response || null; + }); + }, [toPrivateAmount, injected, publicAddress, sendTransaction]); + + const privateTransferTransaction = useCallback(async () => { + sendTransaction(async () => { + const response = await injected?.privateWallet.privateTransferBuild({ + assetId, + amount: new BigNumber(10) + .pow(decimals) + .times(privateTransferAmount) + .toFixed(), + polkadotAddress: publicAddress!, + toZkAddress: receiveZkAddress, + network, + }); + return response || null; + }); + }, [ + privateTransferAmount, + injected, + publicAddress, + sendTransaction, + receiveZkAddress, + ]); + + const toPublicTransaction = useCallback(async () => { + sendTransaction(async () => { + const response = await injected?.privateWallet.toPublicBuild({ + assetId, + amount: new BigNumber(10).pow(decimals).times(toPublicAmount).toFixed(), + polkadotAddress: publicAddress!, + network, + }); + return response || null; + }); + }, [toPublicAmount, injected, publicAddress, sendTransaction]); + + const multiSbtPostBuildTransition = useCallback(async () => { + sendTransaction(async () => { + return injected?.privateWallet.multiSbtPostBuild({ + sbtInfoList: [ + { assetId: startAssetId, amount: '1' }, + { assetId: String(startAssetId + 1), amount: '1' }, + ], + network, + }); + }, false); + }, [startAssetId, injected, sendTransaction]); + + const getSbtIdentityProof = useCallback(async () => { + await sendTransaction(async () => { + return injected?.privateWallet.getSbtIdentityProof({ + virtualAsset, + polkadotAddress: publicAddress!, + network, + }); + }, false); + }, [virtualAsset, publicAddress, injected, sendTransaction]); + + // sub state && unSub state + useEffect(() => { + if (!injected || !injected.privateWallet) { + return; + } + return injected.privateWallet.subscribeWalletState(setStateInfo); + }, [injected, setStateInfo]); + + // loop balance + useEffect(() => { + if (stateInfo?.isWalletBusy || !stateInfo?.isWalletReady) { + return; + } + updateBalance(); + }, [stateInfo?.isWalletReady, updateBalance]); + + // auto connect wallet + useEffect(() => { + if (localStorage.getItem('connected') !== '1' || isConnecting) { + return; + } + isConnecting = true; + setTimeout(onConnect, 1000); + }, []); + + return ( +
+ {!isInjected ? ( + <>No wallet + ) : !injected ? ( + + ) : ( + <> + {!publicAddress ? ( + <>No account + ) : ( + <> +
+ Address +

+ {publicAddress}(Public + Address) +

+

+ {zkAddress}(zKAddress) +

+
+
+ Wallet State + {stateInfo && + Object.keys(stateInfo).map((key: string) => ( +

+ {key}: {/* @ts-ignore */} + {String(stateInfo[key])} +

+ ))} +
+
+ Balance +

+ DOL: {publicBalance} +

+

+ zkDOL: {balance} +

+
+
+ Sign Transaction +

+ + { + setToPrivateAmount(e.target.value); + }} + /> + DOL + + +

+

+ + { + setPrivateTransferAmount(e.target.value); + }} + /> + zkDOL + + + + { + setReceiveZkAddress(e.target.value); + }} + /> + +

+

+ + { + setToPublicAmount(e.target.value); + }} + /> + zkDOL + + +

+

+ + { + setStartAssetId(e.target.value); + }} + /> + AssetId + + +

+

+ + { + setVirtualAsset(e.target.value); + }} + /> + VirtualAsset + + +

+
+
+ Result + +

+ Open Chrome Dev Console to see more information +

+
+ + )} + + )} +
+ ); +} diff --git a/manta-js/examples/extension-example/src/index.css b/manta-js/examples/extension-example/src/index.css new file mode 100644 index 00000000..d6a43f68 --- /dev/null +++ b/manta-js/examples/extension-example/src/index.css @@ -0,0 +1,139 @@ +:root { + font-family: 'Red Hat Mono', monospace, sans-serif; + line-height: 1.5; + font-weight: 400; + color-scheme: light dark; + color: #333; + font-synthesis: none; + text-rendering: optimizelegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-size-adjust: 100%; + font-size: 14px; + padding: 20px; +} + +p, +pre { + padding: 0; + margin: 0; +} + +p { + margin: 2px 0; +} + +strong { + font-weight: 600; +} + +button { + border: 0 none; + border-radius: 8px; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 600; + font-family: inherit; + cursor: pointer; + color: #fff; + transition: border-color 0.25s; + background: linear-gradient(88.95deg, #29ccb9 0%, #0091ff 100%); +} + +button:hover { + background: linear-gradient(0deg, rgb(0 0 0 / 10%), rgb(0 0 0 / 10%)), + linear-gradient(88.95deg, #29ccb9 0%, #0091ff 100%); +} + +button:active, +button:disabled { + background: linear-gradient(0deg, rgb(0 0 0 / 30%), rgb(0 0 0 / 30%)), + linear-gradient(88.95deg, #29ccb9 0%, #0091ff 100%); + box-shadow: inset 0 4px 9px rgb(0 0 0 / 25%); +} + +button:disabled { + cursor: default; +} + +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +fieldset { + border: solid 1px #999; + border-radius: 8px; + padding: 15px 25px; + margin-bottom: 25px; +} + +legend { + font-weight: 600; + padding: 0 5px; + margin-left: -5px; +} + +.address { + background: linear-gradient(88.95deg, #29ccb9 0%, #0091ff 49%, #ff66b7 100%); + -webkit-text-fill-color: transparent; + background-clip: text; + -webkit-background-clip: text; + color: #fff; + margin-right: 10px; +} + +.sign-item { + margin: 15px 0; +} + +.sign-item .input { + margin-right: 10px; + position: relative; +} + +.sign-item input { + border: solid 1px #999; + border-radius: 8px; + padding: 0.6em 8em 0.6em 0.8em; + max-width: 80px; + font-weight: 600; + text-overflow: ellipsis; +} + +.sign-item .receive { + margin-left: 10px; +} + +.sign-item .receive input { + max-width: unset; + width: 180px; + font-weight: normal; + padding-right: 0.8rem; + text-overflow: ellipsis; +} + +.sign-item .input i { + position: absolute; + right: 0.8rem; + top: 50%; + transform: translateY(-50%); + font-size: 13px; + font-style: normal; +} + +.sign-item button { + min-width: 200px; +} + +textarea { + width: 100%; + border: solid 1px #999; + min-height: 180px; + max-height: 360px; + border-radius: 8px; + outline: none; + padding: 15px; + box-sizing: border-box; + margin: 5px 0; +} diff --git a/manta-js/examples/extension-example/src/interfaces.ts b/manta-js/examples/extension-example/src/interfaces.ts new file mode 100644 index 00000000..8aaebbb5 --- /dev/null +++ b/manta-js/examples/extension-example/src/interfaces.ts @@ -0,0 +1,98 @@ +import type { Unsubcall } from '@polkadot/extension-inject/types' +import type { Injected as PolkadotInjected } from '@polkadot/extension-inject/types' + +declare global { + interface Window { + injectedWeb3: { + 'manta-wallet-js': { + version: string + enable: (originName: string) => Promise + } + } + } +} + +export interface Injected extends PolkadotInjected { + privateWallet: InjectedPrivateWallet +} + +export type InjectedWeb3 = { + version: string + enable: (originName: string) => Promise +} + +export type Network = string + +export interface PrivateWalletStateInfo { + isWalletInitialized: boolean + isWalletAuthorized: boolean + isWalletReady: boolean + isWalletBusy: boolean +} + +export interface RequestBasePayload { + assetId: string + network: Network +} + +export interface RequestMultiZkBalancePayload { + assetIds: string[] + network: Network +} + +export interface RequestTransactionPayload extends RequestBasePayload { + amount: string + polkadotAddress: string +} + +export interface RequestTransferTransactionPayload + extends RequestTransactionPayload { + toZkAddress: string +} + +export interface ResponseBuildMultiSbtPost { + transactionDatas: any[]; + posts: any[]; +} + +export type RequestSbtInfo = { + assetId: string; + amount?: string; +}; + +export interface RequestBuildMultiSbtPostPayload { + sbtInfoList: RequestSbtInfo[] + network: Network +} + +export interface RequestGetIdentityProofPayload { + virtualAsset: string + polkadotAddress: string + network: Network +} + +export interface InjectedPrivateWallet { + getWalletState(): Promise + walletSync(): Promise + getZkBalance(payload: RequestBasePayload): Promise + getMultiZkBalance( + payload: RequestMultiZkBalancePayload, + ): Promise + toPrivateBuild(payload: RequestTransactionPayload): Promise + privateTransferBuild( + payload: RequestTransferTransactionPayload, + ): Promise + toPublicBuild(payload: RequestTransactionPayload): Promise + sbtWalletSync(): Promise + getZkSbtBalance(payload: RequestBasePayload): Promise + getMultiZkSbtBalance( + payload: RequestMultiZkBalancePayload, + ): Promise + multiSbtPostBuild( + payload: RequestBuildMultiSbtPostPayload, + ): Promise + getSbtIdentityProof(payload: RequestGetIdentityProofPayload): Promise + subscribeWalletState: ( + cb: (state: PrivateWalletStateInfo) => void | Promise, + ) => Unsubcall +} diff --git a/manta-js/examples/extension-example/src/main.tsx b/manta-js/examples/extension-example/src/main.tsx new file mode 100644 index 00000000..79d776d6 --- /dev/null +++ b/manta-js/examples/extension-example/src/main.tsx @@ -0,0 +1,11 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' + +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , +) diff --git a/manta-js/examples/extension-example/tsconfig.json b/manta-js/examples/extension-example/tsconfig.json new file mode 100644 index 00000000..3d0a51a8 --- /dev/null +++ b/manta-js/examples/extension-example/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/manta-js/examples/extension-example/tsconfig.node.json b/manta-js/examples/extension-example/tsconfig.node.json new file mode 100644 index 00000000..9d31e2ae --- /dev/null +++ b/manta-js/examples/extension-example/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/manta-js/examples/extension-example/vite.config.ts b/manta-js/examples/extension-example/vite.config.ts new file mode 100644 index 00000000..36f7f4e1 --- /dev/null +++ b/manta-js/examples/extension-example/vite.config.ts @@ -0,0 +1,7 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/manta-js/examples/extension-example/yarn.lock b/manta-js/examples/extension-example/yarn.lock new file mode 100644 index 00000000..6ac69208 --- /dev/null +++ b/manta-js/examples/extension-example/yarn.lock @@ -0,0 +1,1250 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.20.5": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298" + integrity sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g== + +"@babel/core@^7.20.12": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.3.tgz#cf1c877284a469da5d1ce1d1e53665253fae712e" + integrity sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.3" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.21.2" + "@babel/helpers" "^7.21.0" + "@babel/parser" "^7.21.3" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.3" + "@babel/types" "^7.21.3" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.0" + +"@babel/generator@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.3.tgz#232359d0874b392df04045d72ce2fd9bb5045fce" + integrity sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA== + dependencies: + "@babel/types" "^7.21.3" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" + integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== + dependencies: + "@babel/compat-data" "^7.20.5" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-function-name@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" + integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== + dependencies: + "@babel/template" "^7.20.7" + "@babel/types" "^7.21.0" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.21.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" + integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.2" + "@babel/types" "^7.21.2" + +"@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== + +"@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== + dependencies: + "@babel/types" "^7.20.2" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/helper-validator-option@^7.18.6": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" + integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== + +"@babel/helpers@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" + integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== + dependencies: + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.20.7", "@babel/parser@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3" + integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ== + +"@babel/plugin-transform-react-jsx-self@^7.18.6": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.21.0.tgz#ec98d4a9baafc5a1eb398da4cf94afbb40254a54" + integrity sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-react-jsx-source@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz#88578ae8331e5887e8ce28e4c9dc83fb29da0b86" + integrity sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + +"@babel/template@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + +"@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.3.tgz#4747c5e7903d224be71f90788b06798331896f67" + integrity sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.3" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.21.3" + "@babel/types" "^7.21.3" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.18.6", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05" + integrity sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@esbuild/android-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.14.tgz#4624cea3c8941c91f9e9c1228f550d23f1cef037" + integrity sha512-eLOpPO1RvtsP71afiFTvS7tVFShJBCT0txiv/xjFBo5a7R7Gjw7X0IgIaFoLKhqXYAXhahoXm7qAmRXhY4guJg== + +"@esbuild/android-arm@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.14.tgz#74fae60fcab34c3f0e15cb56473a6091ba2b53a6" + integrity sha512-0CnlwnjDU8cks0yJLXfkaU/uoLyRf9VZJs4p1PskBr2AlAHeEsFEwJEo0of/Z3g+ilw5mpyDwThlxzNEIxOE4g== + +"@esbuild/android-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.14.tgz#f002fbc08d5e939d8314bd23bcfb1e95d029491f" + integrity sha512-nrfQYWBfLGfSGLvRVlt6xi63B5IbfHm3tZCdu/82zuFPQ7zez4XjmRtF/wIRYbJQ/DsZrxJdEvYFE67avYXyng== + +"@esbuild/darwin-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.14.tgz#b8dcd79a1dd19564950b4ca51d62999011e2e168" + integrity sha512-eoSjEuDsU1ROwgBH/c+fZzuSyJUVXQTOIN9xuLs9dE/9HbV/A5IqdXHU1p2OfIMwBwOYJ9SFVGGldxeRCUJFyw== + +"@esbuild/darwin-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.14.tgz#4b49f195d9473625efc3c773fc757018f2c0d979" + integrity sha512-zN0U8RWfrDttdFNkHqFYZtOH8hdi22z0pFm0aIJPsNC4QQZv7je8DWCX5iA4Zx6tRhS0CCc0XC2m7wKsbWEo5g== + +"@esbuild/freebsd-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.14.tgz#480923fd38f644c6342c55e916cc7c231a85eeb7" + integrity sha512-z0VcD4ibeZWVQCW1O7szaLxGsx54gcCnajEJMdYoYjLiq4g1jrP2lMq6pk71dbS5+7op/L2Aod+erw+EUr28/A== + +"@esbuild/freebsd-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.14.tgz#a6b6b01954ad8562461cb8a5e40e8a860af69cbe" + integrity sha512-hd9mPcxfTgJlolrPlcXkQk9BMwNBvNBsVaUe5eNUqXut6weDQH8whcNaKNF2RO8NbpT6GY8rHOK2A9y++s+ehw== + +"@esbuild/linux-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.14.tgz#1fe2f39f78183b59f75a4ad9c48d079916d92418" + integrity sha512-FhAMNYOq3Iblcj9i+K0l1Fp/MHt+zBeRu/Qkf0LtrcFu3T45jcwB6A1iMsemQ42vR3GBhjNZJZTaCe3VFPbn9g== + +"@esbuild/linux-arm@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.14.tgz#18d594a49b64e4a3a05022c005cb384a58056a2a" + integrity sha512-BNTl+wSJ1omsH8s3TkQmIIIQHwvwJrU9u1ggb9XU2KTVM4TmthRIVyxSp2qxROJHhZuW/r8fht46/QE8hU8Qvg== + +"@esbuild/linux-ia32@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.14.tgz#f7f0182a9cfc0159e0922ed66c805c9c6ef1b654" + integrity sha512-91OK/lQ5y2v7AsmnFT+0EyxdPTNhov3y2CWMdizyMfxSxRqHazXdzgBKtlmkU2KYIc+9ZK3Vwp2KyXogEATYxQ== + +"@esbuild/linux-loong64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.14.tgz#5f5305fdffe2d71dd9a97aa77d0c99c99409066f" + integrity sha512-vp15H+5NR6hubNgMluqqKza85HcGJgq7t6rMH7O3Y6ApiOWPkvW2AJfNojUQimfTp6OUrACUXfR4hmpcENXoMQ== + +"@esbuild/linux-mips64el@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.14.tgz#a602e85c51b2f71d2aedfe7f4143b2f92f97f3f5" + integrity sha512-90TOdFV7N+fgi6c2+GO9ochEkmm9kBAKnuD5e08GQMgMINOdOFHuYLPQ91RYVrnWwQ5683sJKuLi9l4SsbJ7Hg== + +"@esbuild/linux-ppc64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.14.tgz#32d918d782105cbd9345dbfba14ee018b9c7afdf" + integrity sha512-NnBGeoqKkTugpBOBZZoktQQ1Yqb7aHKmHxsw43NddPB2YWLAlpb7THZIzsRsTr0Xw3nqiPxbA1H31ZMOG+VVPQ== + +"@esbuild/linux-riscv64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.14.tgz#38612e7b6c037dff7022c33f49ca17f85c5dec58" + integrity sha512-0qdlKScLXA8MGVy21JUKvMzCYWovctuP8KKqhtE5A6IVPq4onxXhSuhwDd2g5sRCzNDlDjitc5sX31BzDoL5Fw== + +"@esbuild/linux-s390x@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.14.tgz#4397dff354f899e72fd035d72af59a700c465ccb" + integrity sha512-Hdm2Jo1yaaOro4v3+6/zJk6ygCqIZuSDJHdHaf8nVH/tfOuoEX5Riv03Ka15LmQBYJObUTNS1UdyoMk0WUn9Ww== + +"@esbuild/linux-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.14.tgz#6c5cb99891b6c3e0c08369da3ef465e8038ad9c2" + integrity sha512-8KHF17OstlK4DuzeF/KmSgzrTWQrkWj5boluiiq7kvJCiQVzUrmSkaBvcLB2UgHpKENO2i6BthPkmUhNDaJsVw== + +"@esbuild/netbsd-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.14.tgz#5fa5255a64e9bf3947c1b3bef5e458b50b211994" + integrity sha512-nVwpqvb3yyXztxIT2+VsxJhB5GCgzPdk1n0HHSnchRAcxqKO6ghXwHhJnr0j/B+5FSyEqSxF4q03rbA2fKXtUQ== + +"@esbuild/openbsd-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.14.tgz#74d14c79dcb6faf446878cc64284aa4e02f5ca6f" + integrity sha512-1RZ7uQQ9zcy/GSAJL1xPdN7NDdOOtNEGiJalg/MOzeakZeTrgH/DoCkbq7TaPDiPhWqnDF+4bnydxRqQD7il6g== + +"@esbuild/sunos-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.14.tgz#5c7d1c7203781d86c2a9b2ff77bd2f8036d24cfa" + integrity sha512-nqMjDsFwv7vp7msrwWRysnM38Sd44PKmW8EzV01YzDBTcTWUpczQg6mGao9VLicXSgW/iookNK6AxeogNVNDZA== + +"@esbuild/win32-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.14.tgz#dc36ed84f1390e73b6019ccf0566c80045e5ca3d" + integrity sha512-xrD0mccTKRBBIotrITV7WVQAwNJ5+1va6L0H9zN92v2yEdjfAN7864cUaZwJS7JPEs53bDTzKFbfqVlG2HhyKQ== + +"@esbuild/win32-ia32@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.14.tgz#0802a107afa9193c13e35de15a94fe347c588767" + integrity sha512-nXpkz9bbJrLLyUTYtRotSS3t5b+FOuljg8LgLdINWFs3FfqZMtbnBCZFUmBzQPyxqU87F8Av+3Nco/M3hEcu1w== + +"@esbuild/win32-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.14.tgz#e81fb49de05fed91bf74251c9ca0343f4fc77d31" + integrity sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA== + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@noble/hashes@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" + integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== + +"@noble/secp256k1@1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" + integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw== + +"@polkadot/api-augment@10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-augment/-/api-augment-10.2.1.tgz#b4f7b5febec00f025aea2a4d63c62aa0bbd812a6" + integrity sha512-fqxm+B0ASA5bdacOMk/YuVUGuRZT/B0iLOoaUSy2Z9yA4qx1mZnazwzIIkfHxTdcpEw8+prRmgv0EMXxMvmA0g== + dependencies: + "@polkadot/api-base" "10.2.1" + "@polkadot/rpc-augment" "10.2.1" + "@polkadot/types" "10.2.1" + "@polkadot/types-augment" "10.2.1" + "@polkadot/types-codec" "10.2.1" + "@polkadot/util" "^11.1.2" + tslib "^2.5.0" + +"@polkadot/api-base@10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-base/-/api-base-10.2.1.tgz#e2868af7c5884b6cceb6fefd258852e71dae9516" + integrity sha512-SLWjdzAGyVINsMa8V0XPK8npnJWUObuOlRmJ6kcxcwBi4BTJJeDMIp7HsATrxwhdBWZr7uuk+bQi+7ADH8yzvA== + dependencies: + "@polkadot/rpc-core" "10.2.1" + "@polkadot/types" "10.2.1" + "@polkadot/util" "^11.1.2" + rxjs "^7.8.0" + tslib "^2.5.0" + +"@polkadot/api-derive@10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-10.2.1.tgz#0eb9471bc11ac61f28a7d4210f72b847132124b9" + integrity sha512-rLd4n57weI74A2hlDMoS/TmeKdzdyztWMRf4PznN6W2+31bfI9IOtmJaxUs8ZxEYO5nbHOZRzy6l4d+8HhC+5g== + dependencies: + "@polkadot/api" "10.2.1" + "@polkadot/api-augment" "10.2.1" + "@polkadot/api-base" "10.2.1" + "@polkadot/rpc-core" "10.2.1" + "@polkadot/types" "10.2.1" + "@polkadot/types-codec" "10.2.1" + "@polkadot/util" "^11.1.2" + "@polkadot/util-crypto" "^11.1.2" + rxjs "^7.8.0" + tslib "^2.5.0" + +"@polkadot/api@10.2.1", "@polkadot/api@^10.1.4": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-10.2.1.tgz#66ca2e5ec30b3b01de491518cda5ed4919dc6829" + integrity sha512-T8wXJQIZIM5vow1mU9JE2KN2/lFwBGK2YGnZdHRuLJY0QWn+z8FuJik9kEtgNcZ7gjPnT/rBXd71V3PxixvxcA== + dependencies: + "@polkadot/api-augment" "10.2.1" + "@polkadot/api-base" "10.2.1" + "@polkadot/api-derive" "10.2.1" + "@polkadot/keyring" "^11.1.2" + "@polkadot/rpc-augment" "10.2.1" + "@polkadot/rpc-core" "10.2.1" + "@polkadot/rpc-provider" "10.2.1" + "@polkadot/types" "10.2.1" + "@polkadot/types-augment" "10.2.1" + "@polkadot/types-codec" "10.2.1" + "@polkadot/types-create" "10.2.1" + "@polkadot/types-known" "10.2.1" + "@polkadot/util" "^11.1.2" + "@polkadot/util-crypto" "^11.1.2" + eventemitter3 "^5.0.0" + rxjs "^7.8.0" + tslib "^2.5.0" + +"@polkadot/extension-inject@^0.45.3": + version "0.45.4" + resolved "https://registry.yarnpkg.com/@polkadot/extension-inject/-/extension-inject-0.45.4.tgz#36179950e89f154d91535dcbe1ea72f2e4c4ff19" + integrity sha512-JJgUmlNm/u8mold3/i5KKechfDzvQvau2H+QIEbBz0fYIS/daOupSYDh2m2WTkkFt8ci5E+hKNbt3BrReoWMog== + dependencies: + "@polkadot/rpc-provider" "^10.2.1" + "@polkadot/types" "^10.2.1" + "@polkadot/util" "^11.1.2" + "@polkadot/util-crypto" "^11.1.2" + "@polkadot/x-global" "^11.1.2" + tslib "^2.5.0" + +"@polkadot/keyring@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-11.1.2.tgz#5b2478308aca737b842f761435be160d924c6b00" + integrity sha512-LvyvVTvW9gZ3HyYuSfozRI9fKMJNP33LSVoABNHDY3nUPBFPTitEgMtInw6RJkTy2oWGU5C+dEFmtxjb9NvA8g== + dependencies: + "@polkadot/util" "11.1.2" + "@polkadot/util-crypto" "11.1.2" + tslib "^2.5.0" + +"@polkadot/networks@11.1.2", "@polkadot/networks@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-11.1.2.tgz#2566bdc4503e32f34f9ceb051c9a6e159cde415a" + integrity sha512-nYWykUlIdWsFhkEy/uette0nxF1KqmcPBeMqTbvpPdQqGkfZmb6Vfpm8mVRSxPhYLu8D7El7VO+Xz/HK6JLeQQ== + dependencies: + "@polkadot/util" "11.1.2" + "@substrate/ss58-registry" "^1.39.0" + tslib "^2.5.0" + +"@polkadot/rpc-augment@10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-augment/-/rpc-augment-10.2.1.tgz#1c3426cd3dd607a18248432846829298de324e76" + integrity sha512-opWg0//5FsIpu3Mi7UKHGCgu6Azrrhqpwc8by4vhW73gAVuJ5kxGcfJPy4m8pBA0omChD/l33PnhRlc9LkNRYg== + dependencies: + "@polkadot/rpc-core" "10.2.1" + "@polkadot/types" "10.2.1" + "@polkadot/types-codec" "10.2.1" + "@polkadot/util" "^11.1.2" + tslib "^2.5.0" + +"@polkadot/rpc-core@10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-10.2.1.tgz#bc57af228d4789c1a2f69fd041f6af38f6afda08" + integrity sha512-v7InyKGCxZKbsBcN4f0oIAE3f7mY+dCe6q11iCwAS8dAilXwfXDuZ1kwaSIxwZKRCMIWqwlVM8tNij48AWr04A== + dependencies: + "@polkadot/rpc-augment" "10.2.1" + "@polkadot/rpc-provider" "10.2.1" + "@polkadot/types" "10.2.1" + "@polkadot/util" "^11.1.2" + rxjs "^7.8.0" + tslib "^2.5.0" + +"@polkadot/rpc-provider@10.2.1", "@polkadot/rpc-provider@^10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-10.2.1.tgz#4264ea3c49407466a201a0dd53ea7e062c3110b1" + integrity sha512-Zg+6tXwrd/oH/mGW+HoGcwmqTUfusMSugDyL8ccNd+y9iU2d0AlZMbPBvOE4zIP3C2HCM7qye8ndaXzZNezvvA== + dependencies: + "@polkadot/keyring" "^11.1.2" + "@polkadot/types" "10.2.1" + "@polkadot/types-support" "10.2.1" + "@polkadot/util" "^11.1.2" + "@polkadot/util-crypto" "^11.1.2" + "@polkadot/x-fetch" "^11.1.2" + "@polkadot/x-global" "^11.1.2" + "@polkadot/x-ws" "^11.1.2" + eventemitter3 "^5.0.0" + mock-socket "^9.2.1" + nock "^13.3.0" + tslib "^2.5.0" + optionalDependencies: + "@substrate/connect" "0.7.22" + +"@polkadot/types-augment@10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-augment/-/types-augment-10.2.1.tgz#c011edfab39428eabc614345d07be47c2aa01d38" + integrity sha512-WCnL8a2vT/GSrVnWty1pHZhXK2rkYMCon/Ml7kJL+Xk72EPua1lLwUCAC2ct9uyFuqamjIuyWGo0dz34CvrYZw== + dependencies: + "@polkadot/types" "10.2.1" + "@polkadot/types-codec" "10.2.1" + "@polkadot/util" "^11.1.2" + tslib "^2.5.0" + +"@polkadot/types-codec@10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-codec/-/types-codec-10.2.1.tgz#83424223e6679a91fa0c5c58ed4991a1ee9f5cef" + integrity sha512-0H88kead5dLwST2JHGZw91Mq0iifdeXCCCCxTYaGL78naEdEEAUDb6emkr+wzhshoUT4/6iG6a56Idt/Sl+nSQ== + dependencies: + "@polkadot/util" "^11.1.2" + "@polkadot/x-bigint" "^11.1.2" + tslib "^2.5.0" + +"@polkadot/types-create@10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-create/-/types-create-10.2.1.tgz#5ab8ea89595f45f05c414efd2d658b9b6ae41085" + integrity sha512-OWRmsBsy4ee+KyGwO5iySdqCgObaDcyVU+IxzjagrU+HadioDdqI8m+Ptjy2DG/wbjd+NCCplyJ80TM++1+SDA== + dependencies: + "@polkadot/types-codec" "10.2.1" + "@polkadot/util" "^11.1.2" + tslib "^2.5.0" + +"@polkadot/types-known@10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-10.2.1.tgz#67f7a13e80e0e10c257cb3d01dd4ab9a498b0b05" + integrity sha512-DJEfhCBmqRjiL0VF6pqGExczqtOI/o4tg9UI2OUGHPGIKixyHAIiBd1wS+tKWC3toibzI3QVCIDCAPhkuLJJBw== + dependencies: + "@polkadot/networks" "^11.1.2" + "@polkadot/types" "10.2.1" + "@polkadot/types-codec" "10.2.1" + "@polkadot/types-create" "10.2.1" + "@polkadot/util" "^11.1.2" + tslib "^2.5.0" + +"@polkadot/types-support@10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-support/-/types-support-10.2.1.tgz#620048828a7fa1351f60831bbca6cc4a32b1ec53" + integrity sha512-e7CUGtdYazYmoGUGNp1ZDl7Jo++j9d/U64/aZg1pULvi69sBmKQAtdHzJtxMUgZk+f9VDmcA07n1bvfXZUltew== + dependencies: + "@polkadot/util" "^11.1.2" + tslib "^2.5.0" + +"@polkadot/types@10.2.1", "@polkadot/types@^10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-10.2.1.tgz#f60b59ce6fd2f0dd22b778b09660ef6776971677" + integrity sha512-P1v+R+ejvJP7ar1oTXxFUEYSQf/94PtwJaZj7K5AW5mUOfrnU2FLWv8c/W5voJNT0bMXcPAfnhF6X7ufUuf0Bg== + dependencies: + "@polkadot/keyring" "^11.1.2" + "@polkadot/types-augment" "10.2.1" + "@polkadot/types-codec" "10.2.1" + "@polkadot/types-create" "10.2.1" + "@polkadot/util" "^11.1.2" + "@polkadot/util-crypto" "^11.1.2" + rxjs "^7.8.0" + tslib "^2.5.0" + +"@polkadot/ui-keyring@^3.1.1": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@polkadot/ui-keyring/-/ui-keyring-3.1.3.tgz#db2826b988564f3fae0494d283fa62158d23be3c" + integrity sha512-QpnjU6NoVu4M2ZZYnq8WxmXAhWp8wRTK77g0VxnUMsN1sPgI+6pq31biLsFQ3PkPNkUbmwLb9BCr9z6XQMOmCQ== + dependencies: + "@polkadot/keyring" "^11.1.2" + "@polkadot/ui-settings" "3.1.3" + "@polkadot/util" "^11.1.2" + "@polkadot/util-crypto" "^11.1.2" + mkdirp "^2.1.6" + rxjs "^7.8.0" + store "^2.0.12" + tslib "^2.5.0" + +"@polkadot/ui-settings@3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@polkadot/ui-settings/-/ui-settings-3.1.3.tgz#0a27b2d263890f3af35aeba232e6183c6d27373b" + integrity sha512-Dm8LFzYL9Dfeen3oVv2wSK33Ae8FTtJGOr0ps4tB6VmcsCHjRMNmafZ7q86RD0RQ+BhDxD7leJhzF4r8gQ/iIg== + dependencies: + "@polkadot/networks" "^11.1.2" + "@polkadot/util" "^11.1.2" + eventemitter3 "^5.0.0" + store "^2.0.12" + tslib "^2.5.0" + +"@polkadot/util-crypto@11.1.2", "@polkadot/util-crypto@^11.1.1", "@polkadot/util-crypto@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-11.1.2.tgz#216d33e8f09426c3f9bb55f33ae280f620b17bb8" + integrity sha512-9+M4mqhmvtZ0eNfHjy+TuelXG8e2+vZAXLkPrzx0jPMnzhc7mqwBeIMgkV96kMVFPawLNscQGTSZ/ofkU8KYAw== + dependencies: + "@noble/hashes" "1.3.0" + "@noble/secp256k1" "1.7.1" + "@polkadot/networks" "11.1.2" + "@polkadot/util" "11.1.2" + "@polkadot/wasm-crypto" "^7.0.3" + "@polkadot/x-bigint" "11.1.2" + "@polkadot/x-randomvalues" "11.1.2" + "@scure/base" "1.1.1" + tslib "^2.5.0" + tweetnacl "^1.0.3" + +"@polkadot/util@11.1.2", "@polkadot/util@^11.1.1", "@polkadot/util@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-11.1.2.tgz#35859afb4cbbb5e12221258b1361f6da23cee89e" + integrity sha512-DtVjZgiCEJfrnX/I8gWxG/11ObQeWG2aA0HA+8ek2j//Q2W1Jfq7xvQPupmvrlQaBlHZnkqolgIUXy6UdM98mg== + dependencies: + "@polkadot/x-bigint" "11.1.2" + "@polkadot/x-global" "11.1.2" + "@polkadot/x-textdecoder" "11.1.2" + "@polkadot/x-textencoder" "11.1.2" + "@types/bn.js" "^5.1.1" + bn.js "^5.2.1" + tslib "^2.5.0" + +"@polkadot/wasm-bridge@7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-bridge/-/wasm-bridge-7.0.3.tgz#9691450830604dc4a361692a8a2a3df22fa53e96" + integrity sha512-q5qyhkGE9lHQmThNg6G5zCM4gYip2KtmR+De/URX7yWAO6snsinFqt066RFVuHvX1hZijrYSe/BGQABAUtH4pw== + dependencies: + tslib "^2.5.0" + +"@polkadot/wasm-crypto-asmjs@7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.0.3.tgz#a1bc942029979b2696a1062066d774e99a5a6b4c" + integrity sha512-ldMZjowYywn0Uj7jSr8a21rrlFFq/jWhCXVl21/KDcYGdFEfIajqbcrO5cHoT6w95sQgAwMWJwwDClXOaBjc/Q== + dependencies: + tslib "^2.5.0" + +"@polkadot/wasm-crypto-init@7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.0.3.tgz#336af713edfcd6fdd0194fee2919781893fba577" + integrity sha512-W4ClfPrzOTqiX0x4h6rXjCt8UsVsbg3zU7LJFFjeLgrguPoKTLGw4h5O1rR2H7EuMFbuqdztzJn3qTjBcR03Cg== + dependencies: + "@polkadot/wasm-bridge" "7.0.3" + "@polkadot/wasm-crypto-asmjs" "7.0.3" + "@polkadot/wasm-crypto-wasm" "7.0.3" + tslib "^2.5.0" + +"@polkadot/wasm-crypto-wasm@7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.0.3.tgz#016834b1eb2564d8a13b133ee77a4612ad873d41" + integrity sha512-FRjUADiA3wMkjJqQLgB0v9rbSADcb2PY/6dJi06iza9m41HebTN3x7f5D3gWTCfgJjzWLAPchY2Hwsa0WpTQkw== + dependencies: + "@polkadot/wasm-util" "7.0.3" + tslib "^2.5.0" + +"@polkadot/wasm-crypto@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto/-/wasm-crypto-7.0.3.tgz#e07ddbeea0b45149d8e58be292ad423d646f1cb1" + integrity sha512-mOCLCaL9cyrU72PCc9nMNAj3zdvOzau5mOGJjLahIz+mqlHAoAmEXCAJvJ2qCo7OFl8QiDToAEGhdDWQfiHUyg== + dependencies: + "@polkadot/wasm-bridge" "7.0.3" + "@polkadot/wasm-crypto-asmjs" "7.0.3" + "@polkadot/wasm-crypto-init" "7.0.3" + "@polkadot/wasm-crypto-wasm" "7.0.3" + "@polkadot/wasm-util" "7.0.3" + tslib "^2.5.0" + +"@polkadot/wasm-util@7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-util/-/wasm-util-7.0.3.tgz#eab59f9dac0f00ca736aff8b24925108b7b2f860" + integrity sha512-L9U5nSbzr5xa2YSpveP/zZxhOB6i8ibssK+ihuG+7SICYtTC0B9wJp/UnjP/c6bEDlMV3yWiNXJPBTJMGmkmIQ== + dependencies: + tslib "^2.5.0" + +"@polkadot/x-bigint@11.1.2", "@polkadot/x-bigint@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-bigint/-/x-bigint-11.1.2.tgz#b430fce70b78fb687bc6335243f788a09edbf4dd" + integrity sha512-IUsYpkWmHeYyHnGLzFd129YtqZp0oRc8n0bb5+M2tHHPHbwfTBreSP6CimGfcJsmGO9KfWEkzZdvWxYcXzZX+Q== + dependencies: + "@polkadot/x-global" "11.1.2" + tslib "^2.5.0" + +"@polkadot/x-fetch@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-11.1.2.tgz#96d41fbe68bef698765c2f9a2362a6ffa1b92da2" + integrity sha512-c7KF6Zbu2grfgLXrQAhTfW712ZYCJSMDIhxqNVDjBJlWEw1lyOvss79/LpKJGxEAvNIFVaUerxUyl7Rymkm0ug== + dependencies: + "@polkadot/x-global" "11.1.2" + node-fetch "^3.3.1" + tslib "^2.5.0" + +"@polkadot/x-global@11.1.2", "@polkadot/x-global@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-11.1.2.tgz#cbb21fe96e0d85925a23f552c4280b63ac8b5312" + integrity sha512-o/MV4ta7+teq8qOSXpaV7Dtk+9NltuRzsXEHiaqnLwaIXEQ2H7GJ+f/K7c+jQApEkdLp+iCt0D/lLjkW17vc2A== + dependencies: + tslib "^2.5.0" + +"@polkadot/x-randomvalues@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-11.1.2.tgz#db9b85a728a6270927c07e35bdd90082c182f0c7" + integrity sha512-hiAF1MfHVVkmFALikYMOi81Nh13gvg9E5yiW27jS33fyOR8d2mXt17QQzmfPCD3rkXwKZ8FI29sSTUXki1LL3Q== + dependencies: + "@polkadot/x-global" "11.1.2" + tslib "^2.5.0" + +"@polkadot/x-textdecoder@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-11.1.2.tgz#47866922af0262478aafce30ba27aeed77223706" + integrity sha512-CoRd680J6YtW1L5+XhsY8jCT3+esj82AttraiuB0DZ85t/IOT/8hji/MJJAXn6a+cgJqVNgHzrnpiCFYfsMKtA== + dependencies: + "@polkadot/x-global" "11.1.2" + tslib "^2.5.0" + +"@polkadot/x-textencoder@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-11.1.2.tgz#2967c06843ffd8299627da997d7e6b892d1e9c13" + integrity sha512-faZB0DRMJm54nrf8pTnE3ecHfZWi9cyQSx1ocqzjL/+J627alMvfPM7NBXU+HlmtENEbmRSgt6m9rIJKOL2LZw== + dependencies: + "@polkadot/x-global" "11.1.2" + tslib "^2.5.0" + +"@polkadot/x-ws@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-11.1.2.tgz#1becc584928057e6cfebc851a97e1bf82ec54b4b" + integrity sha512-Sp7BnGT8LKXDsGV5j8IzRtChuM39QiMPm6VL4mdDqhVyuqSSAtnLG3ZCuwE0EuREkduZxu1NaIToDeMeeXqxyg== + dependencies: + "@polkadot/x-global" "11.1.2" + tslib "^2.5.0" + ws "^8.13.0" + +"@scure/base@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" + integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== + +"@substrate/connect-extension-protocol@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@substrate/connect-extension-protocol/-/connect-extension-protocol-1.0.1.tgz#fa5738039586c648013caa6a0c95c43265dbe77d" + integrity sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg== + +"@substrate/connect@0.7.22": + version "0.7.22" + resolved "https://registry.yarnpkg.com/@substrate/connect/-/connect-0.7.22.tgz#15a20d734bab082c87f2aaaf75ce012c83881ef7" + integrity sha512-g12IYiepPu0OFWcm87ugDbfPr5a9TCGd4HJv1zXB2TRP/ZvYtHCE9+ftA5IvJbJPw6CI6/0XmUbP7Nz19HT/aw== + dependencies: + "@substrate/connect-extension-protocol" "^1.0.1" + eventemitter3 "^4.0.7" + smoldot "1.0.0" + +"@substrate/ss58-registry@^1.39.0": + version "1.39.0" + resolved "https://registry.yarnpkg.com/@substrate/ss58-registry/-/ss58-registry-1.39.0.tgz#eb916ff5fea7fa02e77745823fde21af979273d2" + integrity sha512-qZYpuE6n+mwew+X71dOur/CbMXj6rNW27o63JeJwdQH/GvcSKm3JLNhd+bGzwUKg0D/zD30Qc6p4JykArzM+tA== + +"@types/bn.js@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" + integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "18.15.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.10.tgz#4ee2171c3306a185d1208dad5f44dae3dee4cfe3" + integrity sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ== + +"@types/prop-types@*": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + +"@types/react-dom@^18.0.11": + version "18.0.11" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33" + integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^18.0.28": + version "18.0.30" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.30.tgz#83944e679fc7aeab3f042b76d63c4d755b56b7c4" + integrity sha512-AnME2cHDH11Pxt/yYX6r0w448BfTwQOLEhQEjCdwB7QskEI7EKtxhGUsExTQe/MsY3D9D5rMtu62WRocw9A8FA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.3" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" + integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== + +"@vitejs/plugin-react@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-3.1.0.tgz#d1091f535eab8b83d6e74034d01e27d73c773240" + integrity sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g== + dependencies: + "@babel/core" "^7.20.12" + "@babel/plugin-transform-react-jsx-self" "^7.18.6" + "@babel/plugin-transform-react-jsx-source" "^7.19.6" + magic-string "^0.27.0" + react-refresh "^0.14.0" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +bignumber.js@^9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" + integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== + +bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +browserslist@^4.21.3: + version "4.21.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" + integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== + dependencies: + caniuse-lite "^1.0.30001449" + electron-to-chromium "^1.4.284" + node-releases "^2.0.8" + update-browserslist-db "^1.0.10" + +caniuse-lite@^1.0.30001449: + version "1.0.30001472" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001472.tgz#3f484885f2a2986c019dc416e65d9d62798cdd64" + integrity sha512-xWC/0+hHHQgj3/vrKYY0AAzeIUgr7L9wlELIcAvZdDUHlhL/kNxMdnQLOSOQfP8R51ZzPhmHdyMkI0MMpmxCfg== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +csstype@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + +debug@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +electron-to-chromium@^1.4.284: + version "1.4.341" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.341.tgz#ab31e9e57ef7758a14c7a7977a1978d599514470" + integrity sha512-R4A8VfUBQY9WmAhuqY5tjHRf5fH2AAf6vqitBOE0y6u2PgHgqHSrhZmu78dIX3fVZtjqlwJNX1i2zwC3VpHtQQ== + +esbuild@^0.17.5: + version "0.17.14" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.14.tgz#d61a22de751a3133f3c6c7f9c1c3e231e91a3245" + integrity sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw== + optionalDependencies: + "@esbuild/android-arm" "0.17.14" + "@esbuild/android-arm64" "0.17.14" + "@esbuild/android-x64" "0.17.14" + "@esbuild/darwin-arm64" "0.17.14" + "@esbuild/darwin-x64" "0.17.14" + "@esbuild/freebsd-arm64" "0.17.14" + "@esbuild/freebsd-x64" "0.17.14" + "@esbuild/linux-arm" "0.17.14" + "@esbuild/linux-arm64" "0.17.14" + "@esbuild/linux-ia32" "0.17.14" + "@esbuild/linux-loong64" "0.17.14" + "@esbuild/linux-mips64el" "0.17.14" + "@esbuild/linux-ppc64" "0.17.14" + "@esbuild/linux-riscv64" "0.17.14" + "@esbuild/linux-s390x" "0.17.14" + "@esbuild/linux-x64" "0.17.14" + "@esbuild/netbsd-x64" "0.17.14" + "@esbuild/openbsd-x64" "0.17.14" + "@esbuild/sunos-x64" "0.17.14" + "@esbuild/win32-arm64" "0.17.14" + "@esbuild/win32-ia32" "0.17.14" + "@esbuild/win32-x64" "0.17.14" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +eventemitter3@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.0.tgz#084eb7f5b5388df1451e63f4c2aafd71b217ccb3" + integrity sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg== + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +magic-string@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3" + integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.13" + +mkdirp@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" + integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== + +mock-socket@^9.2.1: + version "9.2.1" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.2.1.tgz#cc9c0810aa4d0afe02d721dcb2b7e657c00e2282" + integrity sha512-aw9F9T9G2zpGipLLhSNh6ZpgUyUl4frcVmRN08uE1NWPWg43Wx6+sGPDbQ7E5iFZZDJW5b5bypMeAEHqTbIFag== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nanoid@^3.3.4: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + +nock@^13.3.0: + version "13.3.0" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.3.0.tgz#b13069c1a03f1ad63120f994b04bfd2556925768" + integrity sha512-HHqYQ6mBeiMc+N038w8LkMpDCRquCHWeNmN3v6645P3NhN2+qXOBqvPqo7Rt1VyCMzKhJ733wZqw5B7cQVFNPg== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + lodash "^4.17.21" + propagate "^2.0.0" + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.1.tgz#b3eea7b54b3a48020e46f4f88b9c5a7430d20b2e" + integrity sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +node-releases@^2.0.8: + version "2.0.10" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" + integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== + +pako@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +postcss@^8.4.21: + version "8.4.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" + integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react-refresh@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" + integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== + +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +resolve@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rollup@^3.18.0: + version "3.20.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.20.2.tgz#f798c600317f216de2e4ad9f4d9ab30a89b690ff" + integrity sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg== + optionalDependencies: + fsevents "~2.3.2" + +rxjs@^7.8.0: + version "7.8.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" + integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== + dependencies: + tslib "^2.1.0" + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +smoldot@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/smoldot/-/smoldot-1.0.0.tgz#438ddb9903fed28f24e52c4c0fb56f0b479209d7" + integrity sha512-3/y/poD7j42NL6Z/Gp4OLm1qx8svyy255XQ5xRkjv9+O50RT0SeEmnBZmEbVmi1w6WmamPjt8URdzfN7xxgK9Q== + dependencies: + pako "^2.0.4" + ws "^8.8.1" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +store@^2.0.12: + version "2.0.12" + resolved "https://registry.yarnpkg.com/store/-/store-2.0.12.tgz#8c534e2a0b831f72b75fc5f1119857c44ef5d593" + integrity sha512-eO9xlzDpXLiMr9W1nQ3Nfp9EzZieIQc10zPPMP5jsVV7bLOziSFFBP0XoDXACEIFtdI+rIz0NwWVA/QVJ8zJtw== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +tslib@^2.1.0, tslib@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + +tweetnacl@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + +typescript@^4.9.3: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +update-browserslist-db@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +vite@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.2.1.tgz#6c2eb337b0dfd80a9ded5922163b94949d7fc254" + integrity sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg== + dependencies: + esbuild "^0.17.5" + postcss "^8.4.21" + resolve "^1.22.1" + rollup "^3.18.0" + optionalDependencies: + fsevents "~2.3.2" + +web-streams-polyfill@^3.0.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + +ws@^8.13.0, ws@^8.8.1: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/manta-js/examples/asset-webpack-ts/.gitignore b/manta-js/examples/sdk-example/.gitignore similarity index 100% rename from manta-js/examples/asset-webpack-ts/.gitignore rename to manta-js/examples/sdk-example/.gitignore diff --git a/manta-js/examples/sdk-example/README.md b/manta-js/examples/sdk-example/README.md new file mode 100644 index 00000000..b9604770 --- /dev/null +++ b/manta-js/examples/sdk-example/README.md @@ -0,0 +1,90 @@ +# Manta SDK Example + +## How to run this example + + +``` shell +yarn +yarn start # Will rebuild the sdk and run example +``` +> This example supports any polkadot extension wallet, such as polkadot.js, SubWallet, Talisman, MantaWallet. + +## How to test the SDK + +1. Update the config in the [index.ts](./index.ts) file, If you need to change. +``` typescript +const apiEndpoint = 'wss://ws.calamari.systems'; +const nativeTokenDecimals = 12; + +const currentNetwork: interfaces.Network = 'Dolphin'; + +// Native asset id +const assetId = new BN('1'); + +// toPrivate Amount (50 DOL) +const transferInAmount = new BN(50).mul( + new BN(10).pow(new BN(nativeTokenDecimals)), +); +// privateTransfer && toPublic Amount (5 DOL) +const transferOutAmount = transferInAmount.div(new BN(10)); + +let currentSeedPhrase = + 'spike napkin obscure diamond slice style excess table process story excuse absurd'; +``` +2. Run example +```shell +yarn start # chrome will automatically open localhost:8080 +``` +3. Open the chrome devtools,When you see `Initial successful`, it means that the wallet of mantaPay and mantaSBT have been synchronized successfully. Then you can execute the following code to test +``` typescript +// The following methods will be automatically injected into `window.actions` +// This code is at the bottom of the index.ts file +window.actions = { + getPallets() { + return pallets; + }, + async toPrivateBuild() { + await toPrivateBuild(pallets.mantaPay as MantaPayWallet); + }, + async toPrivateSend() { + await toPrivateSend(pallets.mantaPay as MantaPayWallet); + }, + async toPublicSend() { + await toPublicSend(pallets.mantaPay as MantaPayWallet); + }, + async privateTransferSend() { + await privateTransferSend(pallets.mantaPay as MantaPayWallet); + }, + async multiSbtPostBuild(startAssetId: string) { + if (!startAssetId) { + throw new Error('startAssetId is required'); + } + const sbtInfoList: interfaces.SbtInfo[] = [ + { assetId: new BN(startAssetId) }, + { assetId: new BN(startAssetId).add(new BN(1)) }, + ]; + await multiSbtPostBuild( + pallets.mantaSbt as MantaSbtWallet, + sbtInfoList, + ); + }, + async getIdentityProof() { + await getIdentityProof(pallets.mantaSbt as MantaSbtWallet); + }, + async relaunch() { + await relaunch(pallets.mantaPay); + await relaunch(pallets.mantaSbt); + }, +}; + +// Now you can execute the corresponding method to test + +// test toPrivate +window.actions.toPrivateSend(); + +// test privateTransfer +window.actions.privateTransferSend(); + +// test toPublic +window.actions.toPublicSend(); +``` diff --git a/manta-js/examples/asset-webpack-ts/index.html b/manta-js/examples/sdk-example/index.html similarity index 100% rename from manta-js/examples/asset-webpack-ts/index.html rename to manta-js/examples/sdk-example/index.html diff --git a/manta-js/examples/sdk-example/index.ts b/manta-js/examples/sdk-example/index.ts new file mode 100644 index 00000000..3071f3aa --- /dev/null +++ b/manta-js/examples/sdk-example/index.ts @@ -0,0 +1,343 @@ +import BN from 'bn.js'; +import type { interfaces } from 'manta-extension-sdk'; +import type { Signer as InjectSigner } from '@polkadot/api/types'; +import type { SubmittableExtrinsic } from '@polkadot/api/types'; + +import { + BaseWallet, + MantaPayWallet, + MantaSbtWallet, +} from 'manta-extension-sdk'; + +import { + web3Accounts, + web3Enable, + web3FromSource, +} from '@polkadot/extension-dapp'; + +import { + get as getIdbData, + set as setIdbData, + del as delIdbData, +} from 'idb-keyval'; + +const apiEndpoint = 'wss://zenlink.zqhxuyuan.cloud:444'; +const nativeTokenDecimals = 12; + +const currentNetwork: interfaces.Network = 'Dolphin'; + +const assetId = new BN('1'); +// toPrivate Amount (50 DOL) +const transferInAmount = new BN(50).mul( + new BN(10).pow(new BN(nativeTokenDecimals)), +); +// privateTransfer && toPublic Amount (5 DOL) +const transferOutAmount = transferInAmount.div(new BN(10)); + +let currentSeedPhrase = + 'spike napkin obscure diamond slice style excess table process story excuse absurd'; + +// If you need to test a new account without any Ledger data, please update it to true +const newAccountFeatureEnabled = false; + +const loggingEnabled = true; + +const provingFilePath = + 'https://media.githubusercontent.com/media/Manta-Network/manta-rs/main/manta-parameters/data/pay/proving'; +const parametersFilePath = + 'https://raw.githubusercontent.com/Manta-Network/manta-rs/main/manta-parameters/data/pay/parameters'; + +interface PolkadotConfig { + polkadotSigner: InjectSigner; + polkadotAddress: string; +} + +let polkadotConfig: PolkadotConfig = null; + +function _log(...message: any[]) { + console.log(`[Demo] ${performance.now().toFixed(4)}: ${message.join(' ')}`); +} + +// Get Polkadot JS Signer and Polkadot JS account address. +const requestPolkadotSignerAndAddress = async () => { + const extensions = await web3Enable('Manta SDK'); + if (extensions.length === 0) { + throw new Error( + 'Polkadot browser extension missing. https://polkadot.js.org/extension/', + ); + } + const allAccounts = await web3Accounts(); + let account = allAccounts[0]; + + const injector = await web3FromSource(account.meta.source); + const polkadotSigner = injector.signer; + const polkadotAddress = account.address; + return { + polkadotSigner, + polkadotAddress, + }; +}; + +const publishTransition = async ( + txs: SubmittableExtrinsic<'promise', any>[], +) => { + for (let i = 0; i < txs.length; i++) { + await txs[i].signAndSend(polkadotConfig.polkadotAddress, () => {}); + } +}; + +const getBaseWallet = async () => { + const baseWallet = await BaseWallet.init({ + apiEndpoint, + loggingEnabled, + provingFilePath, + parametersFilePath, + saveStorageStateToLocal: async ( + palletName: interfaces.PalletName, + network: interfaces.Network, + data: any, + ): Promise => { + try { + await setIdbData(`storage_state_${palletName}_${network}`, data); + } catch (ex) { + console.error(ex); + return false; + } + return true; + }, + getStorageStateFromLocal: async ( + palletName: interfaces.PalletName, + network: interfaces.Network, + ): Promise => { + let result: string; + try { + result = await getIdbData(`storage_state_${palletName}_${network}`); + } catch (ex) { + console.error(ex); + } + return result || null; + }, + }); + BaseWallet.onWasmCalledJsErrorCallback = (err, palletName) => { + console.log(palletName); + console.error(err); + }; + return baseWallet; +}; + +const initMantaPayWallet = async (baseWallet: BaseWallet) => { + const wallet = MantaPayWallet.init(currentNetwork, baseWallet); + await initWalletData(wallet); + return wallet; +}; + +const initMantaSbtWallet = async (baseWallet: BaseWallet) => { + const wallet = MantaSbtWallet.init(currentNetwork, baseWallet); + await initWalletData(wallet); + return wallet; +}; + +const initWalletData = async (privateWallet: interfaces.IPrivateWallet) => { + _log('Initial signer'); + await privateWallet.initialSigner(); + _log('Load user mnemonic'); + await privateWallet.loadUserSeedPhrase(currentSeedPhrase); + const zkAddress = await privateWallet.getZkAddress(); + _log('The zkAddress is: ', zkAddress); + + _log('Wait for api ready'); + + const isInitialed = (await getIdbData(`storage_state_${privateWallet.palletName}_${currentNetwork}`)); + if (!isInitialed && newAccountFeatureEnabled) { + _log('initialNewAccountWalletSync'); + await privateWallet.initialNewAccountWalletSync(); + } else { + _log('initialWalletSync'); + await privateWallet.initialWalletSync(); + } +}; + +const queryTransferResult = async ( + privateWallet: interfaces.IPrivateWallet, + initialPrivateBalance: BN, +) => { + let retryTimes = 0; + while (true) { + await new Promise((r) => setTimeout(r, 5000)); + _log('Syncing with ledger...'); + await privateWallet.walletSync(); + let newPrivateBalance = await privateWallet.getZkBalance(assetId); + _log('Private Balance after sync: ', newPrivateBalance.toString()); + + if (!initialPrivateBalance.eq(newPrivateBalance)) { + _log('Detected balance change after sync!'); + _log('Retry times: ', retryTimes.toString()); + _log('Old balance: ', initialPrivateBalance.toString()); + _log('New balance: ', newPrivateBalance.toString()); + break; + } + retryTimes += 1; + if (retryTimes >= 10) { + _log('Check balance timeout'); + break; + } + } +}; + +/// Test to execute a `PrivateBuild` transaction. +/// without publishing the transaction. +const toPrivateBuild = async (privateWallet: MantaPayWallet) => { + await privateWallet.walletSync(); + const initialPrivateBalance = await privateWallet.getZkBalance(assetId); + _log('The initial balance is: ', initialPrivateBalance.toString()); + const signResult = await privateWallet.toPrivateBuild( + assetId, + transferInAmount, + ); + _log('The result of the signing: ', signResult); + _log('Full: ', JSON.stringify(signResult.txs)); + // remove first 3 bytes of the signResult + _log( + 'For xcm remote transact payload, please use: ["0x' + + JSON.stringify(signResult.txs).slice(10), + ); +}; + +/// Test to execute a `ToPrivate` transaction. +const toPrivateSend = async (privateWallet: MantaPayWallet) => { + await privateWallet.walletSync(); + const initialPrivateBalance = await privateWallet.getZkBalance(assetId); + _log('The initial balance is: ', initialPrivateBalance.toString()); + const transaction = await privateWallet.toPrivateBuild( + assetId, + transferInAmount, + ); + await publishTransition(transaction.txs); + await queryTransferResult(privateWallet, initialPrivateBalance); +}; + +/// Test to execute a `PrivateTransfer` transaction. +const privateTransferSend = async (privateWallet: MantaPayWallet) => { + const toPrivateTestAddress = '2JZCtGNR1iz6dR613g9p2VGHAAmXQK8xYJ117DLzs4s4'; + await privateWallet.walletSync(); + const initialPrivateBalance = await privateWallet.getZkBalance(assetId); + _log('The initial balance is: ', initialPrivateBalance.toString()); + const transaction = await privateWallet.privateTransferBuild( + assetId, + transferOutAmount, + toPrivateTestAddress, + ); + await publishTransition(transaction.txs); + await queryTransferResult(privateWallet, initialPrivateBalance); +}; + +/// Test to execute a `ToPublic` transaction. +const toPublicSend = async (privateWallet: MantaPayWallet) => { + await privateWallet.walletSync(); + const initialPrivateBalance = await privateWallet.getZkBalance(assetId); + _log('The initial balance is: ', initialPrivateBalance.toString()); + + const transaction = await privateWallet.toPublicBuild( + assetId, + transferOutAmount, + polkadotConfig.polkadotAddress, + ); + await publishTransition(transaction.txs); + await queryTransferResult(privateWallet, initialPrivateBalance); +}; + +/// Test to execute a `MultiSbtPostBuild` transaction. +const multiSbtPostBuild = async ( + privateWallet: MantaSbtWallet, + sbtInfoList: interfaces.SbtInfo[], +) => { + await privateWallet.walletSync(); + const result = await privateWallet.multiSbtPostBuild(sbtInfoList); + console.log(result); +}; + +const getIdentityProof = async (privateWallet: MantaSbtWallet) => { + const proof = await privateWallet.getIdentityProof( + '{"identifier":{"is_transparent":false,"utxo_commitment_randomness":[218,12,198,205,243,45,111,55,97,232,107,40,237,202,174,102,12,100,161,170,141,2,173,101,117,161,177,116,146,37,81,31]},"asset":{"id":[82,77,144,171,218,215,31,37,190,239,170,153,12,42,235,151,22,238,79,66,34,183,22,37,117,55,167,12,74,225,51,45],"value":1}}', + polkadotConfig.polkadotAddress, + ); + _log(`getIdentityProof result: `); + console.log(proof); +}; + +const relaunch = async (privateWallet: interfaces.IPrivateWallet) => { + await privateWallet.resetState(); + await delIdbData( + `storage_state_${privateWallet.palletName}_${currentNetwork}`, + ); + currentSeedPhrase = + 'must payment asthma judge tray recall another course zebra morning march engine'; + await initWalletData(privateWallet); +}; + +const pallets: Record = { + mantaPay: null, + mantaSbt: null, +}; + +// @ts-ignore +window.actions = { + getPallets() { + return pallets; + }, + async toPrivateBuild() { + await toPrivateBuild(pallets.mantaPay as MantaPayWallet); + }, + async toPrivateSend() { + await toPrivateSend(pallets.mantaPay as MantaPayWallet); + }, + async toPublicSend() { + await toPublicSend(pallets.mantaPay as MantaPayWallet); + }, + async privateTransferSend() { + await privateTransferSend(pallets.mantaPay as MantaPayWallet); + }, + async multiSbtPostBuild(startAssetId: string) { + if (!startAssetId) { + throw new Error('startAssetId is required'); + } + const sbtInfoList: interfaces.SbtInfo[] = [ + { assetId: new BN(startAssetId) }, + { assetId: new BN(startAssetId).add(new BN(1)) }, + ]; + await multiSbtPostBuild( + pallets.mantaSbt as MantaSbtWallet, + sbtInfoList, + ); + }, + async getIdentityProof() { + await getIdentityProof(pallets.mantaSbt as MantaSbtWallet); + }, + async relaunch() { + await relaunch(pallets.mantaPay); + await relaunch(pallets.mantaSbt); + }, +}; + +async function main() { + _log('Initial base'); + const baseWallet = await getBaseWallet(); + _log('Initial base end'); + + _log('Initial polkadot signer'); + polkadotConfig = await requestPolkadotSignerAndAddress(); + baseWallet.api.setSigner(polkadotConfig.polkadotSigner); + _log('Initial polkadot signer end'); + + _log('Initial pallet mantaPay'); + pallets.mantaPay = await initMantaPayWallet(baseWallet); + _log('Initial pallet mantaPay end'); + + _log('Initial pallet mantaSBT'); + pallets.mantaSbt = await initMantaSbtWallet(baseWallet); + _log('Initial pallet mantaSBT end'); + + _log('Initial successful'); +} + +main(); diff --git a/manta-js/examples/asset-webpack-ts/package.json b/manta-js/examples/sdk-example/package.json similarity index 61% rename from manta-js/examples/asset-webpack-ts/package.json rename to manta-js/examples/sdk-example/package.json index 35d015df..86274642 100644 --- a/manta-js/examples/asset-webpack-ts/package.json +++ b/manta-js/examples/sdk-example/package.json @@ -1,14 +1,11 @@ { - "name": "asset-webpack-ts", + "name": "sdk-example", "version": "1.0.0", - "description": "manta api example", + "description": "manta sdk example", "main": "./index.ts", "scripts": { - "serve": "webpack serve", - "build": "webpack --mode=production --define-process-env-node-env=production", - "build:dev": "webpack --mode=development", - "build:prod": "webpack --mode=production --define-process-env-node-env=production", - "watch": "webpack --watch" + "start": "yarn --cwd ../../package && yarn --cwd ../../package build-browser && yarn add file:../../package && yarn serve", + "serve": "webpack serve" }, "dependencies": { "@polkadot/extension-dapp": "^0.45.3", @@ -21,16 +18,16 @@ "@types/node": "^18.11.9", "@types/webpack": "^5.28.0", "@webpack-cli/generators": "^3.0.0", - "html-webpack-plugin": "^5.5.0", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.0", - "webpack-dev-server": "^4.11.1", - "ts-loader": "^9.4.1", - "typescript": "^4.9.3", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^11.0.0", "crypto-browserify": "^3.12.0", - "declaration-bundler-webpack-plugin": "^1.0.3" + "declaration-bundler-webpack-plugin": "^1.0.3", + "html-webpack-plugin": "^5.5.0", + "ts-loader": "^9.4.1", + "typescript": "^4.9.3", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.0", + "webpack-dev-server": "^4.11.1" }, "browser": { "crypto": false diff --git a/manta-js/examples/asset-webpack-ts/tsconfig.json b/manta-js/examples/sdk-example/tsconfig.json similarity index 100% rename from manta-js/examples/asset-webpack-ts/tsconfig.json rename to manta-js/examples/sdk-example/tsconfig.json diff --git a/manta-js/examples/asset-webpack-ts/webpack.config.js b/manta-js/examples/sdk-example/webpack.config.js similarity index 100% rename from manta-js/examples/asset-webpack-ts/webpack.config.js rename to manta-js/examples/sdk-example/webpack.config.js diff --git a/manta-js/examples/asset-webpack-ts/yarn.lock b/manta-js/examples/sdk-example/yarn.lock similarity index 99% rename from manta-js/examples/asset-webpack-ts/yarn.lock rename to manta-js/examples/sdk-example/yarn.lock index c14a597a..b7c8d94e 100644 --- a/manta-js/examples/asset-webpack-ts/yarn.lock +++ b/manta-js/examples/sdk-example/yarn.lock @@ -868,9 +868,9 @@ integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/node@*", "@types/node@^18.11.9": - version "18.15.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.5.tgz#3af577099a99c61479149b716183e70b5239324a" - integrity sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew== + version "18.15.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.6.tgz#af98ef4a36e7ac5f2d03040f3109fcce972bf6cb" + integrity sha512-YErOafCZpK4g+Rp3Q/PBgZNAsWKGunQTm9FA3/Pbcm0VCriTEzcrutQ/SxSc0rytAp0NoFWue669jmKhEtd0sA== "@types/node@^15.6.2": version "15.14.9" @@ -2175,9 +2175,9 @@ ejs@^3.1.8: jake "^10.8.5" electron-to-chromium@^1.4.284: - version "1.4.337" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.337.tgz#710168240b2dc5fe5eb5f8e4ef9c16d70aedc0ba" - integrity sha512-W8gdzXG86mVPoc56eM8YA+QiLxaAxJ8cmDjxZgfhLLWVvZQxyA918w5tX2JEWApZta45T1/sYcmFHTsTOUE3nw== + version "1.4.338" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.338.tgz#f2f7641e4cebccd98fc46fa39984761935f4e1ba" + integrity sha512-Kfq90LFsNtGOwCZJ77XQa2fSuGQVlEBIGqiccfXxkoYTe5o79dE/9GMXR2R9OWjKGFfRVX4GHYxeCUyA8mRJlw== elliptic@^6.5.3: version "6.5.4" @@ -3381,7 +3381,7 @@ make-fetch-happen@^9.1.0: ssri "^8.0.0" "manta-extension-sdk@file:../../package": - version "1.0.3-beta.3" + version "1.0.3-beta.5" dependencies: "@polkadot/api" "^10.1.4" "@polkadot/types" "^10.1.4" diff --git a/manta-js/package/.eslintrc b/manta-js/package/.eslintrc index 0f45a1ff..9b802e88 100644 --- a/manta-js/package/.eslintrc +++ b/manta-js/package/.eslintrc @@ -14,14 +14,12 @@ "linebreak-style": ["error", "unix"], "quotes": ["error", "single"], "semi": ["error", "always"], - "no-unused-vars": [ - "error", - { "vars": "all", "args": "after-used", "ignoreRestSiblings": false } - ], "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-explicit-any": "off", - "no-useless-escape": "off" + "no-useless-escape": "off", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "error" }, "env": { "browser": true, diff --git a/manta-js/package/package.json b/manta-js/package/package.json index e7666e48..771a998c 100644 --- a/manta-js/package/package.json +++ b/manta-js/package/package.json @@ -1,6 +1,6 @@ { "name": "manta-extension-sdk", - "version": "1.0.3-beta.3", + "version": "1.0.5", "description": "manta.js sdk", "main": "./dist/browser/index.js", "exports": { diff --git a/manta-js/package/src/BaseWallet.ts b/manta-js/package/src/BaseWallet.ts new file mode 100644 index 00000000..0fbcb0d7 --- /dev/null +++ b/manta-js/package/src/BaseWallet.ts @@ -0,0 +1,138 @@ +import { ApiPromise, WsProvider } from '@polkadot/api'; +import * as mantaWasm from './wallet/crate/pkg/manta_wasm_wallet'; +import type { + SaveStorageStateToLocal, + GetStorageStateFromLocal, + BaseWalletConfig, + IBaseWallet, + PalletName, +} from './interfaces'; +import { PAY_PARAMETER_NAMES, PAY_PROVING_NAMES } from './constants'; +import mantaConfig from './config.json'; +import { fetchFiles, log } from './utils'; + +export default class BaseWallet implements IBaseWallet { + api: ApiPromise; + apiEndpoint: string | string[]; + apiTimeout: number; + wasm: any; + loggingEnabled: boolean; + fullParameters: any; + multiProvingContext: any; + saveStorageStateToLocal: SaveStorageStateToLocal; + getStorageStateFromLocal: GetStorageStateFromLocal; + walletIsBusy = false; + + static onWasmCalledJsErrorCallback: (err: Error, palletName: PalletName) => void; + + constructor( + wasm: any, + apiEndpoint: string | string[], + fullParameters: any, + multiProvingContext: any, + saveStorageStateToLocal: SaveStorageStateToLocal, + getStorageStateFromLocal: GetStorageStateFromLocal, + loggingEnabled: boolean, + apiTimeout?: number, + ) { + this.wasm = wasm; + this.fullParameters = fullParameters; + this.multiProvingContext = multiProvingContext; + this.saveStorageStateToLocal = saveStorageStateToLocal; + this.getStorageStateFromLocal = getStorageStateFromLocal; + this.loggingEnabled = loggingEnabled; + + this.updateApi(apiEndpoint, apiTimeout); + } + + protected static log( + loggingEnabled: boolean, + message: string, + name = 'BaseWallet', + ) { + log(loggingEnabled, message, name); + } + + log(message: string, name?: string) { + BaseWallet.log(this.loggingEnabled, message, name); + } + + updateApi(apiEndpoint: string | string[], apiTimeout?: number) { + this.log('Initial api'); + + this.apiEndpoint = apiEndpoint; + this.apiTimeout = apiTimeout || 10 * 1000; + + this.api = new ApiPromise({ + provider: new WsProvider(this.apiEndpoint, 2500, {}, this.apiTimeout), + types: mantaConfig.TYPES, + rpc: mantaConfig.RPC, + }); + + if (this.loggingEnabled) { + this.api.on('connected', () => { + Promise.all([ + this.api.rpc.system.chain(), + this.api.rpc.system.name(), + this.api.rpc.system.version(), + ]).then(([chain, nodeName, nodeVersion]) => { + this.log( + `Wallet is connected to chain ${chain} using ${nodeName} v${nodeVersion}`, + ); + }); + }); + } + return this.api; + } + + async disconnectApi() { + await this.api.disconnect(); + return true; + } + + static async init(config: BaseWalletConfig) { + const loggingEnabled = Boolean(config.loggingEnabled); + if (loggingEnabled) { + mantaWasm.init_panic_hook(); + } + BaseWallet.log(loggingEnabled, 'Start download'); + const provingFileList = await fetchFiles( + config.provingFilePath, + PAY_PROVING_NAMES, + ); + const parameterFileList = await fetchFiles( + config.parametersFilePath, + PAY_PARAMETER_NAMES, + ); + BaseWallet.log(loggingEnabled, 'Download successful'); + + const multiProvingContext = new mantaWasm.RawMultiProvingContext( + ...(provingFileList as [Uint8Array, Uint8Array, Uint8Array]), + ); + const fullParameters = new mantaWasm.RawFullParameters( + ...(parameterFileList as [ + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + ]), + ); + return new BaseWallet( + mantaWasm, + config.apiEndpoint, + fullParameters, + multiProvingContext, + config.saveStorageStateToLocal, + config.getStorageStateFromLocal, + loggingEnabled, + config.apiTimeout + ); + } +} diff --git a/manta-js/package/src/PrivateWallet.ts b/manta-js/package/src/PrivateWallet.ts new file mode 100644 index 00000000..40e20077 --- /dev/null +++ b/manta-js/package/src/PrivateWallet.ts @@ -0,0 +1,366 @@ +import BN from 'bn.js'; +import { base58Encode } from '@polkadot/util-crypto'; +import type { Wallet as WasmWallet } from './wallet/crate/pkg/manta_wasm_wallet'; +import type { + Address, + IPrivateWallet, + IBaseWallet, + ILedgerApi, + PalletName, + Network, +} from './interfaces'; +import LedgerApi from './ledger-api'; +import { wrapWasmError } from './utils'; +import BaseWallet from './BaseWallet'; + +export default class PrivateWallet implements IPrivateWallet { + palletName: PalletName; + baseWallet: IBaseWallet; + wasmWallet: WasmWallet; + ledgerApi: ILedgerApi; + network: Network; + + initialSyncIsFinished = false; + isBindAuthorizationContext = false; + + constructor( + palletName: PalletName, + network: Network, + baseWallet: IBaseWallet, + wasmWallet: WasmWallet, + ledgerApi: ILedgerApi, + ) { + this.palletName = palletName; + this.network = network; + this.baseWallet = baseWallet; + this.wasmWallet = wasmWallet; + this.ledgerApi = ledgerApi; + } + + get wasm() { + return this.baseWallet.wasm; + } + + get api() { + return this.baseWallet.api; + } + + get walletIsBusy() { + return this.baseWallet.walletIsBusy; + } + + set walletIsBusy(result: boolean) { + this.baseWallet.walletIsBusy = result; + } + + /// + /// Public Methods + /// + + /// Initializes the PrivateWallet class, for a corresponding environment and network. + protected static getInitialParams( + palletName: PalletName, + baseWallet: IBaseWallet, + ): { + wasmWallet: any; + ledgerApi: LedgerApi; + } { + const wasmWallet = new baseWallet.wasm.Wallet(); + const ledgerApi = new LedgerApi( + baseWallet.api, + palletName, + baseWallet.loggingEnabled, + (err) => { + console.error(err); + baseWallet.walletIsBusy = false; + if (typeof BaseWallet.onWasmCalledJsErrorCallback === 'function') { + BaseWallet.onWasmCalledJsErrorCallback(err, palletName); + } + }, + ); + return { + wasmWallet, + ledgerApi, + }; + } + + dropAuthorizationContext() { + this.wasmWallet.drop_authorization_context(this.getWasmNetWork()); + this.isBindAuthorizationContext = false; + return true; + } + + dropUserSeedPhrase() { + this.wasmWallet.drop_accounts(this.getWasmNetWork()); + return true; + } + + loadUserSeedPhrase(seedPhrase: string) { + const wasmSeedPhrase = this.wasm.mnemonic_from_phrase(seedPhrase); + const accountTable = this.wasm.accounts_from_mnemonic(wasmSeedPhrase); + this.wasmWallet.load_accounts(accountTable, this.getWasmNetWork()); + this.wasmWallet.update_authorization_context(this.getWasmNetWork()); + this.isBindAuthorizationContext = true; + return true; + } + + loadAuthorizationContext(seedPhrase: string) { + const wasmSeedPhrase = this.wasm.mnemonic_from_phrase(seedPhrase); + const authorizationContext = this.wasm.authorization_context_from_mnemonic( + wasmSeedPhrase, + this.baseWallet.fullParameters, + ); + this.wasmWallet.load_authorization_context( + authorizationContext, + this.getWasmNetWork(), + ); + this.isBindAuthorizationContext = true; + return true; + } + + async initialSigner() { + const result = await this.setNetwork(this.network); + return result; + } + + /// After initial PrivateWallet, need to call setNetwork + async setNetwork(network: Network) { + this.network = network; + const storageData = await this.baseWallet.getStorageStateFromLocal( + this.palletName, + this.network, + ); + + this.log('Start initial signer'); + + const wasmSigner = new this.wasm.Signer( + this.baseWallet.fullParameters, + this.baseWallet.multiProvingContext, + storageData, + ); + + this.log('Initial signer successful'); + + const wasmLedger = new this.baseWallet.wasm.PolkadotJsLedger( + this.ledgerApi, + ); + + this.wasmWallet.set_network(wasmLedger, wasmSigner, this.getWasmNetWork()); + return true; + } + + /// This method is optimized based on initialWalletSync + /// + /// Requirements: Must be called once after creating an instance of PrivateWallet + /// and must be called before walletSync(). + /// If it is a new wallet (the current solution is that the native token is 0), + /// you can call this method to save initialization time + async initialNewAccountWalletSync(): Promise { + if (!this.isBindAuthorizationContext) { + throw new Error('No ViewingKey'); + } + try { + await this.waitForWallet(); + this.walletIsBusy = true; + this.log('Start initial new account'); + await this.wasmWallet.initial_sync(this.getWasmNetWork()); + this.log('Initial new account completed'); + await this.saveStateToLocal(); + this.walletIsBusy = false; + this.initialSyncIsFinished = true; + return true; + } catch (ex) { + this.walletIsBusy = false; + throw wrapWasmError(ex); + } + } + + /// Performs full wallet recovery. Restarts `self` with an empty state and + /// performs a synchronization against the signer and ledger to catch up to + /// the current checkpoint and balance state. + /// + /// Requirements: Must be called once after creating an instance of PrivateWallet + /// and must be called before walletSync(). + /// If it is a new wallet (the current solution is that the native token is 0), + /// you can call initialNewAccountWalletSync to save initialization time + async initialWalletSync(): Promise { + const result = await this.loopSyncPartialWallet(true); + return result; + } + + /// Pulls data from the ledger, synchronizing the currently connected wallet and + /// balance state. This method runs until all the ledger data has arrived at and + /// has been synchronized with the wallet. + async walletSync(): Promise { + if (!this.initialSyncIsFinished) { + throw new Error('Must call initialWalletSync before walletSync!'); + } + const result = await this.loopSyncPartialWallet(false); + return result; + } + + /// Returns the ZkAddress (Zk Address) of the currently connected manta-signer instance. + async getZkAddress(): Promise
{ + try { + await this.waitForWallet(); + this.walletIsBusy = true; + const zkAddressRaw = await this.wasmWallet.address(this.getWasmNetWork()); + const zkAddressBytes = [...zkAddressRaw.receiving_key]; + const zkAddress = base58Encode(zkAddressBytes); + this.walletIsBusy = false; + return zkAddress; + } catch (ex) { + this.walletIsBusy = false; + throw wrapWasmError(ex); + } + } + + /// Returns the zk balance of the currently connected zkAddress for the currently + /// connected network. + async getZkBalance(assetId: BN): Promise { + try { + await this.waitForWallet(); + this.walletIsBusy = true; + const balanceString = await this.wasmWallet.balance( + assetId.toString(), + this.getWasmNetWork(), + ); + const balance = new BN(balanceString); + this.walletIsBusy = false; + return balance; + } catch (ex) { + this.walletIsBusy = false; + console.error('Failed to fetch zk balance.', ex); + throw wrapWasmError(ex); + } + } + + /// Returns the multi zk balance of the currently connected zkAddress for the currently + /// connected network. + async getMultiZkBalance(assetIds: BN[]): Promise { + try { + await this.waitForWallet(); + this.walletIsBusy = true; + const balances = await Promise.all( + assetIds.map(async (assetId) => { + const balanceString = await this.wasmWallet.balance( + assetId.toString(), + this.getWasmNetWork(), + ); + return new BN(balanceString); + }), + ); + this.walletIsBusy = false; + return balances; + } catch (ex) { + this.walletIsBusy = false; + console.error('Failed to fetch zk balance.', ex); + throw wrapWasmError(ex); + } + } + + /// reset instance state + async resetState() { + this.initialSyncIsFinished = false; + this.isBindAuthorizationContext = false; + const wasmSigner = new this.wasm.Signer( + this.baseWallet.fullParameters, + this.baseWallet.multiProvingContext, + null, + ); + const wasmLedger = new this.wasm.PolkadotJsLedger(this.ledgerApi); + this.wasmWallet.set_network(wasmLedger, wasmSigner, this.getWasmNetWork()); + this.dropUserSeedPhrase(); + this.dropAuthorizationContext(); + return true; + } + + /// + /// Private Methods + /// + + /// Conditionally logs the contents of `message` depending on if `loggingEnabled` + /// is set to `true`. + protected log(message: string) { + this.baseWallet.log(message, `Private Wallet ${this.palletName}`); + } + + protected getWasmNetWork(): any { + return this.wasm.Network.from_string(`"${this.network}"`); + } + + // WASM wallet doesn't allow you to call two methods at once, so before + // calling methods it is necessary to wait for a pending call to finish. + protected async waitForWallet(): Promise { + while (this.walletIsBusy === true) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } + + private async loopSyncPartialWallet(isInitial: boolean): Promise { + if (!this.isBindAuthorizationContext) { + throw new Error('No ViewingKey'); + } + try { + await this.waitForWallet(); + this.walletIsBusy = true; + if (isInitial) { + await this.wasmWallet.reset_state(this.getWasmNetWork()); + } + let syncResult = null; + let retryTimes = 0; + do { + this.log('Sync partial start'); + syncResult = await this.syncPartialWallet(); + this.log( + `Sync partial end, success: ${syncResult.success}, continue: ${syncResult.continue}`, + ); + if (!syncResult.success) { + retryTimes += 1; + } else { + retryTimes = 0; + } + if (retryTimes > 5) { + throw new Error('Sync partial failed'); + } + } while (syncResult && syncResult.continue); + this.walletIsBusy = false; + if (isInitial) { + this.initialSyncIsFinished = true; + } + } catch (ex) { + this.walletIsBusy = false; + throw wrapWasmError(ex); + } + return true; + } + + private async syncPartialWallet(): Promise<{ + success: boolean; + continue: boolean; + }> { + try { + const result = await this.wasmWallet.sync_partial(this.getWasmNetWork()); + await this.saveStateToLocal(); + return { + success: true, + continue: Object.keys(result)[0] === 'Continue', + }; + } catch (ex) { + console.error('Sync partial failed.', ex); + return { + success: false, + continue: true, + }; + } + } + + private async saveStateToLocal() { + const stateData = await this.wasmWallet.get_storage(this.getWasmNetWork()); + await this.baseWallet.saveStorageStateToLocal( + this.palletName, + this.network, + stateData, + ); + } +} diff --git a/manta-js/package/src/api/index.js b/manta-js/package/src/api/index.js deleted file mode 100644 index bfd55401..00000000 --- a/manta-js/package/src/api/index.js +++ /dev/null @@ -1,322 +0,0 @@ -// Polkadot-JS Ledger Integration - -// Polkadot-JS Ledger API - -import { base64Decode } from '@polkadot/util-crypto'; -import * as $ from 'manta-scale-codec'; -import { nToBigInt, u8aToU8a } from '@polkadot/util'; - -export class ApiConfig { - constructor( - maxReceiversPullSize, - maxSendersPullSize, - pullCallback = null, - errorCallback = null, - loggingEnabled = false, - ) { - this.loggingEnabled = loggingEnabled; - this.maxReceiversPullSize = maxReceiversPullSize; - this.maxSendersPullSize = maxSendersPullSize; - this.pullCallback = pullCallback; - this.errorCallback = errorCallback; - } -} - -export default class Api { - // Constructs an API from a config - constructor(api,config) { - this.loggingEnabled = config.loggingEnabled; - this.config = config; - this.api = api; - this.externalAccountSigner = null; - this.maxReceiversPullSize = this.config.maxReceiversPullSize; - this.maxSendersPullSize = this.config.maxSendersPullSize; - this.txResHandler = null; - this.pullCallback = this.config.pullCallback; - this.errorCallback = this.config.errorCallback; - } - - _log(message) { - if (this.loggingEnabled) { - console.log('[INFO]: '+ message); - console.log(performance.now()); - } - } - - // Sets the transaction result handler to `txResHandler`. - setTxResHandler = (txResHandler) => { - this.txResHandler = txResHandler; - }; - - // Sets the externalAccountSigner to `signer`. - setExternalAccountSigner = (signer) => { - this.externalAccountSigner = signer; - }; - - // Converts an `outgoing note` into a JSON object. - _outgoing_note_to_json(note) { - // [[u8; 32], 2] - const ciphertext = note.ciphertext; - const cipher0 = Array.from(ciphertext[0]); - const cipher1 = Array.from(ciphertext[1]); - return { - ephemeral_public_key: Array.from(u8aToU8a(note.ephemeral_public_key)), - ciphertext: [cipher0, cipher1] - }; - } - - // Converts an `light incoming note` into a JSON object. - _light_incoming_note_to_json(note) { - // [[u8; 32], 3] - const ciphertext = note.ciphertext; - const cipher0 = Array.from(ciphertext[0]); - const cipher1 = Array.from(ciphertext[1]); - const cipher2 = Array.from(ciphertext[2]); - return { - ephemeral_public_key: Array.from(u8aToU8a(note.ephemeral_public_key)), - ciphertext: [cipher0, cipher1, cipher2] - }; - } - - // Converts an `incoming note` into a JSON object. - _incoming_note_to_json(note) { - // [[u8; 32]; 3] - const ciphertext = note.ciphertext; - const cipher0 = Array.from(ciphertext[0]); - const cipher1 = Array.from(ciphertext[1]); - const cipher2 = Array.from(ciphertext[2]); - return { - ephemeral_public_key: Array.from( - u8aToU8a(note.ephemeral_public_key) - ), - tag: Array.from(u8aToU8a(note.tag)), - ciphertext: [cipher0, cipher1, cipher2] - }; - } - - // Converts an `full incoming note` into a JSON object. - _full_incoming_note_to_json(note) { - return { - address_partition: note.address_partition, - incoming_note: this._incoming_note_to_json(note.incoming_note), - light_incoming_note: this._light_incoming_note_to_json(note.light_incoming_note), - }; - } - - // Converts an `utxo` into a JSON object. - _utxo_to_json(utxo) { - const asset_id = Array.from(u8aToU8a(utxo.public_asset.id)); - const asset_value = Array.from(u8aToU8a(utxo.public_asset.value)); - return { - is_transparent: utxo.is_transparent, - public_asset: { - id: asset_id, - value: asset_value, - }, - commitment: Array.from(u8aToU8a(utxo.commitment)) - }; - } - - _current_path_to_json(currentPath) { - const sibling_digest = Array.from(u8aToU8a(currentPath.sibling_digest)); - const leaf_index = currentPath.leaf_index; - const inner_path = currentPath.inner_path.map((path) => Array.from(u8aToU8a(path))); - return { - sibling_digest, - leaf_index, - inner_path, - }; - } - - // Pulls data from the ledger from the `checkpoint` or later, returning the new checkpoint. - async initial_pull(checkpoint) { - try { - await this.api.isReady; - - this._log('checkpoint ' + JSON.stringify(checkpoint)); - await this.api.isReady; - - let result = await this.api.rpc.mantaPay.dense_initial_pull(checkpoint, this.maxReceiversPullSize); - - this._log('initial pull result ' + JSON.stringify(result)); - - const decodedUtxoData = $Utxos.decode(base64Decode(result.utxo_data.toString())); - const utxoData = decodedUtxoData.map((utxo) => this._utxo_to_json(utxo)); - - const decodedMemberShipProofData = $CurrentPaths.decode(base64Decode(result.membership_proof_data.toString())); - const membershipProofData = decodedMemberShipProofData.map((currentPath) => this._current_path_to_json(currentPath)); - - const pull_result = { - should_continue: result.should_continue.isTrue, - utxo_data: utxoData, - membership_proof_data: membershipProofData, - nullifier_count: result.nullifier_count.toString(), - }; - this._log('initial pull response: ' + JSON.stringify(pull_result)); - - // JSON.stringify does not support bigint - pull_result.nullifier_count = nToBigInt(result.nullifier_count); - - return pull_result; - } catch (err) { - console.error(err); - if (this.errorCallback) { - this.errorCallback(); - } - } - } - - // Pulls data from the ledger from the `checkpoint` or later, returning the new checkpoint. - async pull(checkpoint) { - try { - await this.api.isReady; - - this._log('checkpoint ' + JSON.stringify(checkpoint)); - let result = await this.api.rpc.mantaPay.dense_pull_ledger_diff( - checkpoint, - this.maxReceiversPullSize, - this.maxSendersPullSize - ); - - this._log('pull result ' + JSON.stringify(result)); - - const decodedReceivers = $Receivers.decode( - base64Decode(result.receivers.toString()) - ); - $.assert($Receivers, decodedReceivers); - const receivers = decodedReceivers.map((receiver) => { - return [ - this._utxo_to_json(receiver[0]), - this._full_incoming_note_to_json(receiver[1]) - ]; - }); - - const decodedSenders = $Senders.decode( - base64Decode(result.senders.toString()) - ); - $.assert($Senders, decodedSenders); - const senders = decodedSenders.map((sender) => { - return [ - Array.from(u8aToU8a(sender[0])), - this._outgoing_note_to_json(sender[1]), - ]; - }); - - if (this.pullCallback) { - this.pullCallback( - receivers, - senders, - checkpoint.sender_index, - result.sender_recievers_total.toNumber() - ); - } - const pull_result = { - should_continue: result.should_continue.isTrue, - receivers: receivers, - senders: senders, - }; - this._log('pull response: ' + JSON.stringify(pull_result)); - return pull_result; - } catch (err) { - console.error(err); - if (this.errorCallback) { - this.errorCallback(); - } - } - } - - // Maps a transfer post object to its corresponding MantaPay extrinsic. - async _map_post_to_transaction(post) { - let sources = post.sources.length; - let senders = post.sender_posts.length; - let receivers = post.receiver_posts.length; - let sinks = post.sinks.length; - if (sources == 1 && senders == 0 && receivers == 1 && sinks == 0) { - const mint_tx = await this.api.tx.mantaPay.toPrivate(post); - return mint_tx; - } else if (sources == 0 && senders == 2 && receivers == 2 && sinks == 0) { - const private_transfer_tx = await this.api.tx.mantaPay.privateTransfer( - post - ); - return private_transfer_tx; - } else if (sources == 0 && senders == 2 && receivers == 1 && sinks == 1) { - const reclaim_tx = await this.api.tx.mantaPay.toPublic(post); - return reclaim_tx; - } else { - throw new Error( - 'Invalid transaction shape; there is no extrinsic for a transaction' + - `with ${sources} sources, ${senders} senders, ` + - ` ${receivers} receivers and ${sinks} sinks` - ); - } - } - - // Sends a set of transfer posts (i.e. "transactions") to the ledger. - async push(posts) { - await this.api.isReady; - const transactions = []; - for (let post of posts) { - const transaction = await this._map_post_to_transaction(post); - transactions.push(transaction); - } - try { - const batchTx = await this.api.tx.utility.batch(transactions); - this._log('Batch Transaction: '+ batchTx); - const signResult = await batchTx.signAndSend(this.externalAccountSigner, this.txResHandler); - this._log('Result: ' + signResult); - return { Ok: SUCCESS }; - } catch (err) { - console.error(err); - return { Ok: FAILURE }; - } - } -} - -export const SUCCESS = 'success'; -export const FAILURE = 'failure'; - -const $Asset = $.object( - $.field('id', $.sizedUint8Array(32)), - $.field('value', $.sizedUint8Array(16)) -); - -const $Utxo = $.object( - $.field('is_transparent', $.bool), - $.field('public_asset', $Asset), - $.field('commitment', $.sizedUint8Array(32)) -); - -const $IncomingNote = $.object( - $.field('ephemeral_public_key', $.sizedUint8Array(32)), - $.field('tag', $.sizedUint8Array(32)), - $.field('ciphertext', $.sizedArray($.sizedUint8Array(32), 3)) -); - -const $LightIncomingNote = $.object( - $.field('ephemeral_public_key', $.sizedUint8Array(32)), - $.field('ciphertext', $.sizedArray($.sizedUint8Array(32), 3)) -); - -const $FullIncomingNote = $.object( - $.field('address_partition', $.u8), - $.field('incoming_note', $IncomingNote), - $.field('light_incoming_note', $LightIncomingNote) -); - -const $OutgoingNote = $.object( - $.field('ephemeral_public_key', $.sizedUint8Array(32)), - $.field('ciphertext', $.sizedArray($.sizedUint8Array(32), 2)) -); - -export const $Receivers = $.array($.tuple($Utxo, $FullIncomingNote)); -export const $Senders = $.array($.tuple($.sizedUint8Array(32), $OutgoingNote)); - -const $Utxos = $.array($Utxo); - -const $CurrentPath = $.object( - $.field('sibling_digest', $.sizedUint8Array(32)), - $.field('leaf_index', $.u32), - $.field('inner_path', $.array($.sizedUint8Array(32))), -); - -const $CurrentPaths = $.array($CurrentPath); \ No newline at end of file diff --git a/manta-js/package/src/config.json b/manta-js/package/src/config.json new file mode 100644 index 00000000..e5cccb1c --- /dev/null +++ b/manta-js/package/src/config.json @@ -0,0 +1,141 @@ +{ + "RPC": { + "mantaPay": { + "dense_pull_ledger_diff": { + "description": "pull from mantaPay", + "params": [ + { + "name": "checkpoint", + "type": "Checkpoint" + }, + { + "name": "max_receiver", + "type": "u64" + }, + { + "name": "max_sender", + "type": "u64" + } + ], + "type": "DensePullResponse" + }, + "dense_initial_pull": { + "description": "pull initial data from mantaPay", + "params": [ + { + "name": "checkpoint", + "type": "Checkpoint" + }, + { + "name": "max_receiver", + "type": "u64" + } + ], + "type": "DenseInitialSyncResponse" + } + }, + "mantaSBT": { + "dense_pull_ledger_diff": { + "description": "pull from mantaSBT", + "params": [ + { + "name": "checkpoint", + "type": "Checkpoint" + }, + { + "name": "max_receiver", + "type": "u64" + }, + { + "name": "max_sender", + "type": "u64" + } + ], + "type": "DensePullResponse" + }, + "dense_initial_pull": { + "description": "pull initial data from mantaSBT", + "params": [ + { + "name": "checkpoint", + "type": "Checkpoint" + }, + { + "name": "max_receiver", + "type": "u64" + } + ], + "type": "DenseInitialSyncResponse" + } + } + }, + "TYPES": { + "Checkpoint": { + "receiver_index": "[u64; 256]", + "sender_index": "u64" + }, + "Asset": { + "id": "[u8; 32]", + "value": "[u8; 16]" + }, + "Utxo": { + "is_transparent": "bool", + "public_asset": "Asset", + "commitment": "[u8; 32]" + }, + "IncomingNote": { + "ephemeral_public_key": "[u8; 32]", + "tag": "[u8; 32]", + "ciphertext": "[[u8; 32]; 3]" + }, + "LightIncomingNote": { + "ephemeral_public_key": "[u8; 32]", + "ciphertext": "[[u8; 32]; 3]" + }, + "FullIncomingNote": { + "address_partition": "u8", + "incoming_note": "IncomingNote", + "light_incoming_note": "LightIncomingNote" + }, + "OutgoingNote": { + "ephemeral_public_key": "[u8; 32]", + "ciphertext": "[[u8; 32]; 2]" + }, + "DensePullResponse": { + "should_continue": "bool", + "receivers": "String", + "senders": "String", + "senders_receivers_total": "[u8; 16]", + "next_checkpoint": "Option" + }, + "AuthorizationSignature": { + "authorization_key": "[u8; 32]", + "signature": "([u8; 32], [u8; 32])" + }, + "SenderPost": { + "utxo_accumulator_output": "[u8; 32]", + "nullifier_commitment": "[u8; 32]", + "outgoing_note": "OutgoingNote" + }, + "ReceiverPost": { + "utxo": "Utxo", + "note": "FullIncomingNote" + }, + "TransferPost": { + "authorization_signature": "Option", + "asset_id": "Option<[u8; 32]>", + "sources": "Vec<[u8; 16]>", + "sender_posts": "Vec", + "receiver_posts": "Vec", + "sinks": "Vec<[u8; 16]>", + "proof": "[u8; 128]", + "sink_accounts": "Vec<[u8; 32]>" + }, + "DenseInitialSyncResponse": { + "should_continue": "bool", + "utxo_data": "String", + "membership_proof_data": "String", + "nullifier_count": "u128" + } + } +} diff --git a/manta-js/package/src/constants.ts b/manta-js/package/src/constants.ts new file mode 100644 index 00000000..11d7a0c7 --- /dev/null +++ b/manta-js/package/src/constants.ts @@ -0,0 +1,28 @@ +export const PRIVATE_ASSET_PREFIX = 'zk'; + +export const NATIVE_TOKEN_ASSET_ID = '1'; + +export const DEFAULT_PULL_SIZE = 4096; + +export const MAX_RECEIVERS_PULL_SIZE = DEFAULT_PULL_SIZE; +export const MAX_SENDERS_PULL_SIZE = DEFAULT_PULL_SIZE; + +export const PAY_PARAMETER_NAMES = [ + 'address-partition-function.dat', + 'group-generator.dat', + 'incoming-base-encryption-scheme.dat', + 'light-incoming-base-encryption-scheme.dat', + 'nullifier-commitment-scheme.dat', + 'outgoing-base-encryption-scheme.dat', + 'schnorr-hash-function.dat', + 'utxo-accumulator-item-hash.dat', + 'utxo-accumulator-model.dat', + 'utxo-commitment-scheme.dat', + 'viewing-key-derivation-function.dat', +]; + +export const PAY_PROVING_NAMES = [ + 'to-private.lfs', + 'private-transfer.lfs', + 'to-public.lfs', +]; diff --git a/manta-js/package/src/index.ts b/manta-js/package/src/index.ts index d30eafce..2ebff3cb 100644 --- a/manta-js/package/src/index.ts +++ b/manta-js/package/src/index.ts @@ -1,5 +1,8 @@ -import { MantaPrivateWallet, Network, Environment } from './privateWallet'; -import { MantaUtilities } from './utils'; -import * as interfaces from './sdk.interfaces'; +import type * as interfaces from './interfaces'; -export { MantaPrivateWallet, Network, Environment, MantaUtilities, interfaces}; \ No newline at end of file +import BaseWallet from './BaseWallet'; +import PrivateWallet from './PrivateWallet'; + +export * from './pallets'; +export { BaseWallet, PrivateWallet }; +export type { interfaces }; diff --git a/manta-js/package/src/interfaces.ts b/manta-js/package/src/interfaces.ts new file mode 100644 index 00000000..c4bfaad8 --- /dev/null +++ b/manta-js/package/src/interfaces.ts @@ -0,0 +1,118 @@ +import type BN from 'bn.js'; +import type { ApiPromise } from '@polkadot/api'; +import type { Wallet } from './wallet/crate/pkg/manta_wasm_wallet'; +import type { SubmittableExtrinsic } from '@polkadot/api/types'; + +export type PalletName = 'mantaPay' | 'mantaSBT'; + +export type Network = 'Dolphin' | 'Calamari' | 'Manta'; + +export type Address = string; + +export interface ILedgerApi { + api: ApiPromise; + palletName: PalletName; + loggingEnabled: boolean; + errorCallback: (err: Error) => void; + + initial_pull(checkpoint: any): any; + pull(checkpoint: any): any; +} + +export type SignedTransaction = { + posts: any; + transactionData: any; + transactions: SubmittableExtrinsic<'promise', any>[]; + txs: SubmittableExtrinsic<'promise', any>[]; +}; + +export type SignedMultiSbtPost = { + transactionDatas: any[]; + posts: any[]; +}; + +export type SaveStorageStateToLocal = ( + palletName: PalletName, + network: Network, + data: any, +) => Promise; +export type GetStorageStateFromLocal = ( + palletName: PalletName, + network: Network, +) => Promise; + +export type BaseWalletConfig = { + apiEndpoint: string | string[]; + apiTimeout?: number; + loggingEnabled: boolean; + provingFilePath: string; + parametersFilePath: string; + saveStorageStateToLocal: SaveStorageStateToLocal; + getStorageStateFromLocal: GetStorageStateFromLocal; +}; + +export type SbtInfo = { + assetId: BN; + amount?: BN; +} + +export interface IBaseWallet { + api: ApiPromise; + apiEndpoint: string | string[]; + apiTimeout: number; + wasm: any; + loggingEnabled: boolean; + fullParameters: any; + multiProvingContext: any; + saveStorageStateToLocal: SaveStorageStateToLocal; + getStorageStateFromLocal: GetStorageStateFromLocal; + walletIsBusy: boolean; + updateApi(apiEndpoint: string | string[], apiTimeout?: number): ApiPromise; + disconnectApi(): Promise; + log(message: string, name?: string): void; +} + +export interface IPrivateWallet { + palletName: PalletName; + baseWallet: IBaseWallet; + wasmWallet: Wallet; + ledgerApi: any; + initialSyncIsFinished: boolean; + isBindAuthorizationContext: boolean; + network: Network; + + initialSigner(): Promise; + setNetwork(network: Network): Promise; + loadUserSeedPhrase(seedPhrase: string): boolean; + loadAuthorizationContext(seedPhrase: string): boolean; + dropAuthorizationContext(): boolean; + dropUserSeedPhrase(): boolean; + initialWalletSync(): Promise; + initialNewAccountWalletSync(): Promise; + walletSync(): Promise; + getZkAddress(): Promise
; + getZkBalance(assetId: BN): Promise; + getMultiZkBalance(assetIds: BN[]): Promise; + resetState(): Promise; +} + +export interface IMantaPayWallet extends IPrivateWallet { + toPrivateBuild(assetId: BN, amount: BN): Promise; + privateTransferBuild( + assetId: BN, + amount: BN, + toZkAddress: Address, + ): Promise; + toPublicBuild( + assetId: BN, + amount: BN, + polkadotAddress: Address, + ): Promise; +} + +export interface IMantaSbtWallet extends IPrivateWallet { + multiSbtPostBuild( + sbtInfoList: SbtInfo[], + ): Promise; + getIdentityProof(virtualAsset: string, polkadotAddress: Address,): Promise; +} diff --git a/manta-js/package/src/api/README.md b/manta-js/package/src/ledger-api/README.md similarity index 72% rename from manta-js/package/src/api/README.md rename to manta-js/package/src/ledger-api/README.md index 0bd7bc30..7a84b880 100644 --- a/manta-js/package/src/api/README.md +++ b/manta-js/package/src/ledger-api/README.md @@ -7,6 +7,6 @@ To interface Polkadot-JS with the Manta Wallet API we implement the following tw /// receiver data and the latest checkpoint. async fn pull(checkpoint: Checkpoint) -> (Checkpoint, Vec, Vec) -/// Pushes a batch of transfers to the ledger. -async fn push(post: Vec); +/// Pulls data from the ledger starting at `checkpoint`, this is for new account +async fn initial_pull(checkpoint: Checkpoint) -> (any) ``` diff --git a/manta-js/package/src/ledger-api/decodeUtils.ts b/manta-js/package/src/ledger-api/decodeUtils.ts new file mode 100644 index 00000000..a70afa6d --- /dev/null +++ b/manta-js/package/src/ledger-api/decodeUtils.ts @@ -0,0 +1,125 @@ +import * as $ from 'manta-scale-codec'; +import { u8aToU8a } from '@polkadot/util'; + +export default $; + +const $Asset = $.object( + $.field('id', $.sizedUint8Array(32)), + $.field('value', $.sizedUint8Array(16)) +); + +const $Utxo = $.object( + $.field('is_transparent', $.bool), + $.field('public_asset', $Asset), + $.field('commitment', $.sizedUint8Array(32)) +); + +const $IncomingNote = $.object( + $.field('ephemeral_public_key', $.sizedUint8Array(32)), + $.field('tag', $.sizedUint8Array(32)), + $.field('ciphertext', $.sizedArray($.sizedUint8Array(32), 3)) +); + +const $LightIncomingNote = $.object( + $.field('ephemeral_public_key', $.sizedUint8Array(32)), + $.field('ciphertext', $.sizedArray($.sizedUint8Array(32), 3)) +); + +const $FullIncomingNote = $.object( + $.field('address_partition', $.u8), + $.field('incoming_note', $IncomingNote), + $.field('light_incoming_note', $LightIncomingNote) +); + +const $OutgoingNote = $.object( + $.field('ephemeral_public_key', $.sizedUint8Array(32)), + $.field('ciphertext', $.sizedArray($.sizedUint8Array(32), 2)) +); + +export const $Receivers = $.array($.tuple($Utxo, $FullIncomingNote)); +export const $Senders = $.array($.tuple($.sizedUint8Array(32), $OutgoingNote)); + +export const $Utxos = $.array($Utxo); + +const $CurrentPath = $.object( + $.field('sibling_digest', $.sizedUint8Array(32)), + $.field('leaf_index', $.u32), + $.field('inner_path', $.array($.sizedUint8Array(32))) +); + +export const $CurrentPaths = $.array($CurrentPath); + +// Converts an `light incoming note` into a JSON object. +function lightIncomingNoteToJson(note: any) { + // [[u8; 32], 3] + const ciphertext = note.ciphertext; + const cipher0 = Array.from(ciphertext[0]); + const cipher1 = Array.from(ciphertext[1]); + const cipher2 = Array.from(ciphertext[2]); + return { + ephemeral_public_key: Array.from(u8aToU8a(note.ephemeral_public_key)), + ciphertext: [cipher0, cipher1, cipher2], + }; +} + +// Converts an `incoming note` into a JSON object. +function incomingNoteToJson(note: any) { + // [[u8; 32]; 3] + const ciphertext = note.ciphertext; + const cipher0 = Array.from(ciphertext[0]); + const cipher1 = Array.from(ciphertext[1]); + const cipher2 = Array.from(ciphertext[2]); + return { + ephemeral_public_key: Array.from(u8aToU8a(note.ephemeral_public_key)), + tag: Array.from(u8aToU8a(note.tag)), + ciphertext: [cipher0, cipher1, cipher2], + }; +} + +// Converts an `outgoing note` into a JSON object. +export function outgoingNoteToJson(note: any) { + // [[u8; 32], 2]' + const ciphertext = note.ciphertext; + const cipher0 = Array.from(ciphertext[0]); + const cipher1 = Array.from(ciphertext[1]); + return { + ephemeral_public_key: Array.from(u8aToU8a(note.ephemeral_public_key)), + ciphertext: [cipher0, cipher1], + }; +} + +// Converts an `full incoming note` into a JSON object. +export function fullIncomingNoteToJson(note: any) { + return { + address_partition: note.address_partition, + incoming_note: incomingNoteToJson(note.incoming_note), + light_incoming_note: lightIncomingNoteToJson(note.light_incoming_note), + }; +} + +// Converts an `utxo` into a JSON object. +export function utxoToJson(utxo: any) { + const asset_id = Array.from(u8aToU8a(utxo.public_asset.id)); + const asset_value = Array.from(u8aToU8a(utxo.public_asset.value)); + return { + is_transparent: utxo.is_transparent, + public_asset: { + id: asset_id, + value: asset_value, + }, + commitment: Array.from(u8aToU8a(utxo.commitment)), + }; +} + +export function currentPathToJson(currentPath: any) { + const sibling_digest = Array.from(u8aToU8a(currentPath.sibling_digest)); + const leaf_index = currentPath.leaf_index; + const inner_path = currentPath.inner_path.map((path: any) => + Array.from(u8aToU8a(path)) + ); + return { + sibling_digest, + leaf_index, + inner_path, + }; +} diff --git a/manta-js/package/src/ledger-api/index.ts b/manta-js/package/src/ledger-api/index.ts new file mode 100644 index 00000000..84195270 --- /dev/null +++ b/manta-js/package/src/ledger-api/index.ts @@ -0,0 +1,139 @@ +// Polkadot-JS Ledger Integration +import { base64Decode } from '@polkadot/util-crypto'; +import { nToBigInt, u8aToU8a } from '@polkadot/util'; +import { MAX_RECEIVERS_PULL_SIZE, MAX_SENDERS_PULL_SIZE } from '../constants'; +import $, { + $Utxos, + $CurrentPaths, + $Receivers, + $Senders, + outgoingNoteToJson, + fullIncomingNoteToJson, + utxoToJson, + currentPathToJson, +} from './decodeUtils'; +import type { ILedgerApi, PalletName } from '../interfaces'; +import { log, wrapWasmError } from '../utils'; +import { ApiPromise } from '@polkadot/api'; + +export default class LedgerApi implements ILedgerApi { + api: ApiPromise; + palletName: PalletName; + loggingEnabled: boolean; + errorCallback: (err: any) => void; + + constructor( + api: ApiPromise, + palletName: PalletName, + loggingEnabled: boolean, + errorCallback: (err: Error) => void, + ) { + this.api = api; + this.palletName = palletName; + this.loggingEnabled = Boolean(loggingEnabled); + this.errorCallback = errorCallback; + } + + _log(message: string) { + log(this.loggingEnabled, message, 'Ledger Api'); + } + + // Pulls data from the ledger from the `checkpoint` + async initial_pull(checkpoint: any) { + try { + await this.api.isReady; + // @ts-ignore + const result = await this.api.rpc[this.palletName].dense_initial_pull( + checkpoint, + MAX_RECEIVERS_PULL_SIZE, + ); + + if (this.loggingEnabled) { + this._log('initial pull result ' + JSON.stringify(result)); + } + + const decodedUtxoData = $Utxos.decode( + base64Decode(result.utxo_data.toString()), + ); + const utxoData = decodedUtxoData.map((utxo) => utxoToJson(utxo)); + + const decodedMemberShipProofData = $CurrentPaths.decode( + base64Decode(result.membership_proof_data.toString()), + ); + const membershipProofData = decodedMemberShipProofData.map( + (currentPath) => currentPathToJson(currentPath), + ); + + const pull_result = { + should_continue: result.should_continue.isTrue, + utxo_data: utxoData, + membership_proof_data: membershipProofData, + nullifier_count: result.nullifier_count.toString(), + }; + if (this.loggingEnabled) { + this._log('initial pull response: ' + JSON.stringify(pull_result)); + } + // JSON.stringify does not support bigint + pull_result.nullifier_count = nToBigInt(result.nullifier_count); + + return pull_result; + } catch (err) { + const newError = wrapWasmError(err); + if (typeof this.errorCallback === 'function') { + this.errorCallback(newError); + } + throw newError; + } + } + + // Pulls data from the ledger from the `checkpoint` + async pull(checkpoint: any) { + try { + await this.api.isReady; + + if (this.loggingEnabled) { + this._log('checkpoint ' + JSON.stringify(checkpoint)); + } + // @ts-ignore + const result = await this.api.rpc[this.palletName].dense_pull_ledger_diff( + checkpoint, + MAX_RECEIVERS_PULL_SIZE, + MAX_SENDERS_PULL_SIZE, + ); + if (this.loggingEnabled) { + this._log('pull result ' + JSON.stringify(result)); + } + + const decodedReceivers = $Receivers.decode( + base64Decode(result.receivers.toString()), + ); + $.assert($Receivers, decodedReceivers); + const receivers = decodedReceivers.map((receiver) => { + return [utxoToJson(receiver[0]), fullIncomingNoteToJson(receiver[1])]; + }); + + const decodedSenders = $Senders.decode( + base64Decode(result.senders.toString()), + ); + $.assert($Senders, decodedSenders); + const senders = decodedSenders.map((sender) => { + return [Array.from(u8aToU8a(sender[0])), outgoingNoteToJson(sender[1])]; + }); + const pull_result = { + should_continue: result.should_continue.isTrue, + receivers: receivers, + senders: senders, + }; + if (this.loggingEnabled) { + this._log('pull response: ' + JSON.stringify(pull_result)); + } + return pull_result; + } catch (err) { + const newError = wrapWasmError(err); + if (typeof this.errorCallback === 'function') { + this.errorCallback(newError); + } + throw newError; + } + } +} diff --git a/manta-js/package/src/manta-config.json b/manta-js/package/src/manta-config.json deleted file mode 100644 index e2e8d6e4..00000000 --- a/manta-js/package/src/manta-config.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "VERSION": "1.0.0", - "NETWORKS": { - "Dolphin": { - "name": "Dolphin", - "ws": "wss://ws.calamari.seabird.systems", - "ws_local": "ws://127.0.0.1:9801", - "parachainId": 2084, - "decimals": 12, - "subscanUrl": "https://dolphin.subscan.io" - }, - "Calamari": { - "name": "Calamari", - "ws": "wss://ws.calamari.systems/", - "ws_local": "ws://127.0.0.1:9944", - "parachainId": 2084, - "decimals": 12, - "subscanUrl": "https://calamari.subscan.io" - }, - "Manta": { - "name": "Manta", - "ws": null, - "ws_local": null, - "parachainId": null, - "decimals": null, - "subscanUrl": null - } - }, - "PRIVATE_LEDGER_ACCOUNT_PUBLIC_KEY": "0x6d6f646c6d616e74617061790000000000000000000000000000000000000000", - "DEFAULT_PULL_SIZE": 4096, - "RPC": { - "mantaPay": { - "dense_pull_ledger_diff": { - "description": "pull from mantaPay", - "params": [{ - "name": "checkpoint", - "type": "Checkpoint" - }, - { - "name": "max_receiver", - "type": "u64" - }, - { - "name": "max_sender", - "type": "u64" - } - ], - "type": "DensePullResponse" - }, - "dense_initial_pull": { - "description": "pull initial data from mantaPay", - "params": [{ - "name": "checkpoint", - "type": "Checkpoint" - }, - { - "name": "max_receiver", - "type": "u64" - } - ], - "type": "DenseInitialSyncResponse" - } - } - }, - "TYPES": { - "Checkpoint": { - "receiver_index": "[u64; 256]", - "sender_index": "u64" - }, - "Asset": { - "id": "[u8; 32]", - "value": "[u8; 16]" - }, - "Utxo": { - "is_transparent": "bool", - "public_asset": "Asset", - "commitment": "[u8; 32]" - }, - "IncomingNote": { - "ephemeral_public_key": "[u8; 32]", - "tag": "[u8; 32]", - "ciphertext": "[[u8; 32]; 3]" - }, - "LightIncomingNote": { - "ephemeral_public_key": "[u8; 32]", - "ciphertext": "[[u8; 32]; 3]" - }, - "FullIncomingNote": { - "address_partition": "u8", - "incoming_note": "IncomingNote", - "light_incoming_note": "LightIncomingNote" - }, - "OutgoingNote": { - "ephemeral_public_key": "[u8; 32]", - "ciphertext": "[[u8; 32]; 2]" - }, - "DensePullResponse": { - "should_continue": "bool", - "receivers": "String", - "senders": "String", - "senders_receivers_total": "[u8; 16]", - "next_checkpoint": "Option" - }, - "AuthorizationSignature": { - "authorization_key": "[u8; 32]", - "signature": "([u8; 32], [u8; 32])" - }, - "SenderPost": { - "utxo_accumulator_output": "[u8; 32]", - "nullifier_commitment": "[u8; 32]", - "outgoing_note": "OutgoingNote" - }, - "ReceiverPost": { - "utxo": "Utxo", - "note": "FullIncomingNote" - }, - "TransferPost": { - "authorization_signature": "Option", - "asset_id": "Option<[u8; 32]>", - "sources": "Vec<[u8; 16]>", - "sender_posts": "Vec", - "receiver_posts": "Vec", - "sinks": "Vec<[u8; 16]>", - "proof": "[u8; 128]", - "sink_accounts": "Vec<[u8; 32]>" - }, - "DenseInitialSyncResponse": { - "should_continue": "bool", - "utxo_data": "String", - "membership_proof_data": "String", - "nullifier_count": "u128" - } - } -} \ No newline at end of file diff --git a/manta-js/package/src/pallets/MantaPayWallet.ts b/manta-js/package/src/pallets/MantaPayWallet.ts new file mode 100644 index 00000000..e33c609a --- /dev/null +++ b/manta-js/package/src/pallets/MantaPayWallet.ts @@ -0,0 +1,176 @@ +import BN from 'bn.js'; +import type { + Wallet as WasmWallet, + Transaction as WasmTransaction, +} from '../wallet/crate/pkg/manta_wasm_wallet'; +import type { + Address, + IMantaPayWallet, + SignedTransaction, + IBaseWallet, + ILedgerApi, + PalletName, + Network, +} from '.././interfaces'; +import { + mapPostToTransaction, + privateTransferBuildUnsigned, + toPrivateBuildUnsigned, + toPublicBuildUnsigned, + transactionsToBatches, + transferPost, + wrapWasmError, +} from '../utils'; +import PrivateWallet from '../PrivateWallet'; + +const CURRENT_PALLET_NAME: PalletName = 'mantaPay'; + +/// PrivateWallet class +export default class MantaPayWallet + extends PrivateWallet + implements IMantaPayWallet +{ + constructor( + network: Network, + baseWallet: IBaseWallet, + wasmWallet: WasmWallet, + ledgerApi: ILedgerApi, + ) { + super(CURRENT_PALLET_NAME, network, baseWallet, wasmWallet, ledgerApi); + } + + static init(network: Network, baseWallet: IBaseWallet): MantaPayWallet { + const params = PrivateWallet.getInitialParams( + CURRENT_PALLET_NAME, + baseWallet, + ); + return new MantaPayWallet( + network, + baseWallet, + params.wasmWallet, + params.ledgerApi, + ); + } + + /// Builds and signs a "To Private" transaction for any fungible token. + /// Note: This transaction is not published to the ledger. + async toPrivateBuild( + assetId: BN, + amount: BN, + ): Promise { + try { + await this.waitForWallet(); + this.walletIsBusy = true; + const transaction = await toPrivateBuildUnsigned( + this.wasm, + assetId, + amount, + ); + const signResult = await this.signTransaction(null, transaction); + this.walletIsBusy = false; + return signResult; + } catch (ex) { + this.walletIsBusy = false; + console.error('Failed to build toPrivateBuild.', ex); + throw wrapWasmError(ex); + } + } + + /// Builds a "Private Transfer" transaction for any fungible token. + /// Note: This transaction is not published to the ledger. + async privateTransferBuild( + assetId: BN, + amount: BN, + toZkAddress: Address, + ): Promise { + try { + await this.waitForWallet(); + this.walletIsBusy = true; + const transaction = await privateTransferBuildUnsigned( + this.wasm, + this.api, + assetId, + amount, + toZkAddress, + this.network, + ); + const signResult = await this.signTransaction( + transaction.assetMetadataJson, + transaction.transaction, + ); + this.walletIsBusy = false; + return signResult; + } catch (ex) { + this.walletIsBusy = false; + console.error('Failed to build privateTransferBuild.', ex); + throw wrapWasmError(ex); + } + } + + /// Builds and signs a "To Public" transaction for any fungible token. + /// Note: This transaction is not published to the ledger. + async toPublicBuild( + assetId: BN, + amount: BN, + polkadotAddress: Address, + ): Promise { + try { + await this.waitForWallet(); + this.walletIsBusy = true; + const transaction = await toPublicBuildUnsigned( + this.wasm, + this.api, + assetId, + amount, + polkadotAddress, + this.network, + ); + const signResult = await this.signTransaction( + transaction.assetMetadataJson, + transaction.transaction, + ); + this.walletIsBusy = false; + return signResult; + } catch (ex) { + this.walletIsBusy = false; + console.error('Failed to build toPublicBuild.', ex); + throw wrapWasmError(ex); + } + } + + /// Signs the a given transaction returning posts, transactions and batches. + /// assetMetaDataJson is optional, pass in null if transaction should not contain any. + private async signTransaction( + assetMetadataJson: any, + transaction: WasmTransaction, + ): Promise { + let assetMetadata: any = null; + if (assetMetadataJson) { + assetMetadata = this.wasm.AssetMetadata.from_string(assetMetadataJson); + } + this.log('Sign Start'); + const posts = await this.wasmWallet.sign( + transaction, + assetMetadata, + this.getWasmNetWork(), + ); + this.log('Sign End'); + const transactions = []; + for (let i = 0; i < posts.length; i++) { + const convertedPost = transferPost(posts[i]); + const transaction = await mapPostToTransaction( + this.palletName, + this.api, + convertedPost, + ); + transactions.push(transaction); + } + const txs = await transactionsToBatches(this.api, transactions); + return { + posts, + transactionData: null, + transactions, + txs, + }; + } +} diff --git a/manta-js/package/src/pallets/MantaSbtWallet.ts b/manta-js/package/src/pallets/MantaSbtWallet.ts new file mode 100644 index 00000000..e5a8f833 --- /dev/null +++ b/manta-js/package/src/pallets/MantaSbtWallet.ts @@ -0,0 +1,123 @@ +import BN from 'bn.js'; +import type { Wallet as WasmWallet } from '../wallet/crate/pkg/manta_wasm_wallet'; +import type { + IBaseWallet, + ILedgerApi, + PalletName, + Network, + IMantaSbtWallet, + SbtInfo, + Address, + SignedMultiSbtPost, +} from '.././interfaces'; +import { + formatWasmJson, + toPrivateBuildUnsigned, + transferPost, + wrapWasmError, +} from '../utils'; +import PrivateWallet from '../PrivateWallet'; +import { decodeAddress } from '@polkadot/util-crypto'; + +const CURRENT_PALLET_NAME: PalletName = 'mantaSBT'; + +/// PrivateWallet class +export default class MantaSbtWallet + extends PrivateWallet + implements IMantaSbtWallet +{ + constructor( + network: Network, + baseWallet: IBaseWallet, + wasmWallet: WasmWallet, + ledgerApi: ILedgerApi, + ) { + super(CURRENT_PALLET_NAME, network, baseWallet, wasmWallet, ledgerApi); + } + + static init(network: Network, baseWallet: IBaseWallet): MantaSbtWallet { + const params = PrivateWallet.getInitialParams( + CURRENT_PALLET_NAME, + baseWallet, + ); + return new MantaSbtWallet( + network, + baseWallet, + params.wasmWallet, + params.ledgerApi, + ); + } + + async multiSbtPostBuild( + sbtInfoList: SbtInfo[], + ): Promise { + try { + await this.waitForWallet(); + this.walletIsBusy = true; + const defaultAmount = new BN('1'); + const posts = []; + const transactionDatas = []; + for (let i = 0; i < sbtInfoList.length; i += 1) { + const sbtInfo = sbtInfoList[i]; + const transactionUnsigned = await toPrivateBuildUnsigned( + this.wasm, + sbtInfo.assetId, + sbtInfo.amount ?? defaultAmount, + ); + const result = await this.wasmWallet.sign_with_transaction_data( + transactionUnsigned, + null, + this.getWasmNetWork(), + ); + const itemPosts: any = []; + const itemDatas: any = []; + result.forEach((item: any) => { + itemPosts.push(transferPost(item[0])); + itemDatas.push(item[1]); + }); + posts.push(itemPosts); + transactionDatas.push(itemDatas); + } + this.walletIsBusy = false; + return { + transactionDatas: formatWasmJson(transactionDatas), + posts, + }; + } catch (ex) { + this.walletIsBusy = false; + console.error('Failed to build multiSbtPostBuild.', ex); + throw wrapWasmError(ex); + } + } + + async getIdentityProof( + virtualAsset: string, + polkadotAddress: Address, + ): Promise { + try { + await this.waitForWallet(); + this.walletIsBusy = true; + const identityJson = `[[${virtualAsset}, ${`[${decodeAddress( + polkadotAddress, + )}]`}]]`; + const identityRequest = + this.wasm.IdentityRequest.from_string(identityJson); + this.log('IdentityProof Start'); + const identityProof = await this.wasmWallet.identity_proof( + identityRequest, + this.getWasmNetWork(), + ); + this.log('IdentityProof End'); + this.walletIsBusy = false; + return ( + (identityProof && + identityProof.map((item: any) => transferPost(item))) || + null + ); + } catch (ex) { + this.walletIsBusy = false; + console.error('Failed to getIdentityProof', ex); + throw wrapWasmError(ex); + } + } +} diff --git a/manta-js/package/src/pallets/index.ts b/manta-js/package/src/pallets/index.ts new file mode 100644 index 00000000..2a506482 --- /dev/null +++ b/manta-js/package/src/pallets/index.ts @@ -0,0 +1,4 @@ +import MantaPayWallet from './MantaPayWallet'; +import MantaSbtWallet from './MantaSbtWallet'; + +export { MantaPayWallet, MantaSbtWallet }; diff --git a/manta-js/package/src/privateWallet.ts b/manta-js/package/src/privateWallet.ts deleted file mode 100644 index 75abe490..00000000 --- a/manta-js/package/src/privateWallet.ts +++ /dev/null @@ -1,980 +0,0 @@ -import { ApiPromise, WsProvider } from '@polkadot/api'; -import { base58Decode, base58Encode, decodeAddress } from '@polkadot/util-crypto'; -import { bnToU8a } from '@polkadot/util'; -import Api, { ApiConfig } from './api/index'; -import BN from 'bn.js'; -import config from './manta-config.json'; -import type { Transaction as WasmTransaction, Wallet as WasmWallet } from './wallet/crate/pkg/manta_wasm_wallet'; -import * as mantaWasm from './wallet/crate/pkg/manta_wasm_wallet'; -import { Signer, SubmittableExtrinsic } from '@polkadot/api/types'; -import { - Address, - InitApiResult, - InitWasmResult, - IMantaPrivateWallet, - SignedTransaction, - PrivateWalletConfig, - RequestUserSeedPhrase, - SaveStorageStateToLocal, - GetStorageStateFromLocal, -} from './sdk.interfaces'; -import { NATIVE_TOKEN_ASSET_ID } from './utils'; - -const rpc = config.RPC; -const types = config.TYPES; -const DEFAULT_PULL_SIZE = config.DEFAULT_PULL_SIZE; -const PRIVATE_ASSET_PREFIX = 'zk'; - -/// The Envrionment that the sdk is configured to run for, if development -/// is selected then it will attempt to connect to a local node instance. -/// If production is selected it will attempt to connect to actual node. -export enum Environment { - Development = 'DEV', - Production = 'PROD', -} - -/// Supported networks. -export enum Network { - Dolphin = 'Dolphin', - Calamari = 'Calamari', - Manta = 'Manta', -} - -// warning: do not update the array's order -const PayParameterNames = [ - 'address-partition-function.dat', - 'group-generator.dat', - 'incoming-base-encryption-scheme.dat', - 'light-incoming-base-encryption-scheme.dat', - 'nullifier-commitment-scheme.dat', - 'outgoing-base-encryption-scheme.dat', - 'schnorr-hash-function.dat', - 'utxo-accumulator-item-hash.dat', - 'utxo-accumulator-model.dat', - 'utxo-commitment-scheme.dat', - 'viewing-key-derivation-function.dat', -]; - -// warning: do not edit the array's order -const PayProvingNames = [ - 'to-private.lfs', - 'private-transfer.lfs', - 'to-public.lfs', -]; - -/// MantaPrivateWallet class -export class MantaPrivateWallet implements IMantaPrivateWallet { - api: ApiPromise; - wasm: any; - wasmWallet: WasmWallet; - network: Network; - wasmApi: any; - walletIsBusy: boolean; - initialSyncIsFinished: boolean; - loggingEnabled: boolean; - isBindAuthorizationContext: boolean; - parameters: any; - provingContext: any; - requestUserSeedPhrase: RequestUserSeedPhrase; - saveStorageStateToLocal: SaveStorageStateToLocal; - getStorageStateFromLocal: GetStorageStateFromLocal; - - constructor( - api: ApiPromise, - wasm: any, - wasmWallet: WasmWallet, - network: Network, - wasmApi: any, - loggingEnabled: boolean, - parameters: any, - provingContext: any, - requestUserSeedPhrase: RequestUserSeedPhrase, - saveStorageStateToLocal: SaveStorageStateToLocal, - getStorageStateFromLocal: GetStorageStateFromLocal, - ) { - this.api = api; - this.wasm = wasm; - this.wasmWallet = wasmWallet; - this.network = network; - this.wasmApi = wasmApi; - this.walletIsBusy = false; - this.initialSyncIsFinished = false; - this.loggingEnabled = loggingEnabled; - this.isBindAuthorizationContext = false; - this.parameters = parameters; - this.provingContext = provingContext; - this.requestUserSeedPhrase = requestUserSeedPhrase; - this.saveStorageStateToLocal = saveStorageStateToLocal; - this.getStorageStateFromLocal = getStorageStateFromLocal; - } - - /// - /// Public Methods - /// - - /// Initializes the MantaPrivateWallet class, for a corresponding environment and network. - static async init(config: PrivateWalletConfig): Promise { - const { api } = await MantaPrivateWallet.initApi( - config.environment, - config.network, - Boolean(config.loggingEnabled) - ); - const { wasm, wasmWallet, wasmApi, parameters, provingContext } = - await MantaPrivateWallet.initWasmSdk(api, config); - return new MantaPrivateWallet( - api, - wasm, - wasmWallet, - config.network, - wasmApi, - Boolean(config.loggingEnabled), - parameters, - provingContext, - config.requestUserSeedPhrase, - config.saveStorageStateToLocal, - config.getStorageStateFromLocal, - ); - } - - /// Convert a private address to JSON. - convertZkAddressToJson(address: string): any { - const bytes = base58Decode(address); - return JSON.stringify({ - receiving_key: Array.from(bytes), - }); - } - - /// Returns information about the currently supported networks. - getNetworks(): any { - return config.NETWORKS; - } - - /// Returns the ZkAddress (Zk Address) of the currently connected manta-signer instance. - async getZkAddress(): Promise
{ - try { - await this.waitForWallet(); - - this.walletIsBusy = true; - const zkAddressRaw = await this.wasmWallet.address( - this.getWasmNetWork() - ); - const zkAddressBytes = [...zkAddressRaw.receiving_key]; - const zkAddress = base58Encode(zkAddressBytes); - this.walletIsBusy = false; - return zkAddress; - } catch (e) { - this.walletIsBusy = false; - console.error('Failed to fetch ZkAddress.', e); - } - } - - /// This method is optimized based on initialWalletSync - /// - /// Requirements: Must be called once after creating an instance of MantaPrivateWallet - /// and must be called before walletSync(). - /// If it is a new wallet (the current solution is that the native token is 0), - /// you can call this method to save initialization time - async initialNewAccountWalletSync(): Promise { - if (!this.isBindAuthorizationContext) { - await this.loadUserSeedPhrase(); - } - try { - await this.waitForWallet(); - this.walletIsBusy = true; - this.log('Start initial new account'); - await this.wasmWallet.initial_sync(this.getWasmNetWork()); - this.log('Initial new account completed'); - await this.saveStateToLocal(); - this.walletIsBusy = false; - this.initialSyncIsFinished = true; - } catch (ex) { - this.walletIsBusy = false; - throw ex; - } - return true; - } - - /// Performs full wallet recovery. Restarts `self` with an empty state and - /// performs a synchronization against the signer and ledger to catch up to - /// the current checkpoint and balance state. - /// - /// Requirements: Must be called once after creating an instance of MantaPrivateWallet - /// and must be called before walletSync(). - /// If it is a new wallet (the current solution is that the native token is 0), - /// you can call initialNewAccountWalletSync to save initialization time - async initialWalletSync(): Promise { - const result = await this.loopSyncPartialWallet(true); - return result; - } - - /// Pulls data from the ledger, synchronizing the currently connected wallet and - /// balance state. This method runs until all the ledger data has arrived at and - /// has been synchronized with the wallet. - async walletSync(): Promise { - if (!this.initialSyncIsFinished) { - throw new Error('Must call initialWalletSync before walletSync!'); - } - const result = await this.loopSyncPartialWallet(false); - return result; - } - - /// Returns the zk balance of the currently connected zkAddress for the currently - /// connected network. - async getZkBalance(assetId: BN): Promise { - try { - await this.waitForWallet(); - this.walletIsBusy = true; - const balanceString = await this.wasmWallet.balance( - assetId.toString(), - this.getWasmNetWork(), - ); - const balance = new BN(balanceString); - this.walletIsBusy = false; - return balance; - } catch (e) { - this.walletIsBusy = false; - console.error('Failed to fetch zk balance.', e); - return null; - } - } - - /// Returns the multi zk balance of the currently connected zkAddress for the currently - /// connected network. - async getMultiZkBalance(assetIds: BN[]): Promise { - try { - await this.waitForWallet(); - this.walletIsBusy = true; - const balances = await Promise.all(assetIds.map(async (assetId) => { - const balanceString = await this.wasmWallet.balance( - assetId.toString(), - this.getWasmNetWork(), - ); - return new BN(balanceString); - })); - this.walletIsBusy = false; - return balances; - } catch (e) { - this.walletIsBusy = false; - console.error('Failed to fetch zk balance.', e); - return null; - } - } - - /// Returns the metadata for an asset with a given `assetId` for the currently - /// connected network. - async getAssetMetadata(assetId: BN): Promise { - const data: any = await this.api.query.assetManager.assetIdMetadata( - assetId - ); - const json = JSON.stringify(data.toHuman()); - const jsonObj = JSON.parse(json); - // Dolphin is equivalent to Calamari on-chain, and only appears differently at UI level - // so it is necessary to set its symbol and name manually - if ( - this.network === Network.Dolphin && - assetId.toString() === NATIVE_TOKEN_ASSET_ID - ) { - jsonObj.metadata.symbol = 'DOL'; - jsonObj.metadata.name = 'Dolphin'; - } - return jsonObj; - } - - /// Executes a "To Private" transaction for any fungible token. - async toPrivateSend( - assetId: BN, - amount: BN, - polkadotSigner: Signer, - polkadotAddress: Address - ): Promise { - const signed = await this.toPrivateBuild( - assetId, - amount, - polkadotAddress - ); - // transaction rejected by signer - if (signed === null) { - return; - } - await this.setPolkadotSigner(polkadotSigner); - await this.sendTransaction(polkadotAddress, signed); - this.log('To Private transaction finished.'); - } - - /// Builds and signs a "To Private" transaction for any fungible token. - /// Note: This transaction is not published to the ledger. - async toPrivateBuild( - assetId: BN, - amount: BN, - polkadotAddress: Address - ): Promise { - try { - await this.waitForWallet(); - this.walletIsBusy = true; - await this.setWasmExternalAccountSigner(polkadotAddress); - const transaction = await this.toPrivateBuildUnsigned(assetId, amount); - const signResult = await this.signTransaction( - null, - transaction, - this.network - ); - this.walletIsBusy = false; - return signResult; - } catch (e) { - this.walletIsBusy = false; - console.error('Failed to build transaction.', e); - return null; - } - } - - /// Executes a "Private Transfer" transaction for any fungible token. - async privateTransferSend( - assetId: BN, - amount: BN, - toZkAddress: Address, - polkadotSigner: Signer, - polkadotAddress: Address - ): Promise { - const signed = await this.privateTransferBuild( - assetId, - amount, - toZkAddress, - polkadotAddress - ); - // transaction rejected by signer - if (signed === null) { - return; - } - await this.setPolkadotSigner(polkadotSigner); - await this.sendTransaction(polkadotAddress, signed); - this.log('Private Transfer transaction finished.'); - } - - /// Builds a "Private Transfer" transaction for any fungible token. - /// Note: This transaction is not published to the ledger. - async privateTransferBuild( - assetId: BN, - amount: BN, - toZkAddress: Address, - polkadotAddress: Address - ): Promise { - try { - await this.waitForWallet(); - this.walletIsBusy = true; - await this.setWasmExternalAccountSigner(polkadotAddress); - const transaction = await this.privateTransferBuildUnsigned( - assetId, - amount, - toZkAddress - ); - const signResult = await this.signTransaction( - transaction.assetMetadataJson, - transaction.transaction, - this.network - ); - this.walletIsBusy = false; - return signResult; - } catch (e) { - this.walletIsBusy = false; - console.error('Failed to build transaction.', e); - return null; - } - } - - /// Executes a "To Public" transaction for any fungible token. - async toPublicSend( - assetId: BN, - amount: BN, - polkadotSigner: Signer, - polkadotAddress: Address - ): Promise { - const signed = await this.toPublicBuild( - assetId, - amount, - polkadotAddress - ); - // transaction rejected by signer - if (signed === null) { - return; - } - await this.setPolkadotSigner(polkadotSigner); - await this.sendTransaction(polkadotAddress, signed); - this.log('To Public transaction finished.'); - } - - /// Builds and signs a "To Public" transaction for any fungible token. - /// Note: This transaction is not published to the ledger. - async toPublicBuild( - assetId: BN, - amount: BN, - polkadotAddress: Address - ): Promise { - try { - await this.waitForWallet(); - this.walletIsBusy = true; - await this.setWasmExternalAccountSigner(polkadotAddress); - const transaction = await this.toPublicBuildUnsigned(assetId, amount, polkadotAddress); - const signResult = await this.signTransaction( - transaction.assetMetadataJson, - transaction.transaction, - this.network - ); - this.walletIsBusy = false; - return signResult; - } catch (e) { - this.walletIsBusy = false; - console.error('Failed to build transaction.', e); - return null; - } - } - - /// reset instance state - async resetState() { - await this.wasmWallet.reset_state(this.getWasmNetWork()); - const wasmSigner = new this.wasm.Signer(this.parameters, this.provingContext, null); - const wasmLedger = new this.wasm.PolkadotJsLedger(this.wasmApi); - this.wasmWallet.set_network( - wasmLedger, - wasmSigner, - this.getWasmNetWork(), - ); - this.dropUserSeedPhrase(); - this.dropAuthorizationContext(); - return true; - } - - /// - /// Private Methods - /// - - /// Conditionally logs the contents of `message` depending on if `loggingEnabled` - /// is set to `true`. - private log(message: string): void { - if (this.loggingEnabled) { - console.log('[INFO]: ' + message); - console.log(performance.now()); - } - } - - // WASM wallet doesn't allow you to call two methods at once, so before - // calling methods it is necessary to wait for a pending call to finish. - private async waitForWallet(): Promise { - while (this.walletIsBusy === true) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - } - - /// Private helper method for internal use to initialize the Polkadot.js API with web3Extension. - private static async initApi( - env: Environment, - network: Network, - loggingEnabled: boolean - ): Promise { - const provider = new WsProvider(MantaPrivateWallet.envUrl(env, network)); - const api = await ApiPromise.create({ provider, types, rpc }); - const [chain, nodeName, nodeVersion] = await Promise.all([ - api.rpc.system.chain(), - api.rpc.system.name(), - api.rpc.system.version(), - ]); - - if (loggingEnabled) { - console.log( - `[INFO]: MantaPrivateWallet is connected to chain ${chain} using ${nodeName} v${nodeVersion}` - ); - } - - return { - api, - }; - } - - /// Private helper method for internal use to initialize the initialize manta-wasm-wallet. - private static async initWasmSdk( - api: ApiPromise, - priConfig: PrivateWalletConfig - ): Promise { - - const wasm = mantaWasm; // await import('./wallet/crate/pkg/manta_wasm_wallet'); - wasm.init_panic_hook(); - - if (priConfig.loggingEnabled) { - console.log(`Start download files: ${performance.now()}`); - } - const provingFileList = await MantaPrivateWallet.fetchFiles(priConfig.provingFilePath, PayProvingNames); - const parameterFileList = await MantaPrivateWallet.fetchFiles(priConfig.parametersFilePath, PayParameterNames); - if (priConfig.loggingEnabled) { - console.log(`Download file successful: ${performance.now()}`); - } - - const multiProvingContext = new wasm.RawMultiProvingContext(...(provingFileList as [Uint8Array, Uint8Array, Uint8Array])); - const fullParameters = new wasm.RawFullParameters(...(parameterFileList as [Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array])); - const storageData = await priConfig.getStorageStateFromLocal (`${priConfig.network}`); - if (priConfig.loggingEnabled) { - console.log(`Start initial signer: ${performance.now()}`); - } - const wasmSigner = new wasm.Signer(fullParameters, multiProvingContext, storageData); - if (priConfig.loggingEnabled) { - console.log(`Initial signer successful: ${performance.now()}`); - } - - const wasmWallet = new wasm.Wallet(); - const wasmApiConfig = new ApiConfig( - priConfig.maxReceiversPullSize ?? DEFAULT_PULL_SIZE, - priConfig.maxSendersPullSize ?? DEFAULT_PULL_SIZE, - priConfig.pullCallback, - priConfig.errorCallback, - Boolean(priConfig.loggingEnabled) - ); - - const wasmApi = new Api(api, wasmApiConfig); - const wasmLedger = new wasm.PolkadotJsLedger(wasmApi); - wasmWallet.set_network( - wasmLedger, - wasmSigner, - wasm.Network.from_string(`"${priConfig.network}"`) - ); - return { - wasm, - wasmWallet, - wasmApi, - parameters: fullParameters, - provingContext: multiProvingContext, - }; - } - - private async loopSyncPartialWallet(isInitial: boolean): Promise { - if (!this.isBindAuthorizationContext) { - await this.loadUserSeedPhrase(); - } - try { - await this.waitForWallet(); - this.walletIsBusy = true; - if (isInitial) { - await this.wasmWallet.reset_state(this.getWasmNetWork()); - } - let syncResult = null; - let retryTimes = 0; - do { - this.log('Sync partial start'); - syncResult = await this.syncPartialWallet(); - this.log(`Sync partial end, success: ${syncResult.success}, continue: ${syncResult.continue}`, ); - if (!syncResult.success) { - retryTimes += 1; - } else { - retryTimes = 0; - } - if (retryTimes > 5) { - throw new Error('Sync partial failed'); - } - } while (syncResult && syncResult.continue); - this.walletIsBusy = false; - if (isInitial) { - this.initialSyncIsFinished = true; - } - } catch (ex) { - this.walletIsBusy = false; - throw ex; - } - return true; - } - - private async syncPartialWallet(): Promise<{success: boolean, continue: boolean}> { - try { - const result = await this.wasmWallet.sync_partial(this.getWasmNetWork()); - await this.saveStateToLocal(); - return { - success: true, - continue: Object.keys(result)[0] === 'Continue', - }; - } catch (e) { - console.error('Sync partial failed.', e); - return { - success: false, - continue: true, - }; - } - } - - private async saveStateToLocal() { - const stateData = await this.wasmWallet.get_storage(this.getWasmNetWork()); - await this.saveStorageStateToLocal(`${this.network}`, stateData); - } - - private getWasmNetWork(): any { - return this.wasm.Network.from_string(`"${this.network}"`); - } - - /// Set polkadot signing address to `polkadotAddress`. - private async setPolkadotSigner( - polkadotSigner: Signer, - ): Promise { - await this.api.setSigner(polkadotSigner); - } - - /// Set the polkadot Signer to `polkadotSigner` - private async setWasmExternalAccountSigner(polkadotAddress: Address): Promise { - await this.wasmApi.setExternalAccountSigner(polkadotAddress); - } - - /// Returns the corresponding blockchain connection URL from Environment - /// and Network values. - private static envUrl(env: Environment, network: Network): string { - let url = config.NETWORKS[network].ws_local; - if (env == Environment.Production) { - url = config.NETWORKS[network].ws; - } - return url; - } - - /// Builds the "ToPrivate" transaction in JSON format to be signed. - private async toPrivateBuildUnsigned(assetId: BN, amount: BN): Promise { - try { - const assetIdArray = bnToU8a(assetId, {bitLength: 256}); - const txJson = `{ "ToPrivate": { "id": [${assetIdArray}], "value": ${amount.toString()} }}`; - const transaction = this.wasm.Transaction.from_string(txJson); - return transaction; - } catch (error) { - console.error('Unable to build "To Private" Transaction.', error); - } - } - - /// private transfer transaction - private async privateTransferBuildUnsigned( - assetId: BN, - amount: BN, - toZkAddress: Address - ): Promise { - try { - const addressJson = this.convertZkAddressToJson(toZkAddress); - const assetIdArray = bnToU8a(assetId, {bitLength: 256}); - const txJson = `{ "PrivateTransfer": [{ "id": [${assetIdArray}], "value": ${amount.toString()} }, ${addressJson} ]}`; - const transaction = this.wasm.Transaction.from_string(txJson); - const jsonObj = await this.getAssetMetadata(assetId); - const decimals = jsonObj['metadata']['decimals']; - const symbol = jsonObj['metadata']['symbol']; - const assetMetadataJson = `{ "token_type": {"FT": ${decimals}}, "symbol": "${PRIVATE_ASSET_PREFIX}${symbol}" }`; - return { - transaction, - assetMetadataJson, - }; - } catch (e) { - console.error('Unable to build "Private Transfer" Transaction.', e); - } - } - - /// Builds the "ToPublic" transaction in JSON format to be signed. - private async toPublicBuildUnsigned(assetId: BN, amount: BN, publicAddress: string): Promise { - try { - const assetIdArray = bnToU8a(assetId, {bitLength: 256}); - const publicAddressArray = `[${decodeAddress(publicAddress)}]`; - const txJson = `{ "ToPublic": [{ "id": [${assetIdArray}], "value": ${amount.toString()} }, ${publicAddressArray} ]}`; - - const transaction = this.wasm.Transaction.from_string(txJson); - const jsonObj = await this.getAssetMetadata(assetId); - const decimals = jsonObj['metadata']['decimals']; - const symbol = jsonObj['metadata']['symbol']; - const assetMetadataJson = `{ "token_type": {"FT": ${decimals}}, "symbol": "${PRIVATE_ASSET_PREFIX}${symbol}" }`; - return { - transaction, - assetMetadataJson, - }; - } catch (error) { - console.error('Unable to build "To Public" Transaction.', error); - } - } - - public dropAuthorizationContext() { - this.wasmWallet.drop_authorization_context(this.getWasmNetWork()); - } - - public dropUserSeedPhrase() { - this.wasmWallet.drop_accounts(this.getWasmNetWork()); - } - - public async loadUserSeedPhrase(initialSeedPhrase?: string) { - const seedPhrase = await this.getUserSeedPhrase(initialSeedPhrase); - const accountTable = await this.wasm.accounts_from_mnemonic(seedPhrase); - await this.wasmWallet.load_accounts(accountTable, this.getWasmNetWork()); - await this.wasmWallet.update_authorization_context(this.getWasmNetWork()); - this.isBindAuthorizationContext = true; - } - - public async loadAuthorizationContext(initialSeedPhrase?: string) { - const autoUpdateAuthContext = await this.wasmWallet.update_authorization_context(this.getWasmNetWork()); - if (autoUpdateAuthContext) { - return; - } - const seedPhrase = await this.getUserSeedPhrase(initialSeedPhrase); - const authorizationContext = await this.wasm.authorization_context_from_mnemonic( - seedPhrase, - this.parameters, - ); - await this.wasmWallet.load_authorization_context( - authorizationContext, - this.getWasmNetWork() - ); - this.isBindAuthorizationContext = true; - } - - private async getUserSeedPhrase(initialSeedPhrase?: string): Promise { - const seedPhrase = initialSeedPhrase || (await this.requestUserSeedPhrase()); - if (!seedPhrase) { - throw new Error('User Rejected'); - } - return this.wasm.mnemonic_from_phrase(seedPhrase); - } - - private async wrapperSpendingKeyOperation(func: () => any): Promise { - await this.loadUserSeedPhrase(); - let result: any = undefined; - this.log('Sign start'); - result = await func(); - this.log('Sign end'); - // this.dropUserSeedPhrase(); - return result; - } - - /// Signs the a given transaction returning posts, transactions and batches. - /// assetMetaDataJson is optional, pass in null if transaction should not contain any. - private async signTransaction( - assetMetadataJson: any, - transaction: WasmTransaction, - network: Network - ): Promise { - try { - let assetMetadata: any = null; - if (assetMetadataJson) { - assetMetadata = this.wasm.AssetMetadata.from_string(assetMetadataJson); - } - const networkType = this.wasm.Network.from_string(`"${network}"`); - const posts = await this.wrapperSpendingKeyOperation(async () => { - return await this.wasmWallet.sign( - transaction, - assetMetadata, - networkType - ); - }); - const transactions = []; - for (let i = 0; i < posts.length; i++) { - const convertedPost = this.transferPost(posts[i]); - const transaction = await this.mapPostToTransaction( - convertedPost, - this.api - ); - transactions.push(transaction); - } - const txs = await this.transactionsToBatches(transactions, this.api); - return { - posts, - transactions, - txs, - }; - } catch (e) { - console.error('Unable to sign transaction.', e); - return null; - } - } - - /// This method sends a transaction to the public ledger after it has been signed - /// by Manta Signer. - private async sendTransaction( - signer: string, - signedTransaction: SignedTransaction - ): Promise { - for (let i = 0; i < signedTransaction.txs.length; i++) { - try { - await signedTransaction.txs[i].signAndSend( - signer, - (_status: any, _events: any) => {} - ); - } catch (error) { - console.error('Transaction failed', error); - } - } - } - - /// Maps a given `post` to a known transaction type, either Mint, Private Transfer or Reclaim. - private async mapPostToTransaction( - post: any, - api: ApiPromise - ): Promise> { - const sources = post.sources.length; - const senders = post.sender_posts.length; - const receivers = post.receiver_posts.length; - const sinks = post.sinks.length; - - if (sources == 1 && senders == 0 && receivers == 1 && sinks == 0) { - const mintTx = await api.tx.mantaPay.toPrivate(post); - return mintTx; - } else if (sources == 0 && senders == 2 && receivers == 2 && sinks == 0) { - const privateTransferTx = await api.tx.mantaPay.privateTransfer(post); - return privateTransferTx; - } else if (sources == 0 && senders == 2 && receivers == 1 && sinks == 1) { - const reclaimTx = await api.tx.mantaPay.toPublic(post); - return reclaimTx; - } else { - throw new Error( - 'Invalid transaction shape; there is no extrinsic for a transaction' + - `with ${sources} sources, ${senders} senders, ` + - ` ${receivers} receivers and ${sinks} sinks` - ); - } - } - - /// Batches transactions. - private async transactionsToBatches( - transactions: any, - api: ApiPromise - ): Promise[]> { - const MAX_BATCH = 2; - const batches = []; - for (let i = 0; i < transactions.length; i += MAX_BATCH) { - const transactionsInSameBatch = transactions.slice(i, i + MAX_BATCH); - const batchTransaction = await api.tx.utility.batch( - transactionsInSameBatch - ); - batches.push(batchTransaction); - } - return batches; - } - - // convert receiver_posts to match runtime side - private convertReceiverPost(x: any) { - const arr1 = x.note.incoming_note.ciphertext.ciphertext.message.flatMap( - function (item: any, index: any, a: any) { - return item; - } - ); - const tag = x.note.incoming_note.ciphertext.ciphertext.tag; - const pk = x.note.incoming_note.ciphertext.ephemeral_public_key; - x.note.incoming_note.tag = tag; - x.note.incoming_note.ephemeral_public_key = pk; - x.note.incoming_note.ciphertext = arr1; - delete x.note.incoming_note.header; - - const lightPk = x.note.light_incoming_note.ciphertext.ephemeral_public_key; - // ciphertext is [u8; 96] on manta-rs, but runtime side is [[u8; 32]; 3] - const lightCipher = x.note.light_incoming_note.ciphertext.ciphertext; - const lightCiper0 = lightCipher.slice(0, 32); - const lightCiper1 = lightCipher.slice(32, 64); - const lightCiper2 = lightCipher.slice(64, 96); - x.note.light_incoming_note.ephemeral_public_key = lightPk; - x.note.light_incoming_note.ciphertext = [ - lightCiper0, - lightCiper1, - lightCiper2, - ]; - delete x.note.light_incoming_note.header; - - // convert asset value to [u8; 16] - x.utxo.public_asset.value = new BN(x.utxo.public_asset.value).toArray( - 'le', - 16 - ); - - x.full_incoming_note = x.note; - delete x.note; - } - - // convert sender_posts to match runtime side - private convertSenderPost(x: any) { - const pk = x.nullifier.outgoing_note.ciphertext.ephemeral_public_key; - const cipher = x.nullifier.outgoing_note.ciphertext.ciphertext; - const ciper0 = cipher.slice(0, 32); - const ciper1 = cipher.slice(32, 64); - const outgoing = { - ephemeral_public_key: pk, - ciphertext: [ciper0, ciper1], - }; - x.outgoing_note = outgoing; - const nullifier = x.nullifier.nullifier.commitment; - x.nullifier_commitment = nullifier; - delete x.nullifier; - } - - // replace all Uint8Array type to Array - // replace all bigint type to string - private formatPostData(post: any): any { - const walk = function(data: any, keys: string[]) { - for (const key of keys) { - if (data[key] instanceof Uint8Array) { - data[key] = Array.from(data[key]); - } else if (typeof data[key] === 'bigint') { - data[key] = (data[key]).toString(); - } else if (data[key]) { - const tKeys = Object.keys(data[key]); - if (tKeys.length > 0) { - walk(data[key], tKeys); - } - } - } - }; - walk(post, Object.keys(post)); - return post; - } - - /// NOTE: `post` from manta-rs sign result should match runtime side data structure type. - private transferPost(post: any): any { - const json = JSON.parse(JSON.stringify(this.formatPostData(post))); - - // transfer authorization_signature format - if (json.authorization_signature != null) { - const scala = json.authorization_signature.signature.scalar; - const nonce = json.authorization_signature.signature.nonce_point; - json.authorization_signature.signature = [scala, nonce]; - } - - // transfer receiver_posts to match runtime side - json.receiver_posts.map((x: any) => { - this.convertReceiverPost(x); - }); - - // transfer sender_posts to match runtime side - json.sender_posts.map((x: any) => { - this.convertSenderPost(x); - }); - - return json; - } - - private static async fetchFiles( - filePrefix: string, - fileNames: string[] - ): Promise { - const fileList = await Promise.all( - fileNames.map((name) => - MantaPrivateWallet.fetchFile(`${filePrefix}/${name}`) - ) - ); - return fileList; - } - - private static async fetchFile( - url: string, - ): Promise { - try { - const responseData = await fetch(url); - const result = await responseData.blob(); - const reader = new FileReader(); - return new Promise((resolve) => { - try { - reader.addEventListener('load', () => { - resolve(new Uint8Array(reader.result as ArrayBuffer)); - }); - reader.addEventListener('error', () => { - resolve(null); - }); - // Read the contents of the specified Blob or File - reader.readAsArrayBuffer(result); - } catch (ex) { - resolve(null); - } - }); - // return result; - } catch (ex) { - console.error(`fetch ${url}, failed`, ex); - } - return null; - } -} diff --git a/manta-js/package/src/sdk.d.ts b/manta-js/package/src/sdk.d.ts deleted file mode 100644 index dc9663e9..00000000 --- a/manta-js/package/src/sdk.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'manta-wasm-wallet-api'; \ No newline at end of file diff --git a/manta-js/package/src/sdk.interfaces.ts b/manta-js/package/src/sdk.interfaces.ts deleted file mode 100644 index b85115eb..00000000 --- a/manta-js/package/src/sdk.interfaces.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { ApiPromise } from '@polkadot/api'; -import { Wallet } from './wallet/crate/pkg'; -import { Environment, Network } from './privateWallet'; -import BN from 'bn.js'; -import { SubmittableExtrinsic, Signer } from '@polkadot/api/types'; - -export type Address = string; - -// Must be a uint8Array of length 32. -export type AssetId = Uint8Array; - -export type InitApiResult = { - api: ApiPromise -} - -export type InitWasmResult = { - wasm: any, - wasmWallet: Wallet, - wasmApi: any, - parameters: any; - provingContext: any; -} - -export type SignedTransaction = { - posts: any, - transactions: SubmittableExtrinsic<'promise', any>[], - txs: SubmittableExtrinsic<'promise', any>[] -} - -export type RequestUserSeedPhrase = () => Promise; -export type SaveStorageStateToLocal = (network: string, data: any) => Promise; -export type GetStorageStateFromLocal = (network: string) => Promise; - -export type PrivateWalletConfig = { - environment: Environment, - network: Network, - loggingEnabled?: boolean, - maxReceiversPullSize?: number, - maxSendersPullSize?: number, - pullCallback?: any, - errorCallback?: any, - provingFilePath: string, - parametersFilePath: string, - requestUserSeedPhrase: RequestUserSeedPhrase, - saveStorageStateToLocal: SaveStorageStateToLocal, - getStorageStateFromLocal: GetStorageStateFromLocal, -} - -export interface IMantaPrivateWallet { - api: ApiPromise; - wasm: any; - wasmWallet: Wallet; - network: Network; - wasmApi: any; - walletIsBusy: boolean; - initialSyncIsFinished: boolean; - loggingEnabled: boolean; - parameters: any; - - loadUserSeedPhrase(initialSeedPhrase?: string):any; - loadAuthorizationContext(initialSeedPhrase?: string):any; - dropAuthorizationContext():any; - dropUserSeedPhrase():any; - convertZkAddressToJson(address: string): any - getNetworks(): any; - getZkAddress(): Promise
; - getAssetMetadata(assetId: BN): Promise; - initialWalletSync(): Promise; - walletSync(): Promise; - getZkBalance(assetId: BN): Promise; - getMultiZkBalance(assetId: BN[]): Promise; - toPrivateSend(assetId: BN, amount: BN, polkadotSigner:Signer, polkadotAddress:Address): Promise; - toPrivateBuild(assetId: BN, amount: BN, polkadotAddress:Address): Promise; - privateTransferSend(assetId: BN, amount: BN, toZkAddress: Address, polkadotSigner:Signer, polkadotAddress:Address): Promise; - privateTransferBuild(assetId: BN, amount: BN, toZkAddress: Address, polkadotAddress:Address): Promise; - toPublicSend(assetId: BN, amount: BN, polkadotSigner:Signer, polkadotAddress:Address): Promise; - toPublicBuild(assetId: BN, amount: BN, polkadotAddress:Address): Promise; - resetState(): Promise; -} diff --git a/manta-js/package/src/utils.ts b/manta-js/package/src/utils.ts index de8389fc..3101c855 100644 --- a/manta-js/package/src/utils.ts +++ b/manta-js/package/src/utils.ts @@ -1,49 +1,279 @@ -import { Address } from './sdk.interfaces'; -import { Signer } from '@polkadot/api/types'; import BN from 'bn.js'; import { ApiPromise } from '@polkadot/api'; +import type { SubmittableExtrinsic } from '@polkadot/api/types'; +import { base58Decode, decodeAddress } from '@polkadot/util-crypto'; import { bnToU8a } from '@polkadot/util'; +import type { Address, PalletName, Network } from './interfaces'; +import { NATIVE_TOKEN_ASSET_ID, PRIVATE_ASSET_PREFIX } from './constants'; -export const NATIVE_TOKEN_ASSET_ID = '1'; - -/// MantaUtilities class -export class MantaUtilities { - - /// Returns the public balance associated with an account for a given AssetId. - static async getPublicBalance(api:ApiPromise, assetId: BN, address:Address): Promise { - try { - if (assetId.toString() === NATIVE_TOKEN_ASSET_ID) { - const nativeBalance: any = await api.query.system.account(address); - return new BN(nativeBalance.data.free.toString()); - } else { - const assetBalance: any = await api.query.assets.account(assetId, address); - if (assetBalance.value.isEmpty) { - return new BN(0); - } else { - return new BN(assetBalance.value.balance.toString()); - } - } - } catch (e) { - console.log('Failed to fetch public balance.'); - console.error(e); - return null; - } - } +/// Convert a private address to JSON. +export function convertZkAddressToJson(address: string) { + const bytes = base58Decode(address); + return JSON.stringify({ + receiving_key: Array.from(bytes), + }); +} + +export async function mapPostToTransaction( + palletName: PalletName, + api: ApiPromise, + post: any, +): Promise> { + if (palletName === 'mantaPay') { + const sources = post.sources.length; + const senders = post.sender_posts.length; + const receivers = post.receiver_posts.length; + const sinks = post.sinks.length; - /// Executes a public transfer. - static async publicTransfer(api:ApiPromise, assetId: BN, amount: BN, destinationAddress: Address, senderAddress:Address, polkadotSigner:Signer): Promise { - api.setSigner(polkadotSigner); - try { - const assetIdArray = bnToU8a(assetId, {bitLength: 256}); - const amountBN = amount.toArray('le', 16); - const tx = await api.tx.mantaPay.publicTransfer( - { id: assetIdArray, value: amountBN }, - destinationAddress + if (sources == 1 && senders == 0 && receivers == 1 && sinks == 0) { + const mintTx = await api.tx.mantaPay.toPrivate(post); + return mintTx; + } else if (sources == 0 && senders == 2 && receivers == 2 && sinks == 0) { + const privateTransferTx = await api.tx.mantaPay.privateTransfer(post); + return privateTransferTx; + } else if (sources == 0 && senders == 2 && receivers == 1 && sinks == 1) { + const reclaimTx = await api.tx.mantaPay.toPublic(post); + return reclaimTx; + } else { + throw new Error( + 'Invalid transaction shape; there is no extrinsic for a transaction' + + `with ${sources} sources, ${senders} senders, ` + + ` ${receivers} receivers and ${sinks} sinks`, ); - await tx.signAndSend(senderAddress); - } catch (e) { - console.log('Failed to execute public transfer.'); - console.error(e); } } } + +/// Returns the metadata for an asset with a given `assetId` for the currently +/// connected network. +export async function getAssetMetadata( + api: ApiPromise, + assetId: BN, + network: Network, +): Promise { + await api.isReady; + const data: any = await api.query.assetManager.assetIdMetadata(assetId); + const json = JSON.stringify(data.toHuman()); + const jsonObj = JSON.parse(json); + // Dolphin is equivalent to Calamari on-chain, and only appears differently at UI level + // so it is necessary to set its symbol and name manually + if (network === 'Dolphin' && assetId.toString() === NATIVE_TOKEN_ASSET_ID) { + jsonObj.metadata.symbol = 'DOL'; + jsonObj.metadata.name = 'Dolphin'; + } + return jsonObj; +} + +/// Builds the "ToPrivate" transaction in JSON format to be signed. +export function toPrivateBuildUnsigned( + wasm: any, + assetId: BN, + amount: BN, +): Promise { + const assetIdArray = bnToU8a(assetId, { bitLength: 256 }); + const txJson = `{ "ToPrivate": { "id": [${assetIdArray}], "value": ${amount.toString()} }}`; + const transaction = wasm.Transaction.from_string(txJson); + return transaction; +} + +/// private transfer transaction +export async function privateTransferBuildUnsigned( + wasm: any, + api: ApiPromise, + assetId: BN, + amount: BN, + toZkAddress: Address, + network: Network, +): Promise { + const addressJson = convertZkAddressToJson(toZkAddress); + const assetIdArray = bnToU8a(assetId, { bitLength: 256 }); + const txJson = `{ "PrivateTransfer": [{ "id": [${assetIdArray}], "value": ${amount.toString()} }, ${addressJson} ]}`; + const transaction = wasm.Transaction.from_string(txJson); + const jsonObj = await getAssetMetadata(api, assetId, network); + const decimals = jsonObj['metadata']['decimals']; + const symbol = jsonObj['metadata']['symbol']; + const assetMetadataJson = `{ "token_type": {"FT": ${decimals}}, "symbol": "${PRIVATE_ASSET_PREFIX}${symbol}" }`; + return { + transaction, + assetMetadataJson, + }; +} + +/// Builds the "ToPublic" transaction in JSON format to be signed. +export async function toPublicBuildUnsigned( + wasm: any, + api: ApiPromise, + assetId: BN, + amount: BN, + publicAddress: string, + network: Network, +): Promise { + const assetIdArray = bnToU8a(assetId, { bitLength: 256 }); + const publicAddressArray = `[${decodeAddress(publicAddress)}]`; + const txJson = `{ "ToPublic": [{ "id": [${assetIdArray}], "value": ${amount.toString()} }, ${publicAddressArray} ]}`; + + const transaction = wasm.Transaction.from_string(txJson); + const jsonObj = await getAssetMetadata(api, assetId, network); + const decimals = jsonObj['metadata']['decimals']; + const symbol = jsonObj['metadata']['symbol']; + const assetMetadataJson = `{ "token_type": {"FT": ${decimals}}, "symbol": "${PRIVATE_ASSET_PREFIX}${symbol}" }`; + return { + transaction, + assetMetadataJson, + }; +} + +/// Batches transactions. +export async function transactionsToBatches( + api: ApiPromise, + transactions: any, +): Promise[]> { + const MAX_BATCH = 2; + const batches = []; + for (let i = 0; i < transactions.length; i += MAX_BATCH) { + const transactionsInSameBatch = transactions.slice(i, i + MAX_BATCH); + const batchTransaction = await api.tx.utility.batch( + transactionsInSameBatch, + ); + batches.push(batchTransaction); + } + return batches; +} + +// convert receiver_posts to match runtime side +export function convertReceiverPost(x: any) { + const arr1 = x.note.incoming_note.ciphertext.ciphertext.message.flatMap( + (item: any) => item, + ); + const tag = x.note.incoming_note.ciphertext.ciphertext.tag; + const pk = x.note.incoming_note.ciphertext.ephemeral_public_key; + x.note.incoming_note.tag = tag; + x.note.incoming_note.ephemeral_public_key = pk; + x.note.incoming_note.ciphertext = arr1; + delete x.note.incoming_note.header; + + const lightPk = x.note.light_incoming_note.ciphertext.ephemeral_public_key; + // ciphertext is [u8; 96] on manta-rs, but runtime side is [[u8; 32]; 3] + const lightCipher = x.note.light_incoming_note.ciphertext.ciphertext; + const lightCipher0 = lightCipher.slice(0, 32); + const lightCipher1 = lightCipher.slice(32, 64); + const lightCipher2 = lightCipher.slice(64, 96); + x.note.light_incoming_note.ephemeral_public_key = lightPk; + x.note.light_incoming_note.ciphertext = [ + lightCipher0, + lightCipher1, + lightCipher2, + ]; + delete x.note.light_incoming_note.header; + + // convert asset value to [u8; 16] + x.utxo.public_asset.value = new BN(x.utxo.public_asset.value).toArray( + 'le', + 16, + ); + + x.full_incoming_note = x.note; + delete x.note; +} + +// convert sender_posts to match runtime side +export function convertSenderPost(x: any) { + const pk = x.nullifier.outgoing_note.ciphertext.ephemeral_public_key; + const cipher = x.nullifier.outgoing_note.ciphertext.ciphertext; + const cipher0 = cipher.slice(0, 32); + const cipher1 = cipher.slice(32, 64); + const outgoing = { + ephemeral_public_key: pk, + ciphertext: [cipher0, cipher1], + }; + x.outgoing_note = outgoing; + const nullifier = x.nullifier.nullifier.commitment; + x.nullifier_commitment = nullifier; + delete x.nullifier; +} + +export function formatWasmJson(data: any): any { + return JSON.parse( + JSON.stringify(data, (_, value) => { + if (typeof value === 'bigint') { + return value.toString(); + } else if (value instanceof Uint8Array) { + return Array.from(value); + } + return value; + }), + ); +} + +/// NOTE: `post` from manta-rs sign result should match runtime side data structure type. +export function transferPost(post: any): any { + const json = formatWasmJson(post); + + // transfer authorization_signature format + if (json.authorization_signature && json.authorization_signature.signature) { + const scalar = json.authorization_signature.signature.scalar; + const nonce = json.authorization_signature.signature.nonce_point; + json.authorization_signature.signature = [scalar, nonce]; + } + + // transfer receiver_posts to match runtime side + json.receiver_posts && + json.receiver_posts.map((x: any) => { + convertReceiverPost(x); + }); + + // transfer sender_posts to match runtime side + json.sender_posts && + json.sender_posts.map((x: any) => { + convertSenderPost(x); + }); + + return json; +} + +export function log(enabled: boolean, message: string, name: string) { + if (!enabled) { + return; + } + console.log(`[${name}]: ${performance.now().toFixed(4)}, ${message}`); +} + +export async function fetchFiles( + filePrefix: string, + fileNames: string[], +): Promise { + const fileList = await Promise.all( + fileNames.map((name) => fetchFile(`${filePrefix}/${name}`)), + ); + return fileList; +} + +export async function fetchFile(url: string): Promise { + try { + const responseData = await fetch(url); + const result = await responseData.blob(); + const reader = new FileReader(); + return new Promise((resolve) => { + try { + reader.addEventListener('load', () => { + resolve(new Uint8Array(reader.result as ArrayBuffer)); + }); + reader.addEventListener('error', () => { + resolve(null); + }); + // Read the contents of the specified Blob or File + reader.readAsArrayBuffer(result); + } catch (ex) { + resolve(null); + } + }); + // return result; + } catch (ex) { + console.error(`Fetch ${url}, failed`, ex); + } + return null; +} + +export function wrapWasmError(error: Error | string) { + return typeof error === 'string' ? new Error(error) : error; +} diff --git a/manta-js/package/src/wallet/crate/Cargo.toml b/manta-js/package/src/wallet/crate/Cargo.toml index 6bc6fdba..0ba4aa69 100644 --- a/manta-js/package/src/wallet/crate/Cargo.toml +++ b/manta-js/package/src/wallet/crate/Cargo.toml @@ -35,11 +35,11 @@ disable-restart = [] console_error_panic_hook = { version = "0.1.7", default-features = false } getrandom = { version = "0.2.8", default-features = false, features = ["js"] } js-sys = { version = "0.3.60", default-features = false } -manta-accounting = { git = "https://github.com/manta-network/manta-rs.git", branch = "signer_initial_sync_checkout", default-features = false, features = ["serde"] } -manta-crypto = { git = "https://github.com/manta-network/manta-rs.git", branch = "signer_initial_sync_checkout", default-features = false, features = ["serde"] } -manta-parameters = { git = "https://github.com/manta-network/manta-rs.git", branch = "signer_initial_sync_checkout", default-features = false } -manta-pay = { git = "https://github.com/manta-network/manta-rs.git", branch = "signer_initial_sync_checkout", default-features = true, features = ["arkworks", "groth16", "http", "scale", "serde", "wallet"] } -manta-util = { git = "https://github.com/manta-network/manta-rs.git", branch = "signer_initial_sync_checkout", default-features = false, features = ["serde", "reqwest"] } +manta-accounting = { git = "https://github.com/manta-network/manta-rs.git", tag = "v0.5.13", default-features = false, features = ["serde"] } +manta-crypto = { git = "https://github.com/manta-network/manta-rs.git", tag = "v0.5.13", default-features = false, features = ["serde"] } +manta-parameters = { git = "https://github.com/manta-network/manta-rs.git", tag = "v0.5.13", default-features = false } +manta-pay = { git = "https://github.com/manta-network/manta-rs.git", tag = "v0.5.13", default-features = true, features = ["arkworks", "groth16", "http", "scale", "serde", "wallet"] } +manta-util = { git = "https://github.com/manta-network/manta-rs.git", tag = "v0.5.13", default-features = false, features = ["serde", "reqwest"] } scale-codec = { package = "parity-scale-codec", version = "3.2.1", default-features = false, features = ["derive", "max-encoded-len"] } serde-wasm-bindgen = { version = "0.4.5", default-features = false } serde_json = { version = "1.0.89", default-features = false, features = ["alloc", "arbitrary_precision"] } diff --git a/manta-js/package/src/wallet/crate/src/lib.rs b/manta-js/package/src/wallet/crate/src/lib.rs index fb399ec1..92426f5b 100644 --- a/manta-js/package/src/wallet/crate/src/lib.rs +++ b/manta-js/package/src/wallet/crate/src/lib.rs @@ -253,11 +253,6 @@ impl_js_compatible!( ); impl_js_compatible!(SyncError, signer::SyncError, "Synchronization Error"); impl_js_compatible!(SyncResult, signer::SyncResult, "Synchronization Result"); -impl_js_compatible!( - SignWithTransactionDataResponse, - signer::SignWithTransactionDataResponse, - "Sign With Transaction Data Response" -); impl_js_compatible!(FullParameters, config::FullParameters, "Full Parameters"); impl_js_compatible!( SignWithTransactionDataResult, @@ -1212,18 +1207,18 @@ impl Signer { /// [`Identifier`]. Returns `None` if `post` has an invalid shape, or if `self` doesn't own the /// underlying assets in `post`. #[inline] - pub fn transaction_data(&self, post: TransferPost) -> Option { - self.0.transaction_data(post.into()).map(Into::into) + pub fn transaction_data(&mut self, post: TransferPost) -> Option { + self.as_mut().transaction_data(post.into()).map(Into::into) } /// Returns a vector with the [`TransactionData`] of each well-formed [`TransferPost`] owned by /// `self`. #[inline] pub fn batched_transaction_data( - &self, + &mut self, posts: TransactionDataRequest, ) -> TransactionDataResponse { - self.as_ref().batched_transaction_data(posts.0 .0).into() + self.as_mut().batched_transaction_data(posts.0 .0).into() } /// Signs `transaction` and returns the generated [`TransferPost`]s, as @@ -1601,7 +1596,13 @@ impl Wallet { Box::pin(async { this.sign_with_transaction_data(transaction.into(), metadata.map(Into::into)) .await - .map(SignWithTransactionDataResponse::from) + .map(|response| { + response + .0 + .into_iter() + .map(|(post, data)| (TransferPost::from(post), data)) + .collect::>() + }) }) }, network, diff --git a/manta-js/package/yarn.lock b/manta-js/package/yarn.lock index d0f50c6b..77e47699 100644 --- a/manta-js/package/yarn.lock +++ b/manta-js/package/yarn.lock @@ -3,16 +3,16 @@ "@eslint-community/eslint-utils@^4.2.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz#a556790523a351b4e47e9d385f47265eaaf9780a" - integrity sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA== + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403" - integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ== + version "4.4.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.1.tgz#087cb8d9d757bb22e9c9946c9c0c2bf8806830f1" + integrity sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw== "@eslint/eslintrc@^2.0.1": version "2.0.1" @@ -444,9 +444,9 @@ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/node@*": - version "18.15.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.5.tgz#3af577099a99c61479149b716183e70b5239324a" - integrity sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew== + version "18.15.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.6.tgz#af98ef4a36e7ac5f2d03040f3109fcce972bf6cb" + integrity sha512-YErOafCZpK4g+Rp3Q/PBgZNAsWKGunQTm9FA3/Pbcm0VCriTEzcrutQ/SxSc0rytAp0NoFWue669jmKhEtd0sA== "@types/semver@^7.3.12": version "7.3.13"