From 341337fc80bfa0d4b8cf4576924ab5cc26738d10 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 26 Aug 2024 11:53:21 +0200 Subject: [PATCH] started new docs draft --- documentation/new_docs/.gitignore | 6 + documentation/new_docs/README.md | 22 ++ .../code-examples/cosmoskit-example-code.mdx | 190 ++++++++++++ .../code-examples/mixfetch-example-code.mdx | 84 ++++++ .../code-examples/mixnodes-example-code.mdx | 54 ++++ .../code-examples/traffic-example-code.mdx | 103 +++++++ .../code-examples/wallet-connect-code.mdx | 133 +++++++++ .../code-examples/wallet-delegations-code.mdx | 189 ++++++++++++ .../code-examples/wallet-sendTokens-code.mdx | 72 +++++ .../new_docs/code-snippets/mixfetchurl.tsx | 47 +++ .../new_docs/components/client/index.ts | 15 + .../new_docs/components/cosmos-kit/data.ts | 28 ++ .../new_docs/components/cosmos-kit/index.tsx | 188 ++++++++++++ .../new_docs/components/cosmos-kit/ledger.tsx | 74 +++++ .../new_docs/components/cosmos-kit/sign.tsx | 57 ++++ documentation/new_docs/components/footer.tsx | 22 ++ .../new_docs/components/mix-fetch.tsx | 82 ++++++ .../new_docs/components/mixnodes.tsx | 80 +++++ documentation/new_docs/components/npm.tsx | 17 ++ documentation/new_docs/components/traffic.tsx | 113 +++++++ .../new_docs/components/wallet/connect.tsx | 67 +++++ .../components/wallet/delegations.tsx | 129 ++++++++ .../new_docs/components/wallet/sendTokens.tsx | 69 +++++ .../wallet/utils/wallet.context.tsx | 262 +++++++++++++++++ .../components/wallet/utils/wallet.methods.ts | 50 ++++ documentation/new_docs/next-env.d.ts | 5 + documentation/new_docs/next.config.js | 50 ++++ documentation/new_docs/package.json | 54 ++++ documentation/new_docs/pages/_app.tsx | 27 ++ documentation/new_docs/pages/_meta.json | 18 ++ documentation/new_docs/pages/index.mdx | 1 + documentation/new_docs/pages/sdk/_meta.json | 4 + .../new_docs/pages/sdk/rust/_meta.json | 7 + .../new_docs/pages/sdk/rust/examples.md | 12 + .../new_docs/pages/sdk/rust/examples/cargo.md | 35 +++ .../pages/sdk/rust/examples/credential.md | 8 + .../pages/sdk/rust/examples/custom-network.md | 18 ++ .../new_docs/pages/sdk/rust/examples/keys.md | 28 ++ .../pages/sdk/rust/examples/simple.md | 8 + .../new_docs/pages/sdk/rust/examples/socks.md | 10 + .../pages/sdk/rust/examples/split-send.md | 6 + .../pages/sdk/rust/examples/storage.md | 6 + .../new_docs/pages/sdk/rust/examples/surbs.md | 10 + .../new_docs/pages/sdk/rust/index.md | 46 +++ .../pages/sdk/rust/message-helpers.md | 70 +++++ .../new_docs/pages/sdk/rust/message-types.md | 5 + .../pages/sdk/rust/troubleshooting.md | 115 ++++++++ .../pages/sdk/typescript/FAQ/_meta.json | 4 + .../pages/sdk/typescript/FAQ/general.mdx | 74 +++++ .../new_docs/pages/sdk/typescript/_meta.json | 18 ++ .../pages/sdk/typescript/bundling/_meta.json | 6 + .../sdk/typescript/bundling/bundling.mdx | 54 ++++ .../pages/sdk/typescript/bundling/esbuild.mdx | 32 ++ .../pages/sdk/typescript/bundling/webpack.mdx | 93 ++++++ .../pages/sdk/typescript/examples/_meta.json | 6 + .../sdk/typescript/examples/cosmos-kit.mdx | 158 ++++++++++ .../sdk/typescript/examples/mix-fetch.mdx | 161 ++++++++++ .../pages/sdk/typescript/examples/mixnet.mdx | 153 ++++++++++ .../examples/nym-smart-contracts.mdx | 275 ++++++++++++++++++ .../new_docs/pages/sdk/typescript/index.mdx | 36 +++ .../pages/sdk/typescript/installation.mdx | 70 +++++ .../pages/sdk/typescript/integrations.mdx | 111 +++++++ .../pages/sdk/typescript/overview.mdx | 71 +++++ .../sdk/typescript/playground/_meta.json | 7 + .../sdk/typescript/playground/cosmos-kit.mdx | 28 ++ .../sdk/typescript/playground/mixfetch.mdx | 24 ++ .../sdk/typescript/playground/mixnodes.mdx | 13 + .../sdk/typescript/playground/traffic.mdx | 11 + .../sdk/typescript/playground/wallet.mdx | 24 ++ .../sdk/typescript/scripts/build-prod-docs.sh | 17 ++ .../new_docs/pages/sdk/typescript/start.mdx | 82 ++++++ documentation/new_docs/pages/styles.css | 108 +++++++ documentation/new_docs/theme.config.tsx | 28 ++ documentation/new_docs/tsconfig.json | 20 ++ documentation/new_docs/typings/txt.d.ts | 5 + 75 files changed, 4385 insertions(+) create mode 100644 documentation/new_docs/.gitignore create mode 100644 documentation/new_docs/README.md create mode 100644 documentation/new_docs/code-examples/cosmoskit-example-code.mdx create mode 100644 documentation/new_docs/code-examples/mixfetch-example-code.mdx create mode 100644 documentation/new_docs/code-examples/mixnodes-example-code.mdx create mode 100644 documentation/new_docs/code-examples/traffic-example-code.mdx create mode 100644 documentation/new_docs/code-examples/wallet-connect-code.mdx create mode 100644 documentation/new_docs/code-examples/wallet-delegations-code.mdx create mode 100644 documentation/new_docs/code-examples/wallet-sendTokens-code.mdx create mode 100644 documentation/new_docs/code-snippets/mixfetchurl.tsx create mode 100644 documentation/new_docs/components/client/index.ts create mode 100644 documentation/new_docs/components/cosmos-kit/data.ts create mode 100644 documentation/new_docs/components/cosmos-kit/index.tsx create mode 100644 documentation/new_docs/components/cosmos-kit/ledger.tsx create mode 100644 documentation/new_docs/components/cosmos-kit/sign.tsx create mode 100644 documentation/new_docs/components/footer.tsx create mode 100644 documentation/new_docs/components/mix-fetch.tsx create mode 100644 documentation/new_docs/components/mixnodes.tsx create mode 100644 documentation/new_docs/components/npm.tsx create mode 100644 documentation/new_docs/components/traffic.tsx create mode 100644 documentation/new_docs/components/wallet/connect.tsx create mode 100644 documentation/new_docs/components/wallet/delegations.tsx create mode 100644 documentation/new_docs/components/wallet/sendTokens.tsx create mode 100644 documentation/new_docs/components/wallet/utils/wallet.context.tsx create mode 100644 documentation/new_docs/components/wallet/utils/wallet.methods.ts create mode 100644 documentation/new_docs/next-env.d.ts create mode 100644 documentation/new_docs/next.config.js create mode 100644 documentation/new_docs/package.json create mode 100644 documentation/new_docs/pages/_app.tsx create mode 100644 documentation/new_docs/pages/_meta.json create mode 100644 documentation/new_docs/pages/index.mdx create mode 100644 documentation/new_docs/pages/sdk/_meta.json create mode 100644 documentation/new_docs/pages/sdk/rust/_meta.json create mode 100644 documentation/new_docs/pages/sdk/rust/examples.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/cargo.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/credential.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/custom-network.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/keys.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/simple.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/socks.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/split-send.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/storage.md create mode 100644 documentation/new_docs/pages/sdk/rust/examples/surbs.md create mode 100644 documentation/new_docs/pages/sdk/rust/index.md create mode 100644 documentation/new_docs/pages/sdk/rust/message-helpers.md create mode 100644 documentation/new_docs/pages/sdk/rust/message-types.md create mode 100644 documentation/new_docs/pages/sdk/rust/troubleshooting.md create mode 100644 documentation/new_docs/pages/sdk/typescript/FAQ/_meta.json create mode 100644 documentation/new_docs/pages/sdk/typescript/FAQ/general.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/_meta.json create mode 100644 documentation/new_docs/pages/sdk/typescript/bundling/_meta.json create mode 100644 documentation/new_docs/pages/sdk/typescript/bundling/bundling.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/bundling/esbuild.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/bundling/webpack.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/examples/_meta.json create mode 100644 documentation/new_docs/pages/sdk/typescript/examples/cosmos-kit.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/examples/mix-fetch.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/examples/mixnet.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/examples/nym-smart-contracts.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/index.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/installation.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/integrations.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/overview.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/playground/_meta.json create mode 100644 documentation/new_docs/pages/sdk/typescript/playground/cosmos-kit.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/playground/mixfetch.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/playground/mixnodes.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/playground/traffic.mdx create mode 100644 documentation/new_docs/pages/sdk/typescript/playground/wallet.mdx create mode 100755 documentation/new_docs/pages/sdk/typescript/scripts/build-prod-docs.sh create mode 100644 documentation/new_docs/pages/sdk/typescript/start.mdx create mode 100644 documentation/new_docs/pages/styles.css create mode 100644 documentation/new_docs/theme.config.tsx create mode 100644 documentation/new_docs/tsconfig.json create mode 100644 documentation/new_docs/typings/txt.d.ts diff --git a/documentation/new_docs/.gitignore b/documentation/new_docs/.gitignore new file mode 100644 index 0000000000..46f9bd0984 --- /dev/null +++ b/documentation/new_docs/.gitignore @@ -0,0 +1,6 @@ +.next +node_modules +out + +# the lock file will break Vercel because it may get committed from a machine with a different build architecture +package-lock.json diff --git a/documentation/new_docs/README.md b/documentation/new_docs/README.md new file mode 100644 index 0000000000..a7d5206dd4 --- /dev/null +++ b/documentation/new_docs/README.md @@ -0,0 +1,22 @@ +# Nym Docs v2 + +## Local development + +``` +npm i +npm run dev +``` + +Open http://localhost:3000 to browse the output that will hot-reload when you make changes. + +## Build + +``` +npm run build +``` + +The static output will be in `./out`; + +## Template details + +This documentation was made with [Nextra](https://nextra.site) using the template from here https://github.com/shuding/nextra-docs-template. diff --git a/documentation/new_docs/code-examples/cosmoskit-example-code.mdx b/documentation/new_docs/code-examples/cosmoskit-example-code.mdx new file mode 100644 index 0000000000..dd04d7c466 --- /dev/null +++ b/documentation/new_docs/code-examples/cosmoskit-example-code.mdx @@ -0,0 +1,190 @@ +```ts copy filename="CosmosKitExample.tsx" +import React, { FC } from 'react'; +import { ChainProvider, useChain } from '@cosmos-kit/react'; +import { assets, chains } from 'chain-registry'; +import { wallets as keplr } from '@cosmos-kit/keplr'; +import { wallets as ledger } from '@cosmos-kit/ledger'; +import Button from '@mui/material/Button'; +import CircularProgress from '@mui/material/CircularProgress'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import { Alert, AlertTitle } from '@mui/material'; +import { Wallet } from '@cosmos-kit/core'; +import { CosmosKitLedger } from './ledger'; +import { CosmosKitSign } from './sign'; + +const CosmosKitSetup: FC<{ children: React.ReactNode }> = ({ children }) => { + const assetsFixedUp = React.useMemo(() => { + const nyx = assets.find((a) => a.chain_name === 'nyx'); + if (nyx) { + const nyxCoin = nyx.assets.find((a) => a.name === 'nyx'); + if (nyxCoin) { + nyxCoin.coingecko_id = 'nyx'; + } + nyx.assets = nyx.assets.reverse(); + } + return assets; + }, [assets]); + + const chainsFixedUp = React.useMemo(() => { + const nyx = chains.find((c) => c.chain_id === 'nyx'); + if (nyx) { + if (!nyx.staking) { + nyx.staking = { + staking_tokens: [{ denom: 'unyx' }], + lock_duration: { + blocks: 10000, + }, + }; + } + } + return chains; + }, [chains]); + + return ( + 'amino', + }} + > + {children} + + ); +}; + +function walletRejectMessageOrError(wallet?: Wallet, error?: React.ReactNode) { + if (!wallet) { + if (!error) { + return undefined; + } + return error; + } + if (typeof wallet.rejectMessage === 'string') { + return wallet.rejectMessage; + } + return wallet.rejectMessage.source; +} +const Wrapper: FC<{ + children?: React.ReactNode; + wallet?: Wallet; + header?: React.ReactNode; + error?: React.ReactNode; + disconnect: () => Promise; +}> = ({ wallet, disconnect, error, header, children }) => { + if (error) { + return ( + + + {wallet && Failed to connect to {wallet.prettyName}} + {wallet && walletRejectMessageOrError(wallet)} + + + + + + ); + } + return ( + + + {header && header} + + + {children} + + ); +}; + +const CosmosKitInner = () => { + const { + wallet, + address, + connect, + disconnect, + isWalletConnecting, + isWalletDisconnected, + isWalletError, + isWalletNotExist, + isWalletRejected, + } = useChain('nyx'); + + if (isWalletError) { + return ; + } + + if (isWalletNotExist) { + return ; + } + + if (isWalletRejected) { + return ( + + ); + } + + if (isWalletDisconnected) { + return ( + + ); + } + + if (isWalletConnecting) { + return ; + } + + // Ledger Hardware Wallet + if (wallet.mode === 'ledger') { + return ( + + Connected to {wallet.prettyName} + + Address: {address}{' '} + + + } + disconnect={disconnect} + > + + + ); + } + + // Extension or Wallet Connect + return ( + + Connected to {wallet.prettyName} + + Address: {address}{' '} + + + } + disconnect={disconnect} + > + + + ); +}; + +export const CosmosKit = () => ( + + + +); +``` diff --git a/documentation/new_docs/code-examples/mixfetch-example-code.mdx b/documentation/new_docs/code-examples/mixfetch-example-code.mdx new file mode 100644 index 0000000000..d0fc84f514 --- /dev/null +++ b/documentation/new_docs/code-examples/mixfetch-example-code.mdx @@ -0,0 +1,84 @@ +```ts copy filename="mixFetchExample.tsx" +import React, { useState } from 'react'; +import CircularProgress from '@mui/material/CircularProgress'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import Box from '@mui/material/Box'; +import { mixFetch } from '@nymproject/mix-fetch-full-fat'; +import Stack from '@mui/material/Stack'; +import Paper from '@mui/material/Paper'; +import type { SetupMixFetchOps } from '@nymproject/mix-fetch'; + +const defaultUrl = 'https://nymtech.net/favicon.svg'; +const args = { mode: 'unsafe-ignore-cors' }; + +const mixFetchOptions: SetupMixFetchOps = { + preferredGateway: '6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', // with WSS + preferredNetworkRequester: + '8rRGWy54oC8drFL9DepMegBt2DLrsqQwCoHMXt9nsnTo.2XjCPVbb4FpQ9hNRcXwb9mTzEAVVk1zf1tcch3wdtNEA@6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', + mixFetchOverride: { + requestTimeoutMs: 60_000, + }, + forceTls: true, // force WSS + extra: {}, +}; + +export const MixFetch = () => { + const [url, setUrl] = useState(defaultUrl); + const [html, setHtml] = useState(); + const [busy, setBusy] = useState(false); + + const handleFetch = async () => { + try { + setBusy(true); + setHtml(undefined); + const response = await mixFetch(url, args, mixFetchOptions); + console.log(response); + const resHtml = await response.text(); + setHtml(resHtml); + } catch (err) { + console.log(err); + } finally { + setBusy(false); + } + }; + + return ( +
+ + setUrl(e.target.value)} + /> + + + + {busy && ( + + + + )} + {html && ( + <> + + Response + + + + {html} + + + + )} +
+ ); +}; +``` diff --git a/documentation/new_docs/code-examples/mixnodes-example-code.mdx b/documentation/new_docs/code-examples/mixnodes-example-code.mdx new file mode 100644 index 0000000000..e0624287bd --- /dev/null +++ b/documentation/new_docs/code-examples/mixnodes-example-code.mdx @@ -0,0 +1,54 @@ +```ts copy filename="MixnodeContractQueryExample.ts" +import { useEffect, useState } from "react"; +import { contracts } from "@nymproject/contract-clients"; +import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { settings } from "./client"; +import Box from "@mui/material/Box"; +import CircularProgress from "@mui/material/CircularProgress"; + +const getClient = async () => { + const cosmWasmClient = await SigningCosmWasmClient.connect(settings.url); + + const client = new contracts.Mixnet.MixnetQueryClient( + cosmWasmClient, + settings.mixnetContractAddress + ); + return client; +}; + +export const Mixnodes = () => { + const [mixnodes, setMixnodes] = useState(); + + const getMixnodes = async () => { + const client = await getClient(); + const { nodes } = await client.getMixNodesDetailed({}); + setMixnodes(nodes); + }; + + useEffect(() => { + getMixnodes(); + }, []); + + if (!mixnodes) { + return ( + + + + ); + } + + return ( +
+ {mixnodes?.length && + mixnodes.map((mixnode: any) => ( + + {`id: ${mixnode.bond_information.mix_id}`} + {`owner: ${mixnode.bond_information.owner}`} + + ))} +
+ ); +}; +``` diff --git a/documentation/new_docs/code-examples/traffic-example-code.mdx b/documentation/new_docs/code-examples/traffic-example-code.mdx new file mode 100644 index 0000000000..351e5bdbde --- /dev/null +++ b/documentation/new_docs/code-examples/traffic-example-code.mdx @@ -0,0 +1,103 @@ +```ts copy filename="MixnetWASMClientExample.tsx" +import React, { useEffect, useState } from 'react'; +import { createNymMixnetClient, NymMixnetClient, Payload } from '@nymproject/sdk-full-fat'; +import Box from '@mui/material/Box'; +import CircularProgress from '@mui/material/CircularProgress'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; + +const nymApiUrl = 'https://validator.nymtech.net/api'; + +export const Traffic = () => { + const [nym, setNym] = useState(); + const [selfAddress, setSelfAddress] = useState(); + const [recipient, setRecipient] = useState(); + const [payload, setPayload] = useState(); + const [receivedMessage, setReceivedMessage] = useState(); + + const init = async () => { + const client = await createNymMixnetClient(); + setNym(client); + + await client?.client.start({ + clientId: crypto.randomUUID(), + nymApiUrl, + forceTls: true, // force WSS + }); + + client?.events.subscribeToConnected((e) => { + const { address } = e.args; + setSelfAddress(address); + }); + + client?.events.subscribeToLoaded((e) => { + console.log('Client ready: ', e.args); + }); + + + client?.events.subscribeToTextMessageReceivedEvent((e) => { + console.log(e.args.payload); + setReceivedMessage(e.args.payload); + }); + }; + + const stop = async () => { + await nym?.client.stop(); + }; + + const send = () => nym.client.send({ payload, recipient }); + + useEffect(() => { + init(); + return () => { + stop(); + }; + }, []); + + if (!nym || !selfAddress) { + return ( + + + + ); + } + + return ( + + + + My self address is: + {selfAddress || 'loading'} + Communication through the Mixnet + setRecipient(e.target.value)} + size="small" + /> + setPayload({ message: e.target.value, mimeType: 'text/plain' })} + size="small" + /> + + + {receivedMessage && ( + + Message Received! + {receivedMessage} + + )} + + + ); +}; +``` diff --git a/documentation/new_docs/code-examples/wallet-connect-code.mdx b/documentation/new_docs/code-examples/wallet-connect-code.mdx new file mode 100644 index 0000000000..060e7a5106 --- /dev/null +++ b/documentation/new_docs/code-examples/wallet-connect-code.mdx @@ -0,0 +1,133 @@ +```ts copy filename="FormattedWalletConnectCode.tsx" +import React from 'react'; +import { Coin } from '@cosmjs/stargate'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import TextField from '@mui/material/TextField'; + +// Connect method on Parent Component +const getSignerAccount = async () => { + setAccountLoading(true); + try { + const signer = await signerAccount(mnemonic); + const accounts = await signer.getAccounts(); + if (accounts[0]) { + setAccount(accounts[0].address); + } + } catch (error) { + console.error(error); + } + setAccountLoading(false); +}; + +// Get Balance on Parent Component +const getBalance = useCallback(async () => { + setBalanceLoading(true); + try { + const newBalance = await signerCosmosWasmClient?.getBalance(account, 'unym'); + setBalance(newBalance); + } catch (error) { + console.error(error); + } + setBalanceLoading(false); +}, [account, signerCosmosWasmClient]); + +const getClients = async () => { + setClientLoading(true); + try { + setSignerCosmosWasmClient(await fetchSignerCosmosWasmClient(mnemonic)); + setSignerClient(await fetchSignerClient(mnemonic)); + } catch (error) { + console.error(error); + } + setClientLoading(false); +}; + +const connect = () => { + getSignerAccount(); + getClients(); +}; + +// Get Signner Account on Parent Component +const getSignerAccount = async () => { + setAccountLoading(true); + try { + const signer = await signerAccount(mnemonic); + const accounts = await signer.getAccounts(); + if (accounts[0]) { + setAccount(accounts[0].address); + } + } catch (error) { + console.error(error); + } + setAccountLoading(false); +}; + +export const ConnectWallet = ({ + setMnemonic, + connect, + mnemonic, + accountLoading, + clientLoading, + balanceLoading, + account, + balance, + connectButtonText, +}: { + setMnemonic: (value: string) => void; + connect: () => void; + mnemonic: string; + accountLoading: boolean; + clientLoading: boolean; + balanceLoading: boolean; + account: string; + balance: Coin; + connectButtonText: string; +}) => { + return ( + + + Connect to your account + + + Your account + + + Enter the mnemonic + + setMnemonic(e.target.value)} + fullWidth + multiline + maxRows={4} + sx={{ marginBottom: 3 }} + /> + + + {account && balance ? ( + + Address: {account} + + Balance: {balance?.amount} {balance?.denom} + + + ) : ( + + Please, enter your mnemonic to receive your account information + + )} + + + ); +}; +``` diff --git a/documentation/new_docs/code-examples/wallet-delegations-code.mdx b/documentation/new_docs/code-examples/wallet-delegations-code.mdx new file mode 100644 index 0000000000..d70b5f1b79 --- /dev/null +++ b/documentation/new_docs/code-examples/wallet-delegations-code.mdx @@ -0,0 +1,189 @@ +```ts copy filename="FormattedWalletDelegationsCode.tsx" +import React from 'react'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Box from '@mui/material/Box'; +import { TableBody, TableCell, TableHead, TableRow, TextField, Typography } from '@mui/material'; +import Table from '@mui/material/Table'; + +// Get Delegations on parent component + const getDelegations = useCallback(async () => { + const newDelegations = await signerClient.getDelegatorDelegations({ + delegator: settings.address, + }); + setDelegations(newDelegations); + }, [signerClient]); + +// Make a Delegation on parent component + const doDelegate = async ({ mixId, amount }: { mixId: number; amount: number }) => { + if (!signerClient) { + return; + } + setDelegationLoader(true); + try { + const res = await signerClient.delegateToMixnode({ mixId }, 'auto', undefined, [ + { amount: `${amount}`, denom: 'unym' }, + ]); + console.log('res', res); + setLog((prev) => [ + ...prev, +
+ {new Date().toLocaleTimeString()} +
{JSON.stringify(res, null, 2)}
+
, + ]); + } catch (error) { + console.error(error); + } + setDelegationLoader(false); + }; + + // Undelegate All on Parent Component + const doUndelegateAll = async () => { + if (!signerClient) { + return; + } + setUndeledationLoader(true); + try { + // eslint-disable-next-line no-restricted-syntax + for (const delegation of delegations.delegations) { + // eslint-disable-next-line no-await-in-loop + await signerClient.undelegateFromMixnode({ mixId: delegation.mix_id }, 'auto'); + } + } catch (error) { + console.error(error); + } + setUndeledationLoader(false); + }; + + // Withdraw Rewards on Parent Component + const doWithdrawRewards = async () => { + const delegatorAddress = ''; + const validatorAdress = ''; + const memo = 'test sending tokens'; + setWithdrawLoading(true); + try { + const res = await signerCosmosWasmClient.withdrawRewards(delegatorAddress, validatorAdress, 'auto', memo); + setLog((prev) => [ + ...prev, +
+ {new Date().toLocaleTimeString()} +
{JSON.stringify(res, null, 2)}
+
, + ]); + } catch (error) { + console.error(error); + } + setWithdrawLoading(false); + }; + +import React, { useState } from 'react'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Box from '@mui/material/Box'; +import { TableBody, TableCell, TableHead, TableRow, TextField, Typography } from '@mui/material'; +import Table from '@mui/material/Table'; + +export const Delegations = ({ + delegations, + doDelegate, + delegationLoader, + doUndelegateAll, + undeledationLoader, + doWithdrawRewards, + withdrawLoading, +}: { + delegations: any; + doDelegate: ({ mixId, amount }: { mixId: number; amount: number }) => void; + delegationLoader: boolean; + doUndelegateAll: () => void; + undeledationLoader: boolean; + doWithdrawRewards: () => void; + withdrawLoading: boolean; +}) => { + const [delegationNodeId, setDelegationNodeId] = useState(); + const [amountToBeDelegated, setAmountToBeDelegated] = useState(); + + return ( + + + Delegations + + + + Make a delegation + + setDelegationNodeId(e.target.value)} + size="small" + /> + + setAmountToBeDelegated(e.target.value)} + size="small" + /> + + + + + + Your delegations + + {!delegations?.delegations?.length ? ( + You do not have delegations + ) : ( + + + + + MixId + Owner + Amount + Cumulative Reward Ratio + + + + {delegations?.delegations.map((delegation: any) => ( + + {delegation.mix_id} + {delegation.owner} + {delegation.amount.amount} + {delegation.cumulative_reward_ratio} + + ))} + +
+
+ )} +
+ {delegations && ( + + + + )} + + + +
+
+
+ ); +}; + +``` \ No newline at end of file diff --git a/documentation/new_docs/code-examples/wallet-sendTokens-code.mdx b/documentation/new_docs/code-examples/wallet-sendTokens-code.mdx new file mode 100644 index 0000000000..1b142ed936 --- /dev/null +++ b/documentation/new_docs/code-examples/wallet-sendTokens-code.mdx @@ -0,0 +1,72 @@ +```ts copy filename="FormattedWalletSendTokensCode.tsx" +import React, { useState } from 'react'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import TextField from '@mui/material/TextField'; + +// Send tokens on Parent component + const doSendTokens = async (amount: string) => { + const memo = 'test sending tokens'; + setSendingTokensLoader(true); + try { + const res = await signerCosmosWasmClient.sendTokens( + account, + recipientAddress, + [{ amount, denom: 'unym' }], + 'auto', + memo, + ); + setLog((prev) => [ + ...prev, +
+ {new Date().toLocaleTimeString()} +
{JSON.stringify(res, null, 2)}
+
, + ]); + } catch (error) { + console.error(error); + } + setSendingTokensLoader(false); + }; + +export const SendTokes = ({ + setRecipientAddress, + doSendTokens, + sendingTokensLoader, +}: { + setRecipientAddress: (value: string) => void; + doSendTokens: (amount: string) => void; + sendingTokensLoader: boolean; +}) => { + const [tokensToSend, setTokensToSend] = useState(); + + return ( + + + Send Tokens + + setRecipientAddress(e.target.value)} + size="small" + /> + + setTokensToSend(e.target.value)} + size="small" + /> + + + + + + ); +}; +``` \ No newline at end of file diff --git a/documentation/new_docs/code-snippets/mixfetchurl.tsx b/documentation/new_docs/code-snippets/mixfetchurl.tsx new file mode 100644 index 0000000000..161400728d --- /dev/null +++ b/documentation/new_docs/code-snippets/mixfetchurl.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; +import Stack from '@mui/material/Stack'; +import Box from '@mui/material/Box'; + +export const GitHubRepoSearch = () => { + const [repoUrl, setRepoUrl] = useState(''); + + const handleSearch = () => { + if(!repoUrl || repoUrl.length < 1 ) { + return window.alert("Please enter a valid Github URL!") + } + const matchedRepo = repoUrl.match(/https:\/\/github\.com\/(.*)/)[1] + + // Construct the search URL + const searchUrl = `https://github.com/search?q=repo:${matchedRepo} fetch(&type=code`; + + // Redirect the user to a new search results page + window.open(searchUrl, "_blank"); + }; + + return ( + + + setRepoUrl(e.target.value)} + size="small" + sx={{width: "450px"}} + /> + + + + + ); +} + diff --git a/documentation/new_docs/components/client/index.ts b/documentation/new_docs/components/client/index.ts new file mode 100644 index 0000000000..ba7a70ef76 --- /dev/null +++ b/documentation/new_docs/components/client/index.ts @@ -0,0 +1,15 @@ +export const mainnetSettings = { + url: 'wss://rpc.nymtech.net:443', + mixnetContractAddress: 'n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr', + mnemonic: process.env.MAINNET_MNEMONIC, + address: 'n1c7y676pe3av76r5usala759xgj0yplmvngu8u8', +}; + +export const qaSettings = { + url: 'wss://rpc.sandbox.nymtech.net', + mixnetContractAddress: 'n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav', + mnemonic: process.env.QA_MNEMONIC, + address: 'n13uryxldwdllpakevsmt6n0uyfn3kgr2wvj5dnf', +}; + +export const settings = qaSettings; diff --git a/documentation/new_docs/components/cosmos-kit/data.ts b/documentation/new_docs/components/cosmos-kit/data.ts new file mode 100644 index 0000000000..d25456c980 --- /dev/null +++ b/documentation/new_docs/components/cosmos-kit/data.ts @@ -0,0 +1,28 @@ +import { AminoMsg, makeSignDoc, serializeSignDoc } from '@cosmjs/amino'; +import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx'; + +export const getDoc = (address: string) => { + const chainId = 'nyx'; + + const msg: AminoMsg = { + type: '/cosmos.bank.v1beta1.MsgSend', + value: MsgSend.fromPartial({ + fromAddress: address, + toAddress: 'n1nn8tghp94n8utsgyg3kfttlxm0exgjrsqkuwu9', + amount: [{ amount: '1000', denom: 'unym' }], + }), + }; + const fee = { + amount: [{ amount: '2000', denom: 'ucosm' }], + gas: '180000', // 180k + }; + const memo = 'Use your power wisely'; + const accountNumber = 15; + const sequence = 16; + + return makeSignDoc([msg], fee, chainId, memo, accountNumber, sequence); +}; +export const aminoDoc = (address: string) => { + const signDoc = getDoc(address); + return serializeSignDoc(signDoc); +}; diff --git a/documentation/new_docs/components/cosmos-kit/index.tsx b/documentation/new_docs/components/cosmos-kit/index.tsx new file mode 100644 index 0000000000..0f575ec77a --- /dev/null +++ b/documentation/new_docs/components/cosmos-kit/index.tsx @@ -0,0 +1,188 @@ +import React, { FC } from 'react'; +import { ChainProvider, useChain } from '@cosmos-kit/react'; +import { assets, chains } from 'chain-registry'; +import { wallets as keplr } from '@cosmos-kit/keplr-extension'; +import { wallets as ledger } from '@cosmos-kit/ledger'; +import Button from '@mui/material/Button'; +import CircularProgress from '@mui/material/CircularProgress'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import { Alert, AlertTitle } from '@mui/material'; +import { Wallet } from '@cosmos-kit/core'; +import { CosmosKitLedger } from './ledger'; +import { CosmosKitSign } from './sign'; + +const CosmosKitSetup: FC<{ children: React.ReactNode }> = ({ children }) => { + const assetsFixedUp = React.useMemo(() => { + const nyx = assets.find((a) => a.chain_name === 'nyx'); + if (nyx) { + const nyxCoin = nyx.assets.find((a) => a.name === 'nyx'); + if (nyxCoin) { + nyxCoin.coingecko_id = 'nyx'; + } + nyx.assets = nyx.assets.reverse(); + } + return assets; + }, [assets]); + + const chainsFixedUp = React.useMemo(() => { + const nyx = chains.find((c) => c.chain_id === 'nyx'); + if (nyx) { + if (!nyx.staking) { + nyx.staking = { + staking_tokens: [{ denom: 'unyx' }], + lock_duration: { + blocks: 10000, + }, + }; + } + } + return chains; + }, [chains]); + + return ( + 'amino', + }} + > + {children} + + ); +}; + +function walletRejectMessageOrError(wallet?: Wallet, error?: React.ReactNode) { + if (!wallet) { + if (!error) { + return undefined; + } + return error; + } + if (typeof wallet.rejectMessage === 'string') { + return wallet.rejectMessage; + } + return wallet.rejectMessage.source; +} +const Wrapper: FC<{ + children?: React.ReactNode; + wallet?: Wallet; + header?: React.ReactNode; + error?: React.ReactNode; + disconnect: () => Promise; +}> = ({ wallet, disconnect, error, header, children }) => { + if (error) { + return ( + + + {wallet && Failed to connect to {wallet.prettyName}} + {wallet && walletRejectMessageOrError(wallet)} + + + + + + ); + } + return ( + + + {header && header} + + + {children} + + ); +}; + +const CosmosKitInner = () => { + const { + wallet, + address, + connect, + disconnect, + isWalletConnecting, + isWalletDisconnected, + isWalletError, + isWalletNotExist, + isWalletRejected, + } = useChain('nyx'); + + if (isWalletError) { + return ; + } + + if (isWalletNotExist) { + return ; + } + + if (isWalletRejected) { + return ( + + ); + } + + if (isWalletDisconnected) { + return ( + + ); + } + + if (isWalletConnecting) { + return ; + } + + // Ledger Hardware Wallet + if (wallet.mode === 'ledger') { + return ( + + Connected to {wallet.prettyName} + + Address: {address}{' '} + + + } + disconnect={disconnect} + > + + + ); + } + + // Extension or Wallet Connect + return ( + + Connected to {wallet.prettyName} + + Address: {address}{' '} + + + } + disconnect={disconnect} + > + + + ); +}; + +export const CosmosKit = () => ( + + + +); diff --git a/documentation/new_docs/components/cosmos-kit/ledger.tsx b/documentation/new_docs/components/cosmos-kit/ledger.tsx new file mode 100644 index 0000000000..a983c30e6d --- /dev/null +++ b/documentation/new_docs/components/cosmos-kit/ledger.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { useChain, useWalletClient } from '@cosmos-kit/react'; +import Button from '@mui/material/Button'; +import Box from '@mui/material/Box'; +import { pubkeyType } from '@cosmjs/amino'; +import { toBase64 } from '@cosmjs/encoding'; +import { Alert, AlertTitle, LinearProgress } from '@mui/material'; +import { aminoDoc } from './data'; + +export const CosmosKitLedger = () => { + const { wallet, address } = useChain('nyx'); + const { client } = useWalletClient(wallet?.name); + const [signResponse, setSignResponse] = React.useState(); + const [busy, setBusy] = React.useState(); + + const sign = async () => { + setBusy(true); + + const serialized = aminoDoc(address); + const account = await client.getAccount('nyx'); + + console.log('Accounts: ', account); + console.log('Info', await (client as any).client.getAppConfiguration()); + const sigAmino = await (client as any).client.sign(account.username, serialized); + const sig = { + signature: toBase64(sigAmino.signature), + pub_key: { + type: pubkeyType.secp256k1, + value: toBase64(account.pubkey), + }, + }; + console.log('Sig', { sigAmino, sig }); + setBusy(false); + + return { signature: sig }; + }; + + const handleSign = async () => { + setSignResponse(await sign()); + }; + + if (busy) { + return ( + + + + Please approve on the Ledger + Follow the instructions on the Ledger to review the message + + + ); + } + + return ( + <> + {!signResponse && ( + + Click the button below to sign a fake request with your Ledger + + + )} + {signResponse && ( + + Signature: + +
{JSON.stringify(signResponse?.signature, null, 2)}
+
+
+ )} + + ); +}; diff --git a/documentation/new_docs/components/cosmos-kit/sign.tsx b/documentation/new_docs/components/cosmos-kit/sign.tsx new file mode 100644 index 0000000000..0cff71808e --- /dev/null +++ b/documentation/new_docs/components/cosmos-kit/sign.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { useChain } from '@cosmos-kit/react'; +import Button from '@mui/material/Button'; +import Box from '@mui/material/Box'; +import { Alert, AlertTitle, LinearProgress } from '@mui/material'; +import { getDoc } from './data'; + +export const CosmosKitSign = () => { + const { address, getOfflineSignerAmino } = useChain('nyx'); + const [signResponse, setSignResponse] = React.useState(); + const [busy, setBusy] = React.useState(); + + const sign = async () => { + setBusy(true); + const doc = getDoc(address); + const tx = await getOfflineSignerAmino().signAmino(address, doc); + setBusy(false); + return tx; + }; + + const handleSign = async () => { + setSignResponse(await sign()); + }; + + if (busy) { + return ( + + + + Please approve in your wallet + Review the message to sign + + + ); + } + + return ( + <> + {!signResponse && ( + + Click the button below to sign a fake request with your wallet + + + )} + {signResponse && ( + + Signature: + +
{JSON.stringify(signResponse?.signature, null, 2)}
+
+
+ )} + + ); +}; diff --git a/documentation/new_docs/components/footer.tsx b/documentation/new_docs/components/footer.tsx new file mode 100644 index 0000000000..71061b0497 --- /dev/null +++ b/documentation/new_docs/components/footer.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import Stack from '@mui/material/Stack'; + +const links = [ + ['Twitter', 'https://nymtech.net/go/twitter'], + ['Telegram', 'https://nymtech.net/go/telegram'], + ['Discord', 'https://nymtech.net/go/discord'], + ['GitHub', 'https://nymtech.net/go/github/nym'], + ['Nym Wallet', 'https://nymtech.net/download/wallet'], + ['Nym Explorer', 'https://explorer.nymtech.net/'], + ['Nym Blog', 'https://nymtech.medium.com/'], + ['Nym Shipyard', 'https://shipyard.nymtech.net/'], +]; +export const Footer = () => ( + + {links.map((link) => ( + + {link[0]} + + ))} + +); diff --git a/documentation/new_docs/components/mix-fetch.tsx b/documentation/new_docs/components/mix-fetch.tsx new file mode 100644 index 0000000000..06cbe5fed2 --- /dev/null +++ b/documentation/new_docs/components/mix-fetch.tsx @@ -0,0 +1,82 @@ +import React, { useState } from 'react'; +import CircularProgress from '@mui/material/CircularProgress'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import Box from '@mui/material/Box'; +import { mixFetch } from '@nymproject/mix-fetch-full-fat'; +import Stack from '@mui/material/Stack'; +import Paper from '@mui/material/Paper'; +import type { SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat'; + +const defaultUrl = 'https://nymtech.net/favicon.svg'; +const args = { mode: 'unsafe-ignore-cors' }; + +const mixFetchOptions: SetupMixFetchOps = { + preferredGateway: '6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', // with WSS + preferredNetworkRequester: + '8rRGWy54oC8drFL9DepMegBt2DLrsqQwCoHMXt9nsnTo.2XjCPVbb4FpQ9hNRcXwb9mTzEAVVk1zf1tcch3wdtNEA@6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', + mixFetchOverride: { + requestTimeoutMs: 60_000, + }, + forceTls: true, // force WSS + extra: {}, +}; + +export const MixFetch = () => { + const [url, setUrl] = useState(defaultUrl); + const [html, setHtml] = useState(); + const [busy, setBusy] = useState(false); + + const handleFetch = async () => { + try { + setBusy(true); + setHtml(undefined); + const response = await mixFetch(url, args, mixFetchOptions); + console.log(response); + const resHtml = await response.text(); + setHtml(resHtml); + } catch (err) { + console.log(err); + } finally { + setBusy(false); + } + }; + + return ( +
+ + setUrl(e.target.value)} + /> + + + + {busy && ( + + + + )} + {html && ( + <> + + Response + + + + {html} + + + + )} +
+ ); +}; diff --git a/documentation/new_docs/components/mixnodes.tsx b/documentation/new_docs/components/mixnodes.tsx new file mode 100644 index 0000000000..461568bdd2 --- /dev/null +++ b/documentation/new_docs/components/mixnodes.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react'; +import { contracts } from '@nymproject/contract-clients'; +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import Box from '@mui/material/Box'; +import CircularProgress from '@mui/material/CircularProgress'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; +import Table from '@mui/material/Table'; +import { TableBody, TableCell, TableHead, TableRow } from '@mui/material'; +import { settings } from './client'; + +const getClient = async () => { + const cosmWasmClient = await SigningCosmWasmClient.connect(settings.url); + + const client = new contracts.Mixnet.MixnetQueryClient(cosmWasmClient, settings.mixnetContractAddress); + return client; +}; + +export const Mixnodes = () => { + const [mixnodes, setMixnodes] = useState(); + const [busy, setBusy] = useState(false); + + const getMixnodes = async () => { + setBusy(true); + const client = await getClient(); + const { nodes } = await client.getMixNodesDetailed({}); + + setMixnodes(nodes); + setBusy(false); + }; + + if (busy) { + return ( + + + + Loading... + + + ); + } + + if (!mixnodes) { + return ( + + + + ); + } + + return ( + + {mixnodes?.length && ( + + + + MixId + Owner Account + Layer + Bonded at Block Height + + + + {mixnodes.map((mixnode: any) => ( + + {mixnode.bond_information.mix_id} + {mixnode.bond_information.owner} + {mixnode.bond_information.layer} + {mixnode.bond_information.bonding_height} + + ))} + +
+ )} +
+ ); +}; diff --git a/documentation/new_docs/components/npm.tsx b/documentation/new_docs/components/npm.tsx new file mode 100644 index 0000000000..90a4a78955 --- /dev/null +++ b/documentation/new_docs/components/npm.tsx @@ -0,0 +1,17 @@ +import React, { FC } from 'react'; +import { Chip, Link } from '@mui/material'; + +export const NPMLink: FC<{ packageName: string; kind: 'esm' | 'cjs'; preBundled?: boolean }> = ({ + packageName, + kind, + preBundled, +}) => ( + + {packageName} {' '} + {preBundled && } + +); diff --git a/documentation/new_docs/components/traffic.tsx b/documentation/new_docs/components/traffic.tsx new file mode 100644 index 0000000000..07b00355ec --- /dev/null +++ b/documentation/new_docs/components/traffic.tsx @@ -0,0 +1,113 @@ +import React, { useEffect, useState } from 'react'; +import { createNymMixnetClient, NymMixnetClient, Payload } from '@nymproject/sdk-full-fat'; +import Box from '@mui/material/Box'; +import CircularProgress from '@mui/material/CircularProgress'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; + +const nymApiUrl = 'https://validator.nymtech.net/api'; + +export const Traffic = () => { + const [nym, setNym] = useState(); + const [selfAddress, setSelfAddress] = useState(); + const [recipient, setRecipient] = useState(); + const [payload, setPayload] = useState(); + const [receivedMessage, setReceivedMessage] = useState(); + const [buttonEnabled, setButtonEnabled] = useState(false); + + const init = async () => { + const client = await createNymMixnetClient(); + setNym(client); + + // start the client and connect to a gateway + await client?.client.start({ + clientId: crypto.randomUUID(), + nymApiUrl, + forceTls: true, // force WSS + }); + + // check when is connected and set the self address + client?.events.subscribeToConnected((e) => { + const { address } = e.args; + setSelfAddress(address); + }); + + // show whether the client is ready or not + client?.events.subscribeToLoaded((e) => { + console.log('Client ready: ', e.args); + }); + + // show message payload content when received + client?.events.subscribeToTextMessageReceivedEvent((e) => { + console.log(e.args.payload); + setReceivedMessage(e.args.payload); + }); + }; + + const stop = async () => { + await nym?.client.stop(); + }; + + const send = () => payload && recipient && nym?.client.send({ payload, recipient }); + + useEffect(() => { + init(); + return () => { + stop(); + }; + }, []); + + useEffect(() => { + if (recipient && payload) { + setButtonEnabled(true); + } else { + setButtonEnabled(false); + } + }, [recipient, payload]); + + if (!nym || !selfAddress) { + return ( + + + + ); + } + + return ( + + + + My self address is: + {selfAddress || 'loading'} + Communication through the Mixnet + setRecipient(e.target.value)} + size="small" + /> + setPayload({ message: e.target.value, mimeType: 'text/plain' })} + size="small" + /> + + + {receivedMessage && ( + + Message Received! + {receivedMessage} + + )} + + + ); +}; diff --git a/documentation/new_docs/components/wallet/connect.tsx b/documentation/new_docs/components/wallet/connect.tsx new file mode 100644 index 0000000000..9b515646c9 --- /dev/null +++ b/documentation/new_docs/components/wallet/connect.tsx @@ -0,0 +1,67 @@ +import React, { useState, useEffect } from 'react'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import TextField from '@mui/material/TextField'; +import { useWalletContext } from './utils/wallet.context'; + +export const ConnectWallet = () => { + const { connect, balance, balanceLoading, accountLoading, account, clientsAreLoading } = useWalletContext(); + + const [mnemonic, setMnemonic] = useState(); + const [connectButtonText, setConnectButtonText] = useState('Connect'); + + useEffect(() => { + if (accountLoading || clientsAreLoading || balanceLoading) { + setConnectButtonText('Loading...'); + } else if (balance) { + setConnectButtonText('Connected'); + } + setConnectButtonText('Connect'); + }, [accountLoading, clientsAreLoading, balanceLoading]); + + return ( + + + Connect to your testnet account + + + Your testnet account: + + + Enter the mnemonic + + setMnemonic(e.target.value)} + fullWidth + multiline + maxRows={4} + sx={{ marginBottom: 3 }} + /> + + + {account && balance ? ( + + Address: {account} + + Balance: {balance?.amount} {balance?.denom} + + + ) : ( + + Please, enter your mnemonic to receive your account information + + )} + + + ); +}; diff --git a/documentation/new_docs/components/wallet/delegations.tsx b/documentation/new_docs/components/wallet/delegations.tsx new file mode 100644 index 0000000000..cfac94ea2a --- /dev/null +++ b/documentation/new_docs/components/wallet/delegations.tsx @@ -0,0 +1,129 @@ +import React, { useEffect, useState } from 'react'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import TextField from '@mui/material/TextField'; +import Alert from '@mui/material/Alert'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import { useWalletContext } from './utils/wallet.context'; + +export const Delegations = () => { + const { delegations, doDelegate, delegationLoader, unDelegateAll, unDelegateAllLoading, log } = useWalletContext(); + + const [delegationNodeId, setDelegationNodeId] = useState(); + const [amountToBeDelegated, setAmountToBeDelegated] = useState(); + const [infoText, setInfoText] = useState(''); + + const cleanFields = () => { + setDelegationNodeId(''); + setAmountToBeDelegated(''); + setInfoText(''); + }; + + useEffect( + () => () => { + cleanFields(); + }, + [], + ); + + return ( + + + + Delegations + + + + Make a delegation + + setDelegationNodeId(e.target.value)} + size="small" + /> + + setAmountToBeDelegated(e.target.value)} + size="small" + /> + + + + + + Your delegations: + + {!delegations?.delegations?.length ? ( + You do not have delegations + ) : ( + + + + + MixId + Owner + Amount + Cumulative Reward Ratio + + + + {delegations?.delegations.map((delegation: any) => ( + + {delegation.mix_id} + {delegation.owner} + {delegation.amount.amount} + {delegation.cumulative_reward_ratio} + + ))} + +
+
+ )} +
+ {delegations?.delegations.length > 0 && ( + + + + )} + + {infoText && {infoText}} +
+
+
+ {log?.node?.length > 0 && log.type === 'delegate' && ( + + Transaction Logs: + {log.node} + + )} +
+ ); +}; diff --git a/documentation/new_docs/components/wallet/sendTokens.tsx b/documentation/new_docs/components/wallet/sendTokens.tsx new file mode 100644 index 0000000000..11ce8fa0c7 --- /dev/null +++ b/documentation/new_docs/components/wallet/sendTokens.tsx @@ -0,0 +1,69 @@ +import React, { useState, useEffect } from 'react'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import TextField from '@mui/material/TextField'; +import { useWalletContext } from './utils/wallet.context'; + +export const SendTokes = () => { + const { sendingTokensLoading, sendTokens, log } = useWalletContext(); + + const [recipientAddress, setRecipientAddress] = useState(); + const [tokensToSend, setTokensToSend] = useState(); + + const cleanFields = () => { + setRecipientAddress(''); + setTokensToSend(''); + }; + + useEffect( + () => () => { + cleanFields(); + }, + [], + ); + + return ( + + + + Send Tokens + + setRecipientAddress(e.target.value)} + size="small" + /> + + setTokensToSend(e.target.value)} + size="small" + /> + + + + + + + {log?.node?.length > 0 && log.type === 'sendTokens' && ( + + Transaction Logs: + {log.node} + + )} + + ); +}; diff --git a/documentation/new_docs/components/wallet/utils/wallet.context.tsx b/documentation/new_docs/components/wallet/utils/wallet.context.tsx new file mode 100644 index 0000000000..30789ed3f6 --- /dev/null +++ b/documentation/new_docs/components/wallet/utils/wallet.context.tsx @@ -0,0 +1,262 @@ +import React, { createContext, useContext, useState, useCallback, useEffect, useMemo } from 'react'; +import { Coin } from '@cosmjs/stargate'; +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { settings } from '../../client'; +import { signerAccount, fetchSignerCosmosWasmClient, fetchSignerClient } from './wallet.methods'; + +/** + * This context provides the state for wallet. + */ + +interface WalletState { + accountLoading: boolean; + account: string; + clientsAreLoading: boolean; + connect?: (mnemonic: string) => void; + balance?: Coin; + balanceLoading: boolean; + setRecipientAddress?: (value: string) => void; + setTokensToSend?: (value: string) => void; + sendingTokensLoading: boolean; + log?: { type: 'delegate' | 'sendTokens'; node: React.ReactNode[] }; + sendTokens?: (recipientAddress: string, tokensToSend: string) => void; + delegations?: any; + doDelegate?: (mixId: string, amount: string) => void; + delegationLoader?: boolean; + unDelegateAll?: () => void; + unDelegateAllLoading?: boolean; +} + +export const WalletContext = createContext({ + accountLoading: false, + account: '', + clientsAreLoading: false, + balanceLoading: false, + sendingTokensLoading: false, +}); + +export const useWalletContext = (): React.ContextType => useContext(WalletContext); + +export const WalletContextProvider = ({ children }: { children: JSX.Element }) => { + const [cosmWasmSignerClient, setCosmWasmSignerClient] = useState(null); + const [nymWasmSignerClient, setNymWasmSignerClient] = useState(null); + const [account, setAccount] = useState(''); + const [accountLoading, setAccountLoading] = useState(false); + const [delegations, setDelegations] = useState<{ delegations: any[]; start_next_after: any }>(); + const [clientsAreLoading, setClientsAreLoading] = useState(false); + const [balance, setBalance] = useState(null); + const [balanceLoading, setBalanceLoading] = useState(false); + const [sendingTokensLoading, setSendingTokensLoading] = useState(false); + const [log, setLog] = useState<{ type: 'delegate' | 'sendTokens'; node: React.ReactNode[] }>(); + const [delegationLoader, setDelegationLoader] = useState(false); + const [unDelegateAllLoading, setUnDelegateAllLoading] = useState(false); + + const Reset = () => { + setAccountLoading(false); + setDelegations(null); + setClientsAreLoading(false); + setBalance(null); + setBalanceLoading(false); + setSendingTokensLoading(false); + }; + + const getSignerAccount = async (mnemonic: string) => { + setAccountLoading(true); + try { + const signer = await signerAccount(mnemonic); + const accounts = await signer.getAccounts(); + if (accounts[0]) { + setAccount(accounts[0].address); + } + } catch (error) { + console.error(error); + } + setAccountLoading(false); + }; + + const getClients = async (mnemonic: string) => { + setClientsAreLoading(true); + try { + setCosmWasmSignerClient(await fetchSignerCosmosWasmClient(mnemonic)); + setNymWasmSignerClient(await fetchSignerClient(mnemonic)); + } catch (error) { + console.error(error); + } + setClientsAreLoading(false); + }; + + const connect = async (mnemonic: string) => { + getSignerAccount(mnemonic); + getClients(mnemonic); + }; + + const getBalance = useCallback(async () => { + setBalanceLoading(true); + try { + const newBalance = await cosmWasmSignerClient?.getBalance(account, 'unym'); + setBalance(newBalance); + } catch (error) { + console.error(error); + } + setBalanceLoading(false); + }, [account, cosmWasmSignerClient]); + + const getDelegations = useCallback(async () => { + const delegationsReceived = await nymWasmSignerClient.getDelegatorDelegations({ + delegator: settings.address, + }); + setDelegations(delegationsReceived); + }, [nymWasmSignerClient]); + + const sendTokens = async (recipientAddress: string, tokensToSend: string) => { + const memo: string = 'test sending tokens'; + setSendingTokensLoading(true); + try { + const res = await cosmWasmSignerClient.sendTokens( + account, + recipientAddress, + [{ amount: tokensToSend, denom: 'unym' }], + 'auto', + memo, + ); + setLog({ + type: 'sendTokens', + node: [ +
+ {new Date().toLocaleTimeString()} +
{JSON.stringify(res, null, 2)}
+
, + ], + }); + } catch (error) { + console.error(error); + } + setSendingTokensLoading(false); + }; + + const doDelegate = async (mixId: string, amount: string) => { + setDelegationLoader(true); + const memo: string = 'test delegation'; + const coinAmount: Coin = { amount, denom: 'unym' }; + try { + const res = await nymWasmSignerClient.delegateToMixnode({ mixId: parseInt(mixId, 10) }, 'auto', memo, [ + coinAmount, + ]); + setLog({ + type: 'delegate', + node: [ +
+ {new Date().toLocaleTimeString()} +
{JSON.stringify(res, null, 2)}
+
, + ], + }); + } catch (error) { + console.error(error); + } + setDelegationLoader(false); + }; + + const unDelegateAll = async () => { + setUnDelegateAllLoading(true); + try { + const logs: React.ReactNode[] = []; + // eslint-disable-next-line no-restricted-syntax + for (const delegation of delegations.delegations) { + // eslint-disable-next-line no-await-in-loop + const res = await nymWasmSignerClient.undelegateFromMixnode({ mixId: delegation.mix_id }, 'auto'); + setUnDelegateAllLoading(false); + logs.push( +
+ {new Date().toLocaleTimeString()} +
{JSON.stringify(res, null, 2)}
+
, + ); + } + setLog({ + type: 'delegate', + node: logs, + }); + } catch (error) { + console.error(error); + setUnDelegateAllLoading(false); + } + }; + + // const withdrawRewards = async () => { + // const validatorAdress = ''; + // const memo = 'test withdraw rewards'; + // setWithdrawLoading(true); + // try { + // const res = await cosmWasmSignerClient.withdrawRewards(account, validatorAdress, 'auto', memo); + // setLog({ + // type: 'delegate', + // node: [ + //
+ // {new Date().toLocaleTimeString()} + //
{JSON.stringify(res, null, 2)}
+ //
, + // ], + // }); + // } catch (error) { + // console.error(error); + // } + // setWithdrawLoading(false); + // }; + + useEffect( + () => () => { + Reset(); + }, + [], + ); + + useEffect(() => { + if (cosmWasmSignerClient) { + getBalance(); + } + }, [cosmWasmSignerClient]); + + useEffect(() => { + if (nymWasmSignerClient) { + getDelegations(); + } + }, [nymWasmSignerClient]); + + const state = useMemo( + () => ({ + accountLoading, + account, + clientsAreLoading, + connect, + balance, + balanceLoading, + sendingTokensLoading, + log, + sendTokens, + delegations, + doDelegate, + delegationLoader, + unDelegateAll, + unDelegateAllLoading, + }), + [ + accountLoading, + account, + clientsAreLoading, + connect, + balance, + balanceLoading, + sendingTokensLoading, + log, + sendTokens, + delegations, + doDelegate, + delegationLoader, + unDelegateAll, + unDelegateAllLoading, + ], + ); + + return {children}; +}; diff --git a/documentation/new_docs/components/wallet/utils/wallet.methods.ts b/documentation/new_docs/components/wallet/utils/wallet.methods.ts new file mode 100644 index 0000000000..4a496b0646 --- /dev/null +++ b/documentation/new_docs/components/wallet/utils/wallet.methods.ts @@ -0,0 +1,50 @@ +import { contracts } from '@nymproject/contract-clients'; +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; +import { GasPrice } from '@cosmjs/stargate'; +import { settings } from '../../client'; + +export const signerAccount = async (mnemonic: string) => { + // create a wallet to sign transactions with the mnemonic + const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { + prefix: 'n', + }); + + return signer; +}; + +export const fetchSignerCosmosWasmClient = async (mnemonic: string) => { + const signer = await signerAccount(mnemonic); + + // create a signing client we don't need to set the gas price conversion for queries + const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, { + gasPrice: GasPrice.fromString('0.025unym'), + }); + + return cosmWasmClient; +}; + +export const fetchSignerClient = async (mnemonic: string) => { + const signer = await signerAccount(mnemonic); + + // create a signing client we don't need to set the gas price conversion for queries + // if you want to connect without signer you'd write ".connect" and "url" as param + const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, { + gasPrice: GasPrice.fromString('0.025unym'), + }); + + /** create a mixnet contract client + * @param cosmWasmClient the client to use for signing and querying + * @param settings.address the bech32 address prefix (human readable part) + * @param settings.mixnetContractAddress the bech32 address prefix (human readable part) + * @returns the client in MixnetClient form + */ + + const mixnetClient = new contracts.Mixnet.MixnetClient( + cosmWasmClient, + settings.address, // sender (that account of the signer) + settings.mixnetContractAddress, // contract address (different on mainnet, QA, etc) + ); + + return mixnetClient; +}; diff --git a/documentation/new_docs/next-env.d.ts b/documentation/new_docs/next-env.d.ts new file mode 100644 index 0000000000..4f11a03dc6 --- /dev/null +++ b/documentation/new_docs/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/documentation/new_docs/next.config.js b/documentation/new_docs/next.config.js new file mode 100644 index 0000000000..cbb3bc7364 --- /dev/null +++ b/documentation/new_docs/next.config.js @@ -0,0 +1,50 @@ +// const path = require('path'); +// const CopyPlugin = require('copy-webpack-plugin'); + +const withNextra = require('nextra')({ + theme: 'nextra-theme-docs', + themeConfig: './theme.config.tsx', +}); + +const nextra = withNextra(); +nextra.webpack = (config, options) => { + // generate Nextra's webpack config + const newConfig = withNextra().webpack(config, options); + + newConfig.module.rules.push({ + test: /\.txt$/i, + use: 'raw-loader', + }); + + // TODO: figure out how to properly bundle WASM and workers with Nextra + // newConfig.plugins.push( + // new CopyPlugin({ + // patterns: [ + // { + // from: path.resolve(path.dirname(require.resolve('@nymproject/mix-fetch/package.json')), '*.wasm'), + // to: '[name][ext]', + // context: path.resolve(__dirname, 'out'), + // }, + // { + // from: path.resolve(path.dirname(require.resolve('@nymproject/mix-fetch/package.json')), '*worker*.js'), + // to: '[name][ext]', + // context: path.resolve(__dirname, 'out'), + // }, + // ], + // }), + // ); + + return newConfig; +}; + +const config = { + ...nextra, + // output: 'export', // static HTML files, has problems with Vercel + // rewrites: undefined, + images: { + unoptimized: true, + }, + transpilePackages: ['@nymproject/contract-clients'], +}; + +module.exports = config; diff --git a/documentation/new_docs/package.json b/documentation/new_docs/package.json new file mode 100644 index 0000000000..abdf67048a --- /dev/null +++ b/documentation/new_docs/package.json @@ -0,0 +1,54 @@ +{ + "name": "@nymproject/ts-sdk-docs", + "version": "1.3.0-rc.0", + "description": "Nym Typescript SDK Docs", + "license": "Apache-2.0", + "author": "Nym Technologies SA", + "scripts": { + "build": "next build", + "dev": "next dev", + "lint": "next lint", + "lint:fix": "next lint --fix", + "start": "next start" + }, + "dependencies": { + "@cosmjs/amino": "^0.32.2", + "@cosmjs/cosmwasm-launchpad": "^0.25.6", + "@cosmjs/cosmwasm-stargate": "^0.32.2", + "@cosmjs/encoding": "^0.32.2", + "@cosmjs/proto-signing": "^0.32.2", + "@cosmjs/stargate": "^0.32.2", + "@cosmos-kit/core": "^2.8.9", + "@cosmos-kit/keplr": "^2.6.9", + "@cosmos-kit/keplr-extension": "^2.7.9", + "@cosmos-kit/ledger": "^2.6.9", + "@cosmos-kit/react": "^2.10.11", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@interchain-ui/react": "^1.8.0", + "@mui/icons-material": "^5.14.9", + "@mui/lab": "^5.0.0-alpha.145", + "@mui/material": "^5.14.8", + "@nymproject/contract-clients": ">=1.2.4-rc.2 || ^1", + "@nymproject/mix-fetch-full-fat": ">=1.2.4-rc.2 || ^1", + "@nymproject/sdk-full-fat": ">=1.2.4-rc.2 || ^1", + "chain-registry": "^1.19.0", + "cosmjs-types": "^0.9.0", + "next": "^13.4.19", + "nextra": "latest", + "nextra-theme-docs": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "save": "^2.9.0", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@types/node": "18.11.10", + "copy-webpack-plugin": "^11.0.0", + "eslint": "8.46.0", + "eslint-config-next": "13.4.13", + "raw-loader": "^4.0.2", + "typescript": "^4.9.3" + }, + "private": false +} diff --git a/documentation/new_docs/pages/_app.tsx b/documentation/new_docs/pages/_app.tsx new file mode 100644 index 0000000000..b3fbeaf252 --- /dev/null +++ b/documentation/new_docs/pages/_app.tsx @@ -0,0 +1,27 @@ +import React, { useMemo } from 'react'; +import type { AppProps } from 'next/app'; +import './styles.css'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; + +const MyApp: React.FC = ({ Component, pageProps }) => { + const muiTheme = useMemo( + () => + createTheme({ + palette: { + mode: 'dark', + primary: { + main: '#e67300', + }, + }, + }), + [], + ); + const AnyComponent = Component as any; + return ( + + + + ); +}; + +export default MyApp; diff --git a/documentation/new_docs/pages/_meta.json b/documentation/new_docs/pages/_meta.json new file mode 100644 index 0000000000..7d98da9029 --- /dev/null +++ b/documentation/new_docs/pages/_meta.json @@ -0,0 +1,18 @@ +{ + "sdk": { + "title": "SDKs", + "type": "page" + }, + "developers": { + "title": "Developers", + "type": "page" + }, + "network": { + "title": "Network", + "type": "page" + }, + "operators": { + "title": "Operators", + "type": "page" + } +} diff --git a/documentation/new_docs/pages/index.mdx b/documentation/new_docs/pages/index.mdx new file mode 100644 index 0000000000..ed01413f76 --- /dev/null +++ b/documentation/new_docs/pages/index.mdx @@ -0,0 +1 @@ +This should be the landing page with w/ever nav we want diff --git a/documentation/new_docs/pages/sdk/_meta.json b/documentation/new_docs/pages/sdk/_meta.json new file mode 100644 index 0000000000..d3b53702c4 --- /dev/null +++ b/documentation/new_docs/pages/sdk/_meta.json @@ -0,0 +1,4 @@ +{ + "typescript": "Typescript", + "rust": "Rust" +} diff --git a/documentation/new_docs/pages/sdk/rust/_meta.json b/documentation/new_docs/pages/sdk/rust/_meta.json new file mode 100644 index 0000000000..eb62a04bb3 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/_meta.json @@ -0,0 +1,7 @@ +{ + "index": "Introduction", + "message-helpers": "Message Helpers", + "message-types": "Message Types", + "examples": "Examples", + "troubleshooting": "Troubleshooting" +} diff --git a/documentation/new_docs/pages/sdk/rust/examples.md b/documentation/new_docs/pages/sdk/rust/examples.md new file mode 100644 index 0000000000..648c32fa0d --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples.md @@ -0,0 +1,12 @@ +# Examples + +All the following examples can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/master/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there with: + +```sh +cargo run --example +``` + +If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates. + +An example `Cargo.toml` file can be found [here](examples/cargo.md). + diff --git a/documentation/new_docs/pages/sdk/rust/examples/cargo.md b/documentation/new_docs/pages/sdk/rust/examples/cargo.md new file mode 100644 index 0000000000..e425e124ed --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/cargo.md @@ -0,0 +1,35 @@ +# Example Cargo File +This file imports the basic requirements for running these pieces of example code, and can be used as the basis for your own cargo project. + +```toml +[package] +name = "your_app" +version = "x.y.z" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Async runtime +tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } +# Used for (de)serialising incoming and outgoing messages +serde = "1.0.152" +serde_json = "1.0.91" +# Nym clients, addressing, etc +nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" } +nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", branch = "master" } +nym-bin-common = { git = "https://github.com/nymtech/nym", branch = "master" } +nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", branch = "master" } +# Additional dependencies if you're interacting with Nyx or another Cosmos SDK blockchain +cosmrs = "=0.14.0" +nym-validator-client = { git = "https://github.com/nymtech/nym", branch = "master" } + +# If you're building an app with a client and server / serivce this might be a useful structure for your repo +[[bin]] +name = "client" +path = "bin/client.rs" + +[[bin]] +name = "service" +path = "bin/service.rs" +``` \ No newline at end of file diff --git a/documentation/new_docs/pages/sdk/rust/examples/credential.md b/documentation/new_docs/pages/sdk/rust/examples/credential.md new file mode 100644 index 0000000000..f29d6748ba --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/credential.md @@ -0,0 +1,8 @@ +# Coconut credential generation +The following code shows how you can use the SDK to create and use a credential representing paid bandwidth on the Sandbox testnet. + +```rust,noplayground +{{#include ../../../../../../sdk/rust/nym-sdk/examples/bandwidth.rs}} +``` + +You can read more about Coconut credentials (also referred to as `zk-Nym`) [here](https://nymtech.net/docs/coconut.html). diff --git a/documentation/new_docs/pages/sdk/rust/examples/custom-network.md b/documentation/new_docs/pages/sdk/rust/examples/custom-network.md new file mode 100644 index 0000000000..ae0f145a3b --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/custom-network.md @@ -0,0 +1,18 @@ +# Importing and using a custom network topology +If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html) (`examples/custom_topology_provider.rs`). + +There are two ways to do this: + +## Import a custom Nym API endpoint +If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood): + +```rust,noplayground +{{#include ../../../../../../sdk/rust/nym-sdk/examples/custom_topology_provider.rs}} +``` + +## Import a specific topology manually +If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually: + +```rust,noplayground +{{#include ../../../../../../sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs}} +``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/keys.md b/documentation/new_docs/pages/sdk/rust/examples/keys.md new file mode 100644 index 0000000000..84746dcfd6 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/keys.md @@ -0,0 +1,28 @@ +# Key Creation and Use +The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys (`examples/builder_with_storage`): + +```rust,noplayground +{{#include ../../../../../../sdk/rust/nym-sdk/examples/builder_with_storage.rs}} +``` + +As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple. + +Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated: +``` +$ tree /tmp/mixnet-client + +mixnet-client +├── ack_key.pem +├── db.sqlite +├── db.sqlite-shm +├── db.sqlite-wal +├── gateway_details.json +├── gateway_shared.pem +├── persistent_reply_store.sqlite +├── private_encryption.pem +├── private_identity.pem +├── public_encryption.pem +└── public_identity.pem + +1 directory, 11 files +``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/simple.md b/documentation/new_docs/pages/sdk/rust/examples/simple.md new file mode 100644 index 0000000000..20872ce96b --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/simple.md @@ -0,0 +1,8 @@ +# Simple Send +Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code (`examples/simple.rs`). + +Simply importing the `nym_sdk` crate into your project allows you to create a client and send traffic through the mixnet. + +```rust,noplayground +{{#include ../../../../../../sdk/rust/nym-sdk/examples/simple.rs}} +``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/socks.md b/documentation/new_docs/pages/sdk/rust/examples/socks.md new file mode 100644 index 0000000000..de027e9011 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/socks.md @@ -0,0 +1,10 @@ +# Socks Proxy +There is also the option to embed the [`socks5-client`](../../../clients/socks5-client.md) into your app code (`examples/socks5.rs`): + +```admonish info +If you are looking at implementing Nym as a transport layer for a crypto wallet or desktop app, this is probably the best place to start if they can speak SOCKS5, 4a, or 4. +``` + +```rust,noplayground +{{#include ../../../../../../sdk/rust/nym-sdk/examples/socks5.rs}} +``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/split-send.md b/documentation/new_docs/pages/sdk/rust/examples/split-send.md new file mode 100644 index 0000000000..6b7cf69789 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/split-send.md @@ -0,0 +1,6 @@ +# Send and Receive in Different Tasks +If you need to split the different actions of your client across different tasks, you can do so like this: + +```rust, noplayground +{{#include ../../../../../../sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs}} +``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/storage.md b/documentation/new_docs/pages/sdk/rust/examples/storage.md new file mode 100644 index 0000000000..bc68bca9ff --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/storage.md @@ -0,0 +1,6 @@ +# Manually Handled Storage +If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform the actions taken automatically above (`examples/manually_handle_keys_and_config.rs`) + +```rust,noplayground +{{#include ../../../../../../sdk/rust/nym-sdk/examples/manually_handle_storage.rs}} +``` diff --git a/documentation/new_docs/pages/sdk/rust/examples/surbs.md b/documentation/new_docs/pages/sdk/rust/examples/surbs.md new file mode 100644 index 0000000000..d3b6b2da1b --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/examples/surbs.md @@ -0,0 +1,10 @@ +# Anonymous Replies with SURBs (Single Use Reply Blocks) +Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default. + +You can read more about how SURBs function under the hood [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs). + +In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to: + +```rust,noplayground +{{#include ../../../../../../sdk/rust/nym-sdk/examples/surb_reply.rs}} +``` diff --git a/documentation/new_docs/pages/sdk/rust/index.md b/documentation/new_docs/pages/sdk/rust/index.md new file mode 100644 index 0000000000..2e65f1f869 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/index.md @@ -0,0 +1,46 @@ +# Introduction +The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a separate process on their machine. This makes both developing and running applications much easier, reducing complexity in the development process (not having to restart another client in a separate console window/tab) and being able to have a single binary for other people to use. + +Currently developers can use the Rust SDK to import either websocket client ([`nym-client`](../../clients/websocket-client.md)) or [`socks-client`](../../clients/socks5-client.md) functionality into their Rust code. + +In the future the SDK will be made up of several components, each of which will allow developers to interact with different parts of Nym infrastructure. + +| Component | Functionality | Released | +|-----------|---------------------------------------------------------------------------------------|----------| +| Mixnet | Create / load clients & keypairs, subscribe to Mixnet events, send & receive messages | ✔️ | +| Coconut | Create & verify Coconut credentials | 🛠️ | +| Validator | Sign & broadcast Nyx blockchain transactions, query the blockchain | ❌ | + +The `mixnet` component currently exposes the logic of two clients: the [websocket client](../../clients/websocket-client.md), and the [socks](../../clients/socks5-client.md) client. + +The `coconut` component is currently being worked on. Right now it exposes logic allowing for the creation of coconut credentials on the Sandbox testnet. + +### Development status +The SDK is still somewhat a work in progress: interfaces are fairly stable but still may change in subsequent releases. + +### Installation +The `nym-sdk` crate is **not yet available via [crates.io](https://crates.io)**. As such, in order to import the crate you must specify the Nym monorepo in your `Cargo.toml` file: + +```toml +nym-sdk = { git = "https://github.com/nymtech/nym" } +``` + +By default the above command will import the current `HEAD` of the default branch, which in our case is `develop`. Assuming instead you wish to pull in another branch (e.g. `master` or a particular release) you can specify this like so: + +```toml +# importing HEAD of master branch +nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" } +# importing HEAD of the third release of 2023, codename 'kinder' +nym-sdk = { git = "https://github.com/nymtech/nym", branch = "release/2023.3-kinder" } +``` + +You can also define a particular git commit to use as your import like so: + +```toml +nym-sdk = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" } +``` + +Since the `HEAD` of `master` is always the most recent release, we recommend developers use that for their imports, unless they have a reason to pull in a specific historic version of the code. + +### Generate Crate Docs +In order to generate the crate docs run `cargo doc --open` from `nym/sdk/rust/nym-sdk/` diff --git a/documentation/new_docs/pages/sdk/rust/message-helpers.md b/documentation/new_docs/pages/sdk/rust/message-helpers.md new file mode 100644 index 0000000000..e7ce201fa7 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/message-helpers.md @@ -0,0 +1,70 @@ +# Message Helpers + +## Handling incoming messages +As seen in the [Chain querier tutorial](https://github.com/nymtech/developer-tutorials/blob/0130ee5a61cd6801bdcfc84608b2a520b5392714/rust/chain-query-service/) when listening out for a response to a sent message (e.g. if you have sent a request to a service, and are awaiting the response) you will want to await [non-empty messages (if you don't know why, read the info on this here)](troubleshooting.md#client-receives-empty-messages-when-listening-for-response). This can be done with something like the helper functions [here](https://github.com/nymtech/developer-tutorials/blob/0130ee5a61cd6801bdcfc84608b2a520b5392714/rust/chain-query-service/src/lib.rs#L71): + +```rust +use nym_sdk::mixnet::ReconstructedMessage; + +pub async fn wait_for_non_empty_message( + client: &mut MixnetClient, +) -> anyhow::Result { + while let Some(mut new_message) = client.wait_for_messages().await { + if !new_message.is_empty() { + return Ok(new_message.pop().unwrap()); + } + } + + bail!("did not receive any non-empty message") +} + +pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result { + ResponseTypes::try_deserialize(message.message) +} + +// Note here that the only difference between handling a request and a response +// is that a request will have a sender_tag to parse. +// +// This is used for anonymous replies with SURBs. +pub fn handle_request( + message: ReconstructedMessage, +) -> anyhow::Result<(RequestTypes, Option)> { + let request = RequestTypes::try_deserialize(message.message)?; + Ok((request, message.sender_tag)) +} +``` + +The above helper functions are used as such by the client in tutorial example: it sends a message to the service (what the message is isn't important - just that your client has sent a message _somewhere_ and you are awaiting a response), waits for a _non_empty_ message, then handles it (then logs it - but you can do whatever you want, parse it, etc): + +```rust +// [snip] + +// Send serialised request to service via mixnet what is await-ed here is +// placing the message in the client's message queue, NOT the sending itself. +let _ = client + .send_message(sp_address, message.serialize(), Default::default()) + .await; + +// Await a non-empty message +let received = wait_for_non_empty_message(client).await?; + +// Handle the response received (the non-empty message awaited above) +let sp_response = handle_response(received)?; + +// Match JSON -> ResponseType +let res = match sp_response { + crate::ResponseTypes::Balance(response) => { + println!("{:#?}", response); + response.balance + } +}; + +// [snip] +``` +([repo code on Github here](https://github.com/nymtech/developer-tutorials/blob/0130ee5a61cd6801bdcfc84608b2a520b5392714/rust/chain-query-service/src/client.rs#L19)) + +## Iterating over incoming messages +It is recommended to use `nym_client.next().await` over `nym_client.wait_for_messages().await` as the latter will return one message at a time which will probably be easier to deal with. See the [parallel send and receive example](https://github.com/nymtech/nym/blob/2993e85c7a17bd5b68171751a48b731b2394ee03/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs#L23-L25) for an example. + +## Remember to disconnect your client +You should always **manually disconnect your client** with `client.disconnect().await` as seen in the code examples. This is important as your client is writing to a local DB and dealing with SURB storage. diff --git a/documentation/new_docs/pages/sdk/rust/message-types.md b/documentation/new_docs/pages/sdk/rust/message-types.md new file mode 100644 index 0000000000..c5adf83377 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/message-types.md @@ -0,0 +1,5 @@ +# Message Types +[//]: # (TODO expand! ) +There are two methods for sending messages through the mixnet using your client: +* `send_plain_message()` is the most simple: pass the recipient address and the message you wish to send as a string (this was previously `send_str()`). This is a nicer-to-use wrapper around `send_message()`. +* `send_message()` allows you to also define the amount of SURBs to send along with your message (which is sent as bytes). diff --git a/documentation/new_docs/pages/sdk/rust/troubleshooting.md b/documentation/new_docs/pages/sdk/rust/troubleshooting.md new file mode 100644 index 0000000000..458a4eaab4 --- /dev/null +++ b/documentation/new_docs/pages/sdk/rust/troubleshooting.md @@ -0,0 +1,115 @@ +# Troubleshooting +Below are several common issues or questions you may have. + +If you come across something that isn't explained here, [PRs are welcome](https://github.com/nymtech/nym/issues/new/choose). + +## Verbose `task client is being dropped` logging +### On client shutdown (expected) +If this is happening at the end of your code when disconnecting your client, this is fine; we just have a verbose client! When calling `client.disconnect().await` this is simply informing you that the client is shutting down. + +On client shutdown / disconnect this is to be expected - this can be seen in many of the code examples as well. We use the [`nym_bin_common::logging`](https://github.com/nymtech/nym/blob/develop/common/bin-common/src/logging/mod.rs) import to set logging in our example code. This defaults to `INFO` level. + +If you wish to quickly lower the verbosity of your client process logs when developing you can prepend your command with `RUST_LOG=`. + +If you want to run the `builder.rs` example with only `WARN` level logging and below: + +```sh +cargo run --example builder +``` + +Becomes: + +```sh +RUST_LOG=warn cargo run --example builder +``` + +You can also make the logging _more_ verbose with: + +```sh +RUST_LOG=debug cargo run --example builder +``` + +### Not on client shutdown (unexpected) +If this is happening unexpectedly then you might be shutting your client process down too early. See the [accidentally killing your client process](#accidentally-killing-your-client-process-too-early) below for possible explanations and how to fix this issue. + +[//]: # (TODO note on poisson dance and not immediately killing client process) +## Accidentally killing your client process too early +If you are seeing either of the following errors when trying to run a client, specifically sending a message, then you may be accidentally killing your client process. + +```sh + 2023-11-02T10:31:03.930Z INFO TaskClient-BaseNymClient-real_traffic_controller-ack_control-action_controller > the task client is getting dropped + 2023-11-02T10:31:04.625Z INFO TaskClient-BaseNymClient-received_messages_buffer-request_receiver > the task client is getting dropped + 2023-11-02T10:31:04.626Z DEBUG nym_client_core::client::real_messages_control::acknowledgement_control::input_message_listener > InputMessageListener: Exiting + 2023-11-02T10:31:04.626Z INFO TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener > the task client is getting dropped + 2023-11-02T10:31:04.626Z INFO TaskClient-BaseNymClient-real_traffic_controller-reply_control > the task client is getting dropped + 2023-11-02T10:31:04.626Z DEBUG nym_client_core::client::real_messages_control > The reply controller has finished execution! + 2023-11-02T10:31:04.626Z DEBUG nym_client_core::client::real_messages_control::acknowledgement_control > The input listener has finished execution! + 2023-11-02T10:31:04.626Z INFO nym_task::manager > All registered tasks succesfully shutdown +``` + +```sh + 2023-11-02T11:22:08.408Z ERROR TaskClient-BaseNymClient-topology_refresher > Assuming this means we should shutdown... + 2023-11-02T11:22:08.408Z ERROR TaskClient-BaseNymClient-mix_traffic_controller > Polling shutdown failed: channel closed + 2023-11-02T11:22:08.408Z INFO TaskClient-BaseNymClient-gateway_transceiver-child > the task client is getting dropped + 2023-11-02T11:22:08.408Z ERROR TaskClient-BaseNymClient-mix_traffic_controller > Assuming this means we should shutdown... +thread 'tokio-runtime-worker' panicked at 'action control task has died: TrySendError { kind: Disconnected }', /home/.local/share/cargo/git/checkouts/nym-fbd2f6ea2e760da9/a800cba/common/client-core/src/client/real_messages_control/message_handler.rs:634:14 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + 2023-11-02T11:22:08.477Z INFO TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener > the task client is getting dropped + 2023-11-02T11:22:08.477Z ERROR TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener > Polling shutdown failed: channel closed + 2023-11-02T11:22:08.477Z ERROR TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener > Assuming this means we should shutdown... +``` + +Using the following piece of code as an example: + +```rust +use nym_sdk::mixnet::{MixnetClient, MixnetMessageSender, Recipient}; +use clap::Parser; + +#[derive(Debug, Clone, Parser)] +enum Opts { + Client { + recipient: Recipient + } +} + +#[tokio::main] +async fn main() { + let opts: Opts = Parser::parse(); + nym_bin_common::logging::setup_logging(); + + let mut nym_client = MixnetClient::connect_new().await.expect("Could not build Nym client"); + + match opts { + Opts::Client { recipient } => { + nym_client.send_plain_message(recipient, "some message string").await.expect("send failed"); + } + } +} +``` + +This is a simplified snippet of code for sending a simple hardcoded message with the following command: + +```sh +cargo run client +``` + +You might assume that `send`-ing your message would _just work_ as `nym_client.send_plain_message()` is an async function; you might expect that the client will block until the message is actually sent into the mixnet, then shutdown. + +However, this is not true. + +**This will only block until the message is put into client's internal queue**. Therefore in the above example, the client is being shut down before the message is _actually sent to the mixnet_; after being placed in the client's internal queue, there is still work to be done under the hood, such as route encrypting the message and placing it amongst the stream of cover traffic. + +The simple solution? Make sure the program/client stays active, either by calling `sleep`, or listening out for new messages. As sending a one-shot message without listening out for a response is likely not what you'll be doing, then you will be then awaiting a response (see the [message helpers page](message-helpers.md) for an example of this). + +Furthermore, you should always **manually disconnect your client** with `client.disconnect().await` as seen in the code examples. This is important as your client is writing to a local DB and dealing with SURB storage. + +## Client receives empty messages when listening for response +If you are sending out a message, it makes sense for your client to then listen out for incoming messages; this would probably be the reply you get from the service you've sent a message to. + +You might however be receiving messages without data attached to them / empty payloads. This is most likely because your client is receiving a message containing a [SURB request](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs) - a SURB requesting more SURB packets to be sent to the service, in order for them to have enough packets (with a big enough overall payload) to split the entire response to your initial request across. + +Whether the `data` of a SURB request being empty is a feature or a bug is to be decided - there is some discussion surrounding whether we can use SURB requests to send additional data to streamline the process of sending large replies across the mixnet. + +You can find a few helper functions [here](message-helpers.md) to help deal with this issue in the meantime. + +> If you can think of a more succinct or different way of handling this do reach out - we're happy to hear other opinions \ No newline at end of file diff --git a/documentation/new_docs/pages/sdk/typescript/FAQ/_meta.json b/documentation/new_docs/pages/sdk/typescript/FAQ/_meta.json new file mode 100644 index 0000000000..767f1ddbdd --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/FAQ/_meta.json @@ -0,0 +1,4 @@ +{ + "general": "General FAQ" + } + \ No newline at end of file diff --git a/documentation/new_docs/pages/sdk/typescript/FAQ/general.mdx b/documentation/new_docs/pages/sdk/typescript/FAQ/general.mdx new file mode 100644 index 0000000000..9067733a24 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/FAQ/general.mdx @@ -0,0 +1,74 @@ +# Welcome to the TS SDK FAQ! + +## How can I interact with Nym? + +#### For existing projects: +If you would like to integrate parts of the Nym stack to your existing app, please check out the dedicated [integrations page](../FAQ/integrations). + +#### For builders: +###### SDKs +If you’re looking to build or ‘Nymify’ existing solutions, read on: For developing in Rust or TS/JS, then the Nym SDKs are your go-to. Please visit the [Rust SDK documentation](https://nymtech.net/developers/tutorials/rust-sdk.html) for more Rust-related information and tutorials. +Stay on this page, the [TS SDK handbook](../) (you are here) for using the TypeScript SDK. +These SDKs abstract away much of the messaging and core logic from your app, and allow you to run a Nym client as part of your application process, instead of having to run them separately. In short, they simplify building Nym clients into your project. + +###### Standalone Nym clients: Websocket, WebAssembly, SOCKS5 +Alternatively, you can also use one of the three standalone Nym clients to connect your application to the mixnet. +These clients do the majority of the heavy-lifting with regards to cryptographic operations and routing under the hood. +Essentially, they all do the same thing: create a connection to a gateway, encrypt and decrypt packets sent to and received from the mixnet, and send cover traffic to hide the flow of actual app traffic from observers. You can learn more about the Nym clients in this [Nym integration page](https://nymtech.net/developers/integrations/mixnet-integration.html). + +###### Network requesters: +Network requesters are a type of Service Provider that essentially act as a kind of proxy, somewhat similarly to a Tor exit node. If you have access to a server, you can run a Network Requester, which will perform the following functions: +- Send outbound requests from the local machine through the mixnet to a server; +- The Network Requester then makes a request on the user’s behalf, shielding the user and their metadata from the untrusted and unknown infrastructure, for example with email or instant messaging client servers; + + +By default the Network Requester is not an open proxy but rather uses a local and global [allow list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) to whitelist host access. + +## Which Service Provider to run? +In order to ensure uptime and reliability, it is recommended that you run some pieces of mixnet infrastructure. This infrastructure varies depending on the architecture of your application, as well as the endpoints that it needs to hit: + +- No Service Provider (Network Requester) needed: If you’re running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet; +- Network Requester needed (existing or own): If you’re wanting to place the mixnet between your users’ application instances and a server-based backend, you will need a Network Requester. In this case, if your app supports SOCKS5, you could either use an existing NR or, if your app supports SOCKS5 but needs more extensive whitelisting, you could use the [network requester service provider binary](https://nymtech.net/operators/nodes/network-requester-setup.html) to proxy these requests to your application backend yourself, with the mixnet ‘between’ the user and your service, in order to prevent metadata leakage being broadcast to the internet. +- Running your own Service Provider: If your usecase is more complex, you’re wanting to route RPC requests through the mixnet to a blockchain for example, you will need to look into setting up some sort of Service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications page](https://nymtech.net/developers/community-resources/community-applications-and-guides.html). + + +## Why gateways? +Nym apps have a stable, potentially long-lasting relation to a gateway node. A client will establish a symmetric key share with a gateway that can be verified on subsequent connection attempts. + +Gateways serve a few different functions: + +- They act as an end-to-end encrypted message store in case your app goes offline; +- They send encrypted [surb-acks](https://nymtech.net/docs/architecture/traffic-flow.html) for potentially offline recipients, to ensure reliable message delivery; +- They offer a stable addressing location for apps, although the IP may change frequently; + +If you want to learn more about gateways, you can check the [mixnet integration page](https://nymtech.net/developers/integrations/mixnet-integration.html). + + +## Why and when does the mixnet client complain about insufficient topology? + +It will in one of the following cases: +- There are empty mix layers - although this is rare; +- The gateway you've registered with does not appear in the network topology -> it is either unbonded or was blacklisted; +- The gateway you want to send packets to does not appear in the network topology -> it is either unbonded or was blacklisted; + +To avoid the last two, you need to make sure the gateway you are calling is bonded and whitelisted. + +## How can I check whether the gateway I am connecting to is bonded and not blacklisted? + +The easiest way of checking what gateway you're registered with is to look at your client address. +Client addresses are in the format of: +`client-id . client-dh @ gateway-id. ` + +To illustrate this: `DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh ` + +- `DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko`: is the client's identity key; +- `ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx`: is the client's Diffie Hellman key; +- `2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh`: is the gateway's identity, which is what you'll need to check the state of the gateway in the [Nym Explorer](https://explorer.nymtech.net/network-components/gateways). + + +## How can I get my service host whitelisted? +Currently, the different options are: +- You can get it added to the local list of an existing Network Requester; +- You can ask the Nym team to add it to the global [allow list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) if it's not already there; +- You can run your own Network Requester and locally configure it to allow the hosts you need to connect to; +If you'd like to learn more about Network Requesters and the global allow list, you can visit the [network requester set-up page](https://nymtech.net/operators/nodes/network-requester-setup.html). \ No newline at end of file diff --git a/documentation/new_docs/pages/sdk/typescript/_meta.json b/documentation/new_docs/pages/sdk/typescript/_meta.json new file mode 100644 index 0000000000..d66720a97d --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/_meta.json @@ -0,0 +1,18 @@ +{ + "index": "Introduction", + "overview": "SDK overview", + "integrations": "Nym integrations", + "installation": "Installation", + "start": "Getting started", + "examples": "Step-by-step examples", + "playground": "Live Playground", + "bundling": "Bundling", + "FAQ": "FAQ", + + "contact": { + "title": "Contact ↗", + "type": "page", + "href": "https://nymtech.net/", + "newWindow": true + } +} diff --git a/documentation/new_docs/pages/sdk/typescript/bundling/_meta.json b/documentation/new_docs/pages/sdk/typescript/bundling/_meta.json new file mode 100644 index 0000000000..5ee55efacc --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/bundling/_meta.json @@ -0,0 +1,6 @@ +{ + "bundling": "General troubleshooting", + "esbuild": "ESbuild", + "webpack": "Webpack" +} + \ No newline at end of file diff --git a/documentation/new_docs/pages/sdk/typescript/bundling/bundling.mdx b/documentation/new_docs/pages/sdk/typescript/bundling/bundling.mdx new file mode 100644 index 0000000000..667eea07ad --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/bundling/bundling.mdx @@ -0,0 +1,54 @@ +# Troubleshooting bundling + +You might need some help bundling packages from the Nym Typescript SDK into your package. + +Here are some things that could go wrong: + +## WebAssembly (WASM) and web worker not included in output bundle + +### Webpack + +You might need to use the CopyPlugin by adding this to your Webpack config: + +```js +const CopyPlugin = require('copy-webpack-plugin'); + +... + +module.exports = { + ... + plugins: [ + ... + new CopyPlugin({ + patterns: [ + { + from: path.resolve(path.dirname(require.resolve('@nymproject/mix-fetch/package.json')), '*.wasm'), + to: '[name][ext]', + }, + { + from: path.resolve(path.dirname(require.resolve('@nymproject/mix-fetch/package.json')), '*worker*.js'), + to: '[name][ext]', + }, + ], + }), + ], +} +``` + +How does this work? The statement `require.resolve('@nymproject/mix-fetch/package.json')` finds the disk location of +the Nym SDK package, and resolve the directory name is `path.dirname`, the add the `*.wasm` glob to the search pattern +list. Use `[name][ext]` to preserve the output filename, because the package expects the filename to stay the same. + +## ESM not supported + +If your bundler does not support ECMAScript Modules (ESM), CommonJS packages are supported for most parts of the SDK. + +For those that don't have ESM versions, you will need to use a tool like [Babel](https://babeljs.io/) to convert +ESM to CommonJS. + +## CSP prevents loading + +If you are using a `*-full-fat` package, or if you inline WASM or web workers, you may not be able to load them if the +[CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) prevents WASM from being instantiated from a string. + +You'll have to experiment with either adjusting the CSP or use another variant that is unbundled. diff --git a/documentation/new_docs/pages/sdk/typescript/bundling/esbuild.mdx b/documentation/new_docs/pages/sdk/typescript/bundling/esbuild.mdx new file mode 100644 index 0000000000..c794cf7ce6 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/bundling/esbuild.mdx @@ -0,0 +1,32 @@ +import { Callout } from 'nextra/components'; + +# Troubleshooting bundling with ESbuild + +If you've been following the steps outlined in the Examples section, your development environment should be configured as follows: + +#### Environment Setup +Begin by creating a directory and configuring your application environment: + +Create your directory and set-up your app environment: +```bash +npm create vite@latest +``` +During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands: +```bash +cd < YOUR_APP > +npm i +npm run dev +``` + +##### Installation +Install the required package: +```bash +npm install @nymproject/< PACKAGE_NAME > +``` + + + Remember that the CosmosKit example will require you to make use of polyfills. + + +By implementing the provided code for the various components in the step-by-step examples section, you should be able to set-up and run your application without encountering any bundling challenges! + diff --git a/documentation/new_docs/pages/sdk/typescript/bundling/webpack.mdx b/documentation/new_docs/pages/sdk/typescript/bundling/webpack.mdx new file mode 100644 index 0000000000..12878675e9 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/bundling/webpack.mdx @@ -0,0 +1,93 @@ +import { Callout } from 'nextra/components'; + +# Troubleshooting bundling with Webpack + +## Webpack > 5 ESM + +For any project using Webpack, you´ll need the following rule in your `webpack.config.js` above version 5: +```json +{ + test: /\.(m?js)$/, + resolve: { + fullySpecified: false + } +} +``` + +### Create-react-app + +#### General cases + +If you wish to use Webpack for your app with the code provided in the step-by-step examples section, you'll need to: + +```bash +npx create-react-app nymapp --template typescript +cd nymapp +``` +You'll then need to install the needed dependencies, head to your app's `App.tsx` file and paste the code provided in the step-by-step section. + +#### Contract client + + + Using webpack, the `Contract client` for querying or executing might need polyfills. As create-react-app doesn´t allow you access to the Webpack config without ejecting, you'll overwrite it as follow: + + +##### Install contract-clients dependencies +```bash +npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing +``` + +Head to you app's `App.tsx` file and replace the code by the one provided in the step-by-step examples section. + +##### Polyfilling + +Copy the following to your terminal and run: + +```bash +npm install react-app-rewired +npm install --save-dev crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url buffer process +cat < config-overrides.js +const webpack = require('webpack'); +const path = require('path') + +module.exports = function override(config) { + const fallback = config.resolve.fallback || {}; + Object.assign(fallback, { + "crypto": require.resolve("crypto-browserify"), + "stream": require.resolve("stream-browserify"), + "assert": require.resolve("assert"), + "http": require.resolve("stream-http"), + "https": require.resolve("https-browserify"), + "os": require.resolve("os-browserify"), + "url": require.resolve("url") + }) + config.resolve.fallback = fallback; + config.plugins = (config.plugins || []).concat([ + new webpack.ProvidePlugin({ + process: 'process/browser', + Buffer: ['buffer', 'Buffer'] + }) + ]) + config.module.rules = (config.module.rules || []).concat([ + { + test: /\.(m?js)$/, + resolve: { + fullySpecified: false + } + } + ]) + return config; +} +EOF +``` + +#### Edit the `package.json` file as follows: + +```json + "scripts": { + "start": "react-app-rewired start", + "build": "react-app-rewired build", + "test": "react-app-rewired test", + "eject": "react-scripts eject" + }, +``` \ No newline at end of file diff --git a/documentation/new_docs/pages/sdk/typescript/examples/_meta.json b/documentation/new_docs/pages/sdk/typescript/examples/_meta.json new file mode 100644 index 0000000000..fd79b8ef06 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/examples/_meta.json @@ -0,0 +1,6 @@ +{ + "mix-fetch": "1. mixFetch", + "mixnet": "2. Mixnet Client", + "nym-smart-contracts": "3. Nym Smart Contracts", + "cosmos-kit": "4. Cosmos Kit" +} diff --git a/documentation/new_docs/pages/sdk/typescript/examples/cosmos-kit.mdx b/documentation/new_docs/pages/sdk/typescript/examples/cosmos-kit.mdx new file mode 100644 index 0000000000..c25403aa7f --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/examples/cosmos-kit.mdx @@ -0,0 +1,158 @@ +# Cosmos Kit + +The wonderful people of Cosmology have made some [fantastic components](https://cosmoskit.com/) that can be used with +Nym. These include: + +- Using the wallets such as Keplr, Cosmostation and others from your React application; +- Using the [Ledger hardware wallet](https://docs.cosmoskit.com/integrating-wallets/ledger) from your browser; +- Any wallet that supports [Wallet Connect v2.0](https://docs.cosmoskit.com/integrating-wallets/adding-new-wallets); + +##### Environment Setup +Begin by creating a directory and configuring your application environment: + +```bash +npm create vite@latest +``` + +During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands: +```bash +cd < YOUR_APP > +npm i +npm run dev +``` + +##### Installation +Install the required package: +```bash +npm install @cosmos-kit/react @cosmos-kit/keplr @cosmos-kit/ledger chain-registry +``` + +You need to polyfill some nodejs modules in order to use keplr and ledger wallets by modifying your `vite.config.js` file: +```bash +npm install @esbuild-plugins/node-globals-polyfill +``` + +```js +// vite.config.js +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill' +export default defineConfig({ + plugins: [react()], + optimizeDeps: { + esbuildOptions: { + define: { + global: 'globalThis' + }, + plugins: [ + NodeGlobalsPolyfillPlugin({ + buffer: true + }) + ] + } + } +}) +``` + +Your components have to be wrapped into a [ChainProvider](https://docs.cosmoskit.com/chain-provider), +in order to use the `useChain('nyx')` hook. The nyx chain is provided in the 'chain-registry' NPM package by default. + +Now, go to the `src` folder and open your `App.tsx` file to replace all the code with the following, which will allow you to connect and disconnect a Ledger or Keplr wallet to Nyx: + +```ts +import "./App.css"; +import React from 'react'; +import { ChainProvider, useChain } from '@cosmos-kit/react'; +import { assets, chains } from 'chain-registry'; +import { wallets as ledger } from '@cosmos-kit/ledger'; +import { wallets as keplr } from '@cosmos-kit/keplr'; +import { AminoMsg, makeSignDoc } from '@cosmjs/amino'; +import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx'; + +export const getDoc = (address: string) => { + const chainId = 'nyx'; + const msg: AminoMsg = { + type: '/cosmos.bank.v1beta1.MsgSend', + value: MsgSend.fromPartial({ + fromAddress: address, + toAddress: 'n1nn8tghp94n8utsgyg3kfttlxm0exgjrsqkuwu9', + amount: [{ amount: '1000', denom: 'unym' }], + }), + }; + const fee = { + amount: [{ amount: '2000', denom: 'ucosm' }], + gas: '180000', // 180k + }; + const memo = 'Use your power wisely'; + const accountNumber = 15; + const sequence = 16; + const doc = makeSignDoc([msg], fee, chainId, memo, accountNumber, sequence); + return doc +}; + +function MyComponent() { + const {wallet, address, connect, disconnect, getOfflineSignerAmino } = + useChain('nyx'); + + React.useEffect(() => { + connect(); + disconnect(); + }, []); + + const sign = async () => { + if (!address) return + const doc = getDoc(address); + return getOfflineSignerAmino().signAmino(address, doc); + }; + + return ( +
+
+ {wallet && +
+
Connected to {wallet?.prettyName}
+
Address: {address}
+
} +
+ {wallet ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ ); +} + +export default function App() { + const assetsFixedUp = React.useMemo(() => { + const nyx = assets.find((a) => a.chain_name === 'nyx'); + if (nyx) { + const nyxCoin = nyx.assets.find((a) => a.name === 'nyx'); + if (nyxCoin) { + nyxCoin.coingecko_id = 'nyx'; + } + nyx.assets = nyx.assets.reverse(); + } + return assets; + }, [assets]); + + return ( + c.chain_id === 'nyx')!]} + assetLists={assetsFixedUp} + wallets={[...ledger, ...keplr]} + signerOptions={{ + preferredSignType: () => 'amino', + }} + > + + + + + ) +} +``` diff --git a/documentation/new_docs/pages/sdk/typescript/examples/mix-fetch.mdx b/documentation/new_docs/pages/sdk/typescript/examples/mix-fetch.mdx new file mode 100644 index 0000000000..26161a706c --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/examples/mix-fetch.mdx @@ -0,0 +1,161 @@ +import { Callout } from 'nextra/components'; + +# `mixFetch` + +An easy way to secure parts or all of your web app is to replace calls to [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) with `mixFetch`: + +MixFetch works the same as vanilla `fetch` as it's a proxied wrapper around the original function. +Sounds great, are there any catches? Well, there are a few (for now): + +1. Currently, the operators of Network Requesters that make the final request at the egress part of the Nym mixnet to the internet use a [standard allow list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) in combination with their own configuration. If you are trying to access something that is not on the allow list, please check the FAQ page. + +2. CA certificates in `mixFetch` are periodically updated, so if you get a certificate error, the root certificate you need might not be valid. If that's the case, [send a PR](https://github.com/nymtech/nym/pulls) if you need changes to the Certificates. + +3. If you are using `mixFetch` in a web app with HTTPS you will need to use a gateway that has Secure Websockets to avoid getting a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error. + +4. For now, mixfetch doesn't work with SURBS, altough this may change in the future. + +Read [this article](https://blog.nymtech.net/mixfetch-like-the-fetch-api-but-via-the-mixnet-82acfd435c62) to learn more about mixFetch. + + + Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so. + +For the moment you have to select a Gateway that has WSS enabled and Network Requester from [Harbourmaster Gateways for mixFetch list](https://harbourmaster.nymtech.net/). + +``` +curl -X 'GET' \ +'https://validator.nymtech.net/api/v1/gateways/described' \ +-H 'accept: application/json' +``` + + + +```ts +// For mainnet +import type { SetupMixFetchOps } from '@nymproject/mix-fetch'; + +const mixFetchOptions: SetupMixFetchOps = { + preferredGateway: '6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', // with WSS + preferredNetworkRequester: + '8rRGWy54oC8drFL9DepMegBt2DLrsqQwCoHMXt9nsnTo.2XjCPVbb4FpQ9hNRcXwb9mTzEAVVk1zf1tcch3wdtNEA@6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', + mixFetchOverride: { + requestTimeoutMs: 60_000, + }, + forceTls: true, // force WSS + extra: {}, +}; +``` + +##### Environment Setup + +Begin by creating a directory and configuring your application environment: + +```bash +npm create vite@latest +``` + +During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands: + +```bash +cd < YOUR_APP > +npm i +npm run dev +``` + +##### Installation + +Install the required package: + +```bash +npm install @nymproject/mix-fetch-full-fat +``` + +##### Imports + +In the `src` folder, open the `App.tsx` file and delete all the code. + +Import the client in your app: + +```js +import { mixFetch } from '@nymproject/mix-fetch-full-fat'; +``` + +##### Example: using the `mixFetch` client: + +`Get` and `Post` outputs will be observable from your console. + +```ts +import './App.css'; +import { mixFetch, SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat'; +import React from 'react'; + +const mixFetchOptions: SetupMixFetchOps = { + preferredGateway: '6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', // with WSS + preferredNetworkRequester: + '8rRGWy54oC8drFL9DepMegBt2DLrsqQwCoHMXt9nsnTo.2XjCPVbb4FpQ9hNRcXwb9mTzEAVVk1zf1tcch3wdtNEA@6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', + mixFetchOverride: { + requestTimeoutMs: 60_000, + }, + forceTls: true, // force WSS + extra: {}, +}; + +export function HttpGET() { + const [html, setHtml] = React.useState(''); + async function get() { + //Make sure the URL is whitelisted (see 'standard allowed list') otherwise you will get a network requester filter check error + const response = await mixFetch('https://nymtech.net/favicon.svg', { mode: 'unsafe-ignore-cors' }, mixFetchOptions); + const text = await response.text(); + console.log('response was', text); + setHtml(html); + } + + return ( + <> + + + ); +} + +export function HttpPOST() { + async function post() { + //Make sure the URL is whitelisted (see 'standard allowed list') otherwise you will get a network requester filter check error + const apiResponse = await mixFetch( + 'https://httpbin.org/post', + { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }), + headers: { 'Content-Type': 'application/json' }, + }, + mixFetchOptions, + ); + console.log(apiResponse); + } + return ( + <> + + + ); +} + +export default function App() { + return ( + <> + + + + ); +} +``` diff --git a/documentation/new_docs/pages/sdk/typescript/examples/mixnet.mdx b/documentation/new_docs/pages/sdk/typescript/examples/mixnet.mdx new file mode 100644 index 0000000000..d7f47b5809 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/examples/mixnet.mdx @@ -0,0 +1,153 @@ +import { Callout } from 'nextra/components' + +# Mixnet Client + +As you know by now, in order to send or receive messages over the mixnet, you'll need to use the [`SDK Client`](https://www.npmjs.com/package/@nymproject/sdk), which will allow you to create apps that can use the Nym mixnet and Coconut credentials. + +This client is message based - it can only send a one-way message to another client's address. + +Replying can be achieved in two ways: +- reveal the sender's address to the recipient (as part of the payload) +- use a SURB (single use reply block) that allows the recipient to reply to the sender without compromising the identity of either party + +##### Environment Setup +Begin by creating a directory and configuring your application environment: + +```bash +npm create vite@latest +``` + +During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands: +```bash +cd < YOUR_APP > +npm i +npm run dev +``` + +##### Installation +Install the required package: +```bash +npm install @nymproject/sdk-full-fat +``` + +##### Imports +In the `src` folder, open the `App.tsx` file and delete all the code. + +Import the SDK's Mixnet Client in your app: +````js +import { createNymMixnetClient, NymMixnetClient, Payload } from "@nymproject/sdk-full-fat"; +```` + +##### Example: using the SDK's Mixnet Client to send and receive messages over the Nym mixnet +By pasting the below code example, you should be able to send and receive messages through the mixnet through an unstyled mixnet app template! + + For this example, we will be using the `full-fat` version of the ESM SDK. If you'd like to use the unbundled version of the ESM one, make sure your [bundler configuration](../../bundling/bundling) copies the WebAssembly (WASM) and web worker files to the output bundle. + + +```ts +import "./App.css"; +import { useEffect, useState } from "react"; +import { + createNymMixnetClient, + NymMixnetClient, + Payload, +} from "@nymproject/sdk-full-fat"; + +const nymApiUrl = "https://validator.nymtech.net/api"; + +export function MixnetClient() { + const [nym, setNym] = useState(); + const [selfAddress, setSelfAddress] = useState(); + const [recipient, setRecipient] = useState(); + const [payload, setPayload] = useState(); + const [receivedMessage, setReceivedMessage] = useState(); + + const init = async () => { + const client = await createNymMixnetClient(); + setNym(client); + + // Start the client and connect to a gateway + await client?.client.start({ + clientId: crypto.randomUUID(), + nymApiUrl, + forceTls: true, // force WSS + }); + + // Check when is connected and set the self address + client?.events.subscribeToConnected((e) => { + const { address } = e.args; + setSelfAddress(address); + }); + + // Show whether the client is ready or not + client?.events.subscribeToLoaded((e) => { + console.log("Client ready: ", e.args); + }); + + // Show message payload content when received + client?.events.subscribeToTextMessageReceivedEvent((e) => { + console.log(e.args.payload); + setReceivedMessage(e.args.payload); + }); + }; + + + const stop = async () => { + await nym?.client.stop(); + }; + + const send = () => { + if (!nym || !payload || !recipient) return + nym.client.send({ payload, recipient }); + } + + useEffect(() => { + init(); + return () => { + stop(); + }; + }, []); + + if (!nym) return
Waiting for the mixnet client...
; + + if (!selfAddress) return
Connecting...
; + + + return ( +
+

Send messages through the Nym mixnet

+

+ My self address is: {selfAddress ? selfAddress : "loading"} +

+
+ + setRecipient(e.target.value)} + > + + setPayload({ message: e.target.value, mimeType: "text/plain" }) + } + > + +
+

Received message: {receivedMessage}

+
+ ); +}; + +export default function App () { + return ( + <> + + + ) +} +``` + + + If you encounter a Gateway client error that persists even after a hard refresh, you may need to take the following steps: Open your browser's console => Navigate to the "Application" tab => Delete the databases listed under "IndexedDB". + Additionally, please be aware that the mixnet client is currently limited to functioning in local development environments due to SSL-related issues. + diff --git a/documentation/new_docs/pages/sdk/typescript/examples/nym-smart-contracts.mdx b/documentation/new_docs/pages/sdk/typescript/examples/nym-smart-contracts.mdx new file mode 100644 index 0000000000..669d284611 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/examples/nym-smart-contracts.mdx @@ -0,0 +1,275 @@ +import { Callout } from 'nextra/components' + +# Nym Smart Contract Clients + +As previously mentioned, to query or execute on any of the Nym contracts, you'll need to use one of the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients), which contains read-only query and signing clients for all of Nym's smart contracts. + +##### Contract Clients list +Lists of the different available clients and methods from the `Contract Clients` can be found in the `.client.ts` files: +| Client name | Functionality| Methods list | +| :-------------: | :----------: | :----------: | +| Coconut Bandwidth Client| Manages the depositing and release of funds. Tracks double spending. | [Coconut Bandwidth](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutBandwidth.client.ts) | +| Coconut DKG Client | Allows signers participating in issuing Coconut credentials to derive keys to be used. | [Coconut DKG](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutDkg.client.ts) | +| Cw3FlexMultisig Client | Used by the Coconut APIs to issue credentials. [This](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw3-flex-multisig) is a multisig contract that is backed by the cw4 (group) contract, which independently maintains the voter set. | [Cw3Flex Multisig](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Cw3FlexMultisig.client.ts) | +| Cw4Group Client | Used by the Coconut APIs to issue credentials. [Cw4 Group](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw4-group) stores a set of members along with an admin, and allows the admin to update the state. | [Cw4Group](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Cw4Group.client.ts) | +| Mixnet Client | Manages the network topology of the mixnet, tracking delegations and rewards. | [Mixnet](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Mixnet.client.ts) | +| Name Service Client | Operates as a directory of user-defined aliases, analogous to a Domain Name System (DNS). | [Name service](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/NameService.client.ts) | +| Service provider Directory Client| Allows users to register their service provider in a public directory. | [Service Provider](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/ServiceProviderDirectory.client.ts) | +| Vesting Client | Manages NYM token vesting functionality. | [Vesting](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Vesting.client.ts) | + + + +##### Environment Setup +Begin by creating a directory and configuring your application environment: + +```bash +npm create vite@latest +``` + +During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands: +```bash +cd < YOUR_APP > +npm i +npm run dev +``` + +##### Installation +Install the packages and their dependencies if you don't already have them: +```bash +npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate +``` + +## Query clients + +In the `src` folder, open the `App.tsx` file and delete all the code. + +##### Imports +Import the contracts' client in your app: +````js +import { contracts } from '@nymproject/contract-clients'; +import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +```` + +##### Example: using the mixnet smart contract client to query +In this example, we will use the `MixnetQueryClient`from the `Contract Clients` to simply query the contract and return a list of mixnodes. + +```ts +import "./App.css"; +import { contracts } from "@nymproject/contract-clients"; +import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { useEffect, useState } from "react"; + +export default function Mixnodes() { + + const [mixnodes, setMixnodes] = useState([]); + + async function fetchMixnodes(){ + // Set-up the CosmWasm Client + const cosmWasmClient = await SigningCosmWasmClient.connect("wss://rpc.nymtech.net:443"); + const client = new contracts.Mixnet.MixnetQueryClient( + cosmWasmClient, + "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr" // The mainnet mixnet contract address (which will be different on mainnet, QA, etc) + ); + const result = await client.getMixNodesDetailed({}); + setMixnodes(result.nodes) + } + + useEffect(() => { + fetchMixnodes(); + }, []) + + return( + <> + + + {mixnodes?.map((value: any, index: number) => { + return( + + + + ) + }) + } + +
{value?.bond_information?.mix_node?.identity_key}
+ + ) +} +``` + +By pasting the above code in the `App.tsx` file and `npm run dev` your app from the terminal, you should see an unstyled printed list of Nym mixnodes! + + + + +## Execute clients + +##### Installation +Install the packages and their dependencies if you don't already have them: + +```bash +npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing +``` + + +##### Imports +Import the contracts' execute clients in your app: +````js +import { contracts } from '@nymproject/contract-clients'; +import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; +```` + +##### Example: using the Mixnet smart contract client to execute methods +In this example, we will use the `MixnetClient`and the `signer` from the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients) to execute methods. + +Note that you will need to create a `settings.ts` file (here created in the same directory), using the following structure: +```json + +export const mySettings = { + url: "wss://rpc.nymtech.net:443", + mixnetContractAddress: '', + mnemonic: '', + address: '' +}; + +export const settings = mySettings; +``` + +```ts +import "./App.css"; +import { contracts } from "@nymproject/contract-clients"; +import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; +import { GasPrice } from "@cosmjs/stargate"; +import { settings } from "./settings"; + +export default function Exec() { + let signer: DirectSecp256k1HdWallet; + let signerMixnetClient: any; + let cosmWasmSigningClient: SigningCosmWasmClient; + let mixId: number; + let amountToDelegate: string; + let nodeAddress: string; + let amountToSend: string; + let delegations: any; + + async function ExecuteOnNyx() { + // Cosmos client + signer = await DirectSecp256k1HdWallet.fromMnemonic(settings.mnemonic, { + prefix: "n", + }); + const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner( + settings.url, + signer, + { + gasPrice: GasPrice.fromString("0.025unym"), + } + ); + // Save globally + cosmWasmSigningClient = cosmWasmClient; + + // Nym client + const mixnetClient = new contracts.Mixnet.MixnetClient( + cosmWasmSigningClient, + settings.address, // Sender (that account of the signer) + settings.mixnetContractAddress // Contract address (different on mainnet, QA, etc) + ); + // Save globally + signerMixnetClient = mixnetClient; + + } + + // Get delegations + const getDelegations = async () => { + if (!signerMixnetClient) { + return; + } + const delegationsObject = await signerMixnetClient.getDelegatorDelegations({ + delegator: settings.address, + }); + delegations = delegationsObject; + }; + + // Make delegation + const doDelegation = async () => { + if (!signerMixnetClient) { + return; + } + const res = await signerMixnetClient.delegateToMixnode( + { mixId }, + "auto", + undefined, + [{ amount: `${amountToDelegate}`, denom: "unym" }] + ); + console.log(res); + }; + + // Undelegate all + const doUndelegateAll = async () => { + for (const delegation of delegations.delegations) { + await signerMixnetClient.undelegateFromMixnode( + { mixId: delegation.mix_id }, + "auto" + ); + } + }; + + // Sending tokens + const doSendTokens = async () => { + const memo = "test sending tokens"; + const res = await cosmWasmSigningClient.sendTokens( + settings.address, + nodeAddress, + [{ amount: amountToSend, denom: "unym" }], + "auto", + memo + ); + console.log(res); + }; + + ExecuteOnNyx(); + setTimeout(() => getDelegations(), 1000); + + return ( +
+

Exec

+
+

Send Tokens

+ (nodeAddress = e.target.value)} + /> + (amountToSend = e.target.value)} + /> +
+ +
+
+
+

Delegate

+ (mixId = +e.target.value)} + /> + (amountToDelegate = e.target.value)} + /> +
+ +
+
+ +
+
+
+ ); +} +``` diff --git a/documentation/new_docs/pages/sdk/typescript/index.mdx b/documentation/new_docs/pages/sdk/typescript/index.mdx new file mode 100644 index 0000000000..0869c79b50 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/index.mdx @@ -0,0 +1,36 @@ +# Introduction + +Welcome to the documentation for Nym's TypeScript SDK! + +This comprehensive guide contains information about the various TypeScript SDK modules that facilitate interaction with different components of the Nym stack, including the Nym mixnet, the Nyx blockchain, and Coconut credentials. + +## Other developer guides + +If you're new to the Nym ecosystem and want to better understand the mixnet, explore kickstart options and demos, learn network integration, or follow developer tutorials, the [Developer Portal](https://nymtech.net/developers/) is your go-to resource. + +For a more in-depth exploration of Nym's architecture, clients, nodes, and SDK examples, please refer to the [Technical Documentation](https://nymtech.net/docs/) section. + +If you'd like to build your own app or integrate pieces of the Nym infrastructure using Rust, please use our [Rust SDK documentation](https://nymtech.net/developers/tutorials/rust-sdk.html). + +If you're looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways, network requesters) and Nyx blockchain validators, then have a look at our [Operators Guide](https://nymtech.net/operators/introduction.html). + + +## What is Nym? +Nym is a **privacy infrastructure that secures user data and protects against surveillance at the network level**. +The platform does so by leveraging different technological components: + - **A Mixnet**, a type of overlay network that makes both content and metadata of transactions private through network-level obfuscation and incentivisation (using [Sphinx](https://blog.nymtech.net/sphinx-tl-dr-the-data-packet-that-can-anonymize-bitcoin-and-the-internet-18d152c6e4dc)); +- **A blockchain, [Nyx](https://blog.nymtech.net/nym-is-not-a-blockchain-but-it-is-powered-by-one-4bb16ef16587)**, our Cosmos SDK blockchain, to allow for us to use payment tokens in the form of NYM, as well as smart contracts, in order to create a robust, decentralized, and secure environment incentives for the mixnet; +- **Coconut, a signature scheme** that creates an application-level private access control layer to power [Zk-Nyms](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a); +- **A utility token, [NYM](https://blog.nymtech.net/nym-tokens-where-do-they-live-and-how-are-they-distributed-cross-chain-8d134bf9c41f)**, to pay for usage, measure reputation and serve as rewards for the privacy infrastructure. + +Simply put, the Nym network ("Nym") is a decentralized and incentivized infrastructure to provision privacy to a broad range of message-based applications and services. + + + +## Read our protocol + +[The Nym network ("Nym")](https://www.feat-nym-update-nym-web.websites.dev.nymte.ch/nym-whitepaper.pdf) is a privacy infrastructure that, simply put, can be seen as a ["Layer 0" privacy infrastructure](https://blog.nymtech.net/nym-layer-0-privacy-infrastructure-for-the-whole-internet-e53238f9b8e7) for the entire internet. +Read more to understand the differences in between Nym, TOR (and other mixnets) and VPNs. + + + diff --git a/documentation/new_docs/pages/sdk/typescript/installation.mdx b/documentation/new_docs/pages/sdk/typescript/installation.mdx new file mode 100644 index 0000000000..7a2a52a39f --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/installation.mdx @@ -0,0 +1,70 @@ +import { Callout } from 'nextra/components' + +# Overview + +The different modules in the Typescript SDK allow developers to start building browser-based applications quickly. Simply import the SDK module of your choice – depending on the component from the Nym architecture you want to use – into your code via NPM, as you would any other TypeScript library. + + + Other than the `Contract Clients`, SDK modules come in four different flavours (ESM, CJS and full-fat for ESM and CJS). + This documentation focuses on examples using the `full-fat` versions. + + +#### Install all + +```bash +npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk-full-fat @nymproject/mix-fetch-full-fat +``` + +## MixFetch +#### Overview +MixFetch is a drop-in replacement for [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) that sends HTTP requests through the Nym mixnet. It does this by grabbing the same arguments as traditional fetch and constructing a SOCKS5 request that will be made to the destination host on the Internet via a [SOCKS5](https://nymtech.net/developers/quickstart/socks-proxy.html) [Network Requester](https://nymtech.net/docs/nodes/network-requester.html). + +#### Installation: MixFetch package +In order to fetch data through mixFetch you'll need to use the [`MixFetch package`](https://www.npmjs.com/package/@nymproject/mix-fetch). + +First install the package and its dependencies: + +```bash +npm install @nymproject/mix-fetch-full-fat +``` + +## Mixnet +#### Overview +The [Nym mixnet](https://nymtech.net/docs/architecture/network-overview.html) provides extremely robust protection against network-level surveillance. It splits data into smaller, identically sized,[Sphinx encrypted packet](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf), which are then mixed in with dummy traffic and dispersed through Nym nodes around the world at randomised intervals. Finally these are decrypted and reassembled, preventing the observation of metadata and providing pattern privacy so that it cannot be determined who is communicating with whom. The Nym mixnet is based on a modified version of the [Loopix design](https://www.usenix.org/sites/default/files/conference/protected-files/usenixsecurity17_slides_piotrowska.pdf). + +*You can explore the Nym mixnet using the [mixnet explorer](https://nymtech.net/docs/explorers/mixnet-explorer.html) here.* + + +#### Installation: Mixnet Client +In order to send or receive traffic over the mixnet, you'll need to use the [`Mixnet Client`](https://www.npmjs.com/package/@nymproject/sdk). + +First install the package and its dependencies: + +```bash +npm install @nymproject/sdk-full-fat +``` + + + +## Nym Smart Contracts +#### Overview +The Nyx blockchain is a general-purpose CosmWasm-enabled smart contract platform, and the home of the smart contracts which keep track of the mixnet, amongst others. +Further information about the chain can be found on the [Nyx blockchain explorer](https://nym.explorers.guru/). + +Using the [Nym mixnet smart contract clients](https://nymtech.net/docs/nyx/smart-contracts.html), you will be able to query contract states or execute methods when providing a signing key. + +*You can learn about our different methods to interact with the chain [here](https://nymtech.net/docs/nyx/interacting-with-chain.html)*. + +#### Installation: Contract Clients +In order to query or execute on any of the Nym smart contracts, you'll need to use the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients), which contains read-only query and signing clients for all Nyx's smart contracts. + +First install the package and its dependencies from Cosmos Stargate: + +```bash +npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing +``` + + + + + diff --git a/documentation/new_docs/pages/sdk/typescript/integrations.mdx b/documentation/new_docs/pages/sdk/typescript/integrations.mdx new file mode 100644 index 0000000000..a0596810be --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/integrations.mdx @@ -0,0 +1,111 @@ +import Box from '@mui/material/Box'; +import { Steps } from 'nextra/components' +import { Tabs } from 'nextra/components' +import { GitHubRepoSearch } from '../../../code-snippets/mixfetchurl'; + +# Integrations page + +## How can I integrate Nym into my app? +If you're unsure where to start, the following set of questions should help you determine which path to follow in regards to integrations with Nym: + + +### Is your app developed in TS/JS or Rust? + + + **Yes - TS/JS or RUST**: If yes, you will either be able to leverage `mixFetch` (go to step 2 below) or the `sdk client` to route app traffic through the mixnet. Note that `mixfetch` currently only works with JS/TS at the moment, as we do not yet have a Rust implementation of it. + **No**: You'll most likely need to use FFI or one of our [standalone clients](https://nymtech.net/developers/integrations/mixnet-integration.html). + + + + +### Does your TS/JS app rely on `fetch` for its network traffic and remote connections? + Check whether `mixFetch` can be used to route your traffic through the mixnet by entering your repository's URL below: + + + + + **Yes - my repo currently uses `fetch`**: The best way to integrate Nym's `mixFetch` into your application will be where external network calls and RPC happens, for example, something in the lines of `sendRawTransaction` if you have an ETH-compatible wallet or `JsonRpcClient` if you use CosmJS. Although you can simply search for any JS `fetch` calls in your code (using our tool above) that are easily replaceable with `mixFetch`, keep in mind that `fetch` is not the only way to make `JSONRPC` or `XHR` calls. We advise to approach the integration process in a semantic way, searching for a module that is the common denominator for external communication in the codebase. Usually these are API controllers, middlewares or repositories. + **No**: While mixFetch is the shortest and easiest way to integrate Nym, a well-modularized JS/TS or Rust codebase should permit the integration of one of our SDK components, which will allow more flexibility and control. Read more about our different SDK components in the [TS SDK overview page](./overview) and the [Rust SDK documentation](https://nymtech.net/developers/tutorials/rust-sdk.html). + + + + + ### Use one of our standalone Nym clients + If you've answered 'No' to all of the above, you may need to use one of our [standalone clients](https://nymtech.net/docs/clients/overview.html). All Nym client packages present basically the same capabilities to the privacy application developer. They need to run as a persistent process in order to stay connected and ready to receive any incoming messages from their gateway nodes. They register and authenticate to gateways, and construct Sphinx packets. While setting up those, and depending on your usecase, you may need to set-up and run a Service Provider. Read below the "How to deal with Service Providers" section for more details. + + + + + + +```ascii + +-----------------------------------+ + | | +---------------------+ + | Is app JS/TS or another language? +-----+|Go / C / C++ / Swift |+----------------------+ + | | +---------------------+ | + +--------+-----------------+--------+ | + | | | + | | | + +----+----+ +--+---+ | + | TS/JS | | Rust |+-------------------------------------+ | + +----+----+ +------+ | | + | | | + +----+------------+---------------------+--------+--------------+ | | + +-----+----+ +--------+ | | | + | Browser | | Server | | | | + +-----+----+ +---+----+ | | | + | | | | | + +-------+--------+ +---+----+ | | | + | HTTP requests | | nodeJS | | | | + +---+-------+--------+----+ +---+----+ | | | + | | | | | | | + +--+--+ +----+----+ +-----+----------+ | | | | + | yes | |websocket| | webRTC or other| | | | | + +--+--+ +----+----+ +------+---------+ | | | | + | | | | | | | ++-----v---+ +-----v-----+ +----v-------+ +--------v---------+ +----v---+ +----v-----+ +--------v---------+ +|mixFetch | |Talk to us | | No support | | mixFetch nodeJS | | TS SDK | | Rust SDK | | FFI* / Nym | ++---------+ +-----------+ +------------+ +------------------+ +--------+ +----------+ | standalone client| + +------------------+ + + * Coming soon™️ + ``` + +## When do I need Service Providers? +If you decide to interact with one of the Nym [standalone clients](https://nymtech.net/docs/clients/overview.html), then you most likely will need to set-up a Service Provider. The need for a Service Provider mostly depends on your app's goal and architecture (and the endpoint it needs to hit). +Again, as detailed in the [FAQ](../FAQ/general): + +- No Service Provider (Network Requester) needed: If you’re running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet. +- Network Requester needed (existing or own): If you’re wanting to place the mixnet between your users’ application instances and a server-based backend, you will need a Network Requester. In this case, if your app supports SOCKS5, you could either use an existing NR or, if your app supports SOCKS5 but needs more extensive whitelisting, you could use the [network requester service provider binary](https://nymtech.net/operators/nodes/network-requester-setup.html) to proxy these requests to your application backend yourself, with the mixnet ‘between’ the user and your service, in order to prevent metadata leakage being broadcast to the internet. +- Running your own Service Provider: If your usecase is more complex, you’re wanting to route RPC requests through the mixnet to a blockchain for example, you will need to look into setting up some sort of Service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications page](https://nymtech.net/developers/community-resources/community-applications-and-guides.html). + +``` ascii + +----------------------+ + +----+| App supports SOCKS5? |+-----+ + | +----------------------+ | + +--+--+ +--+--+ + | yes | | no | + +-----+ +-----+ + | | + +------------+ +-----------------------+ + +----+|App use case|+--+ | Set standalone client | + | +------------+ | | + SP | + needs needs +-----------------------+ + | | + +-------+--------+ +---------+---------+ + |simple whitelist| |extensive whitelist| + +----------------+ +-+---------------+-+ + | | ++------------------+ +---------------+ +| use existing NR | | run own NR/SP | ++------------------+ +---------------+ + +``` + + + +## Other resources +If you'd like to learn more about potential integrations, please make sure to read: +- The [integrations FAQ](https://nymtech.net/developers/faq/integrations-faq.html): which lists a set of common questions regarding integrating Nym and Nyx; +- The [mixnet integration page](https://nymtech.net/developers/integrations/mixnet-integration.html): which will help you integrate with Nym to use the mixnet for application traffic; +- The [payment integration page](https://nymtech.net/developers/integrations/payment-integration.html): which will help you integrate with the Nyx blockchain and use Nym for payments; diff --git a/documentation/new_docs/pages/sdk/typescript/overview.mdx b/documentation/new_docs/pages/sdk/typescript/overview.mdx new file mode 100644 index 0000000000..3efb50cc63 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/overview.mdx @@ -0,0 +1,71 @@ +import { Callout } from 'nextra/components' +import { TableContainer, Table, TableBody, TableCell, TableRow, Paper } from '@mui/material' +import { NPMLink } from '../../../components/npm'; + +## SDK overview +The Typescript SDK allows developers to start building browser-based Nym-based applications quickly, by simply importing the SDK modules into their code via NPM as they would any other Typescript library. + +Currently developers can use different packages from the Typescript SDK to run the following entirely in browser: + + + + + + + **Nym Smart Contracts** + + + Create a simple query or query and execute methods on the smart contracts that run the Nym Mixnet + + + + + + + + **Mixnet Client** + + + Send & receive text and binary traffic over the Nym Mixnet + + +
+
+
+ +
+
+ + + **mixFetch** + + + A drop-in replacement for [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) + that sends HTTP requests over the Nym Mixnet + + +
+
+
+ +
+
+
+
+
+ +## Which package should I use? + +All packages come in four different variations: +- **ESM**: For new projects with current tooling. These packages use the ECMAScript Modules (ESM) system. You may need to [configure your bundler](bundling) to handle the packages WASM and web worker components; +- **ESM full-fat**: These ESM packages are pre-bundled and include inline WebAssembly and web worker code; +- **CommonJS**: For older projects that still use CommonJS. All WebAssembly (WASM) and web workers in the package need to be [bundled](bundling) to work correctly; +- **CommonJS full-fat**: These packages are already pre-bundled and should work in your project without additional configuration; + + + All `*-full-fat` variants have large bundle sizes because they include all WASM and web-workers as inline Base64 strings. If you care about your app's bundle size, then use the ESM variant. + + + + As the Typescript SDK and associated modules are still a work in progress, note that the inline WASM and web worker versions are likely to have issues if the web site / app / extension has a strict [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). + diff --git a/documentation/new_docs/pages/sdk/typescript/playground/_meta.json b/documentation/new_docs/pages/sdk/typescript/playground/_meta.json new file mode 100644 index 0000000000..09149f2a76 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/playground/_meta.json @@ -0,0 +1,7 @@ +{ + "mixfetch": "1. Use mixFetch", + "traffic": "2. Send traffic over the mixnet", + "mixnodes": "3.1. Query mixnet contract for a list of mix nodes", + "wallet": "3.2. Basic wallet operations", + "cosmos-kit": "4. Cosmos Kit" +} diff --git a/documentation/new_docs/pages/sdk/typescript/playground/cosmos-kit.mdx b/documentation/new_docs/pages/sdk/typescript/playground/cosmos-kit.mdx new file mode 100644 index 0000000000..52fda2f42e --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/playground/cosmos-kit.mdx @@ -0,0 +1,28 @@ +import "@interchain-ui/react/styles" +import { CosmosKit } from "../../../../components/cosmos-kit"; +import { Callout } from 'nextra/components' +import FormattedCosmoskitExampleCode from '../../../../code-examples/cosmoskit-example-code.mdx'; + +# Cosmos Kit + +Below is an example that uses [CosmosKit](https://cosmoskit.com/) to connect and sign a fake transaction with your [Keplr wallet](https://www.keplr.app/) or +[Ledger hardware wallet](https://www.ledger.com/) to this page: + + + +Once you connect either Keplr or your hardware Ledger, you can request the fake transaction to be signed. The hash +of the message will be displayed. + + + No transactions will be broadcast. You will only be signing a transaction. + + +If you are using the Ledger hardware wallet, please make sure: + +- You have the `cosmoshub` app installed on the Ledger; +- It is connected to your computer; +- It is unlocked; +- The Cosmos Hub app is open; +- Grant permissions to your browser when you click the button above to connect to the Ledger (if you do not see a prompt, try another browser); + + diff --git a/documentation/new_docs/pages/sdk/typescript/playground/mixfetch.mdx b/documentation/new_docs/pages/sdk/typescript/playground/mixfetch.mdx new file mode 100644 index 0000000000..bb3009d413 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/playground/mixfetch.mdx @@ -0,0 +1,24 @@ +# mixFetch + +import { MixFetch } from '../../../../components/mix-fetch' +import Box from '@mui/material/Box'; +import FormattedMixFetchExampleCode from '../../../../code-examples/mixfetch-example-code.mdx'; +import { Callout } from 'nextra/components' + + + Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so. + + For the moment you have to select a Gateway that has WSS enabled from [this list](https://harbourmaster.nymtech.net/v1/services?wss=true). + + You can also find WSS-enabled nodes by querying the `gateways/described` endpoint on the Nym API and filtering for `wss_port`, either via the [Swagger webpage](https://validator.nymtech.net/api/swagger/index.html) or with `curl`: + + ``` +curl -X 'GET' \ + 'https://validator.nymtech.net/api/v1/gateways/described' \ + -H 'accept: application/json' + ``` + + + + + diff --git a/documentation/new_docs/pages/sdk/typescript/playground/mixnodes.mdx b/documentation/new_docs/pages/sdk/typescript/playground/mixnodes.mdx new file mode 100644 index 0000000000..d0d5bdbefc --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/playground/mixnodes.mdx @@ -0,0 +1,13 @@ +# Query for Mixnodes + +import { Mixnodes } from '../../../../components/mixnodes'; +import Box from '@mui/material/Box'; +import FormattedExampleCode from '../../../../code-examples/mixnodes-example-code.mdx'; + +The Nym Mixnet contract keeps a directory of all mixnodes that can be used to mix traffic. + +Here is a live example of querying the Mixnet Contract for a paged list of mixnodes: + + + + diff --git a/documentation/new_docs/pages/sdk/typescript/playground/traffic.mdx b/documentation/new_docs/pages/sdk/typescript/playground/traffic.mdx new file mode 100644 index 0000000000..3b188fd7e5 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/playground/traffic.mdx @@ -0,0 +1,11 @@ +# Traffic +import { Callout } from 'nextra/components' +import { Traffic } from '../../../../components/traffic'; +import Box from '@mui/material/Box'; +import FormattedTrafficExampleCode from '../../../../code-examples/traffic-example-code.mdx'; + + +Use this tool to experiment with the mixnet: send and receive messages! + + + diff --git a/documentation/new_docs/pages/sdk/typescript/playground/wallet.mdx b/documentation/new_docs/pages/sdk/typescript/playground/wallet.mdx new file mode 100644 index 0000000000..e0ec0a4fde --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/playground/wallet.mdx @@ -0,0 +1,24 @@ +# Wallet + +import Box from '@mui/material/Box'; + +import { WalletContextProvider } from '../../../../components/wallet/utils/wallet.context'; +import { ConnectWallet } from '../../../../components/wallet/connect'; +import { SendTokes } from '../../../../components/wallet/sendTokens'; +import { Delegations } from '../../../../components/wallet/delegations'; +import FormattedWalletConnectCode from '../../../../code-examples/wallet-connect-code.mdx'; +import FormattedWalletSendTokensCode from '../../../../code-examples/wallet-sendTokens-code.mdx'; +import FormattedWalletDelegationsCode from '../../../../code-examples/wallet-delegations-code.mdx'; + +Here's a small wallet example using testnet for you to test out! + + + + + + + + + + + diff --git a/documentation/new_docs/pages/sdk/typescript/scripts/build-prod-docs.sh b/documentation/new_docs/pages/sdk/typescript/scripts/build-prod-docs.sh new file mode 100755 index 0000000000..5f37401e87 --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/scripts/build-prod-docs.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +rm -rf ../../../dist/ts/docs/sdk/typescript || true + +# run the build +npm run build + +# move the output outside of the yarn/npm workspaces +mkdir -p ../../../dist/ts/docs/sdk +mv out ../../../dist/ts/docs/sdk/typescript + +echo "Output can be found in:" +realpath ../../../dist/ts/docs/sdk/typescript diff --git a/documentation/new_docs/pages/sdk/typescript/start.mdx b/documentation/new_docs/pages/sdk/typescript/start.mdx new file mode 100644 index 0000000000..be611faf3b --- /dev/null +++ b/documentation/new_docs/pages/sdk/typescript/start.mdx @@ -0,0 +1,82 @@ +import { Callout } from 'nextra/components' + + +## MixFetch + +Use the [`mixFetch`](https://www.npmjs.com/package/@nymproject/mix-fetch) package as a drop-in replacement for `fetch`to send HTTP requests over the Nym mixnet: + +```ts +import { mixFetch } from '@nymproject/mix-fetch'; + +// HTTP GET +const response = await mixFetch('https://nymtech.net'); +const html = await response.text(); + +// HTTP POST +const apiResponse = await mixFetch('https://api.example.com', { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }), + headers: { [`Content-Type`]: 'application/json', Authorization: `Bearer ${AUTH_TOKEN}` } +}); +``` + +Check the [standard allowed list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) to see +if the host you want to `mixFetch` from is whitelisted. + +## Mixnet Client +After instantiating the [`Mixnet Client`](https://www.npmjs.com/package/@nymproject/sdk), you can use it and send messages to yourself and output them in the console by following these steps: +````js +import { createNymMixnetClient } from '@nymproject/sdk'; + +const main = async () => { + const nym = await createNymMixnetClient(); + + const nymApiUrl = 'https://validator.nymtech.net/api'; + + // Show message payload content when received + nym.events.subscribeToTextMessageReceivedEvent((e) => { + console.log('Got a message: ', e.args.payload); + }); + + // Start the client and connect to a gateway + await nym.client.start({ + clientId: 'My awesome client', + nymApiUrl, + }); + + // Stop the client connection + const stop = async () => { + await nym?.client.stop(); + }; + + // Send a message to yourself + const payload = 'Hello mixnet'; + const recipient = nym.client.selfAddress(); + nym.client.send({ payload, recipient }); + +}; +```` + +## Nym Smart Contracts + +After having installed your client from the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients) to query any of the Nym smart contracts, you can import the packages and execute some methods, signing them with a mnemonic: +````js +import { contracts } from '@nymproject/contract-clients'; +import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; + +async function main() { + // Generate a signer from a mnemonic + const signer = await DirectSecp256k1HdWallet.fromMnemonic("..."); + const accounts = await signer.getAccounts(); + + // Make a signing client for the Nym Mixnet contract on mainnet + const cosmWasmSigningClient = await SigningCosmWasmClient.connectWithSigner("https://rpc.nymtech.net:443", signer); + const client = new contracts.Mixnet.MixnetClient(cosmWasmSigningClient, accounts[0].address, 'n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr'); + + // Delegate 1 NYM to mixnode with id 100 + const result = await client.delegateToMixnode({ mixId: 100 }, 'auto', undefined, [{ amount: `${1_000_000}`, denom: 'unym' }]); + + console.log(`Tx Hash = ${result.transactionHash}`); +}; +```` diff --git a/documentation/new_docs/pages/styles.css b/documentation/new_docs/pages/styles.css new file mode 100644 index 0000000000..94738ed641 --- /dev/null +++ b/documentation/new_docs/pages/styles.css @@ -0,0 +1,108 @@ +body { +--colorPrimary: #fb6e4e; +--textPrimary: #121726; +} + +div.nextra-code-block > div { + background:var(--colorPrimary) !important; +} + +.nextra-code-block > pre { + max-height: 350px !important; + scroll-y: auto !important; +} + +/* Code blocks*/ +.nx-absolute.nx-top-0 { + background: var(--colorPrimary) !important; + color: var(--textPrimary) !important; +} + + +:is(html[class~=dark] .dark\:nx-bg-primary-300\/10) { + background-color: hsl(var(black)100% 77%/.1) !important; +} + + +/* Sidebar buttons */ +:is(html .dark\:nx-bg-primary-400\/10){ + background: var(--colorPrimary) !important; + color: var(--textPrimary) !important; +} + +/* aside ul > li > a:hover{ + background: #fb6e4ea9 !important; +} */ + +/* aside ul > li > a:active{ + background: #fb6e4e !important; +} */ + + +/* Links*/ +.nx-text-primary-600.nx-underline.nx-decoration-from-font { + color: var(--colorPrimary)!important; +} + +.MuiTypography-root.MuiTypography-inherit.MuiLink-root.MuiLink-underlineAlways.css-1xr3c94-MuiTypography-root-MuiLink-root { + color: var(--colorPrimary) !important; +} + +/* Callouts */ +.nextra-callout { + background-color: #fb6e4e21 !important; +} + +.nx-mt-6 .nx-leading-7 > p { + color: var(--colorPrimary)!important; +} + +/* Callout Secondary */ +:is(html .dark\:nx-border-yellow-200\/30) { + border-color: transparent !important; +} + +/* Callout Primary */ +:is(html .dark\:nx-border-blue-200\/30) { + border-color: var(--colorPrimary)!important; +} + +/* Chips*/ +.chipContained{ + background-color: var(--colorPrimary) !important; +} + +/* Buttons */ +.MuiButton-root { + color: var(--colorPrimary) !important; + border-color: var(--colorPrimary) !important; +} + +.MuiButton-root:hover { + color: white !important; + background-color: var(--colorPrimary) !important; +} + +.MuiCircularProgress-root { + color: var(--colorPrimary) !important; +} + +.nextra-scrollbar.nx-sticky{ + color: var(--colorPrimary) !important; +} + +.nx-text-primary-600 { + color: var(--colorPrimary) !important; +} + +input:focus-visible { + --tw-ring-shadow: var(--colorPrimary) !important; +} + +.Mui-focused > fieldset { +border-color: var(--colorPrimary) !important; +} + +a.MuiLink-root { + color: var(--colorPrimary) !important; +} \ No newline at end of file diff --git a/documentation/new_docs/theme.config.tsx b/documentation/new_docs/theme.config.tsx new file mode 100644 index 0000000000..41295389af --- /dev/null +++ b/documentation/new_docs/theme.config.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { DocsThemeConfig } from "nextra-theme-docs"; +import { Footer } from "./components/footer"; + +const config: DocsThemeConfig = { + logo: Nym Docs, + project: { + link: "https://github.com/nymtech/nym", + }, + // chat: { + // link: "https://matrix.to/#/#dev:nymtech.chat", + // }, + docsRepositoryBase: + "https://github.com/nymtech/nym/tree/develop/documentation/", + footer: { + text: Footer, + }, + darkMode: true, + nextThemes: { + forcedTheme: "dark", + }, + primaryHue: { + dark: 30, + light: 30, + }, +}; + +export default config; diff --git a/documentation/new_docs/tsconfig.json b/documentation/new_docs/tsconfig.json new file mode 100644 index 0000000000..5020c55e5a --- /dev/null +++ b/documentation/new_docs/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/documentation/new_docs/typings/txt.d.ts b/documentation/new_docs/typings/txt.d.ts new file mode 100644 index 0000000000..c20aff5ada --- /dev/null +++ b/documentation/new_docs/typings/txt.d.ts @@ -0,0 +1,5 @@ +declare module '*.txt' { + const content: any; + export default content; + } + \ No newline at end of file