moved old docs -> old_docs dir for clarity when devving
@@ -1,14 +1,6 @@
|
||||
# mdbook files
|
||||
book/
|
||||
|
||||
# Compiled assets
|
||||
.sass-cache
|
||||
_site
|
||||
|
||||
# Developing
|
||||
todo.md
|
||||
.idea
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
.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
|
||||
|
||||
@@ -1,42 +1,24 @@
|
||||
# Nym Documentation
|
||||
Documentation for the Nym privacy platform built using the [mdBook](https://rust-lang.github.io/mdBook/) docs framework.
|
||||
# Nym Docs v2
|
||||
|
||||
Documentation can be viewed at https://nymtech.net/docs
|
||||
New consolidated version of the nym docs.
|
||||
|
||||
## Contributing
|
||||
Contributions to our documentation are very welcome. Please work on your contribution in either a `feature/<feature-name>` or `chore/<chore-name>` branch from `master` and target your pull request at `master`.
|
||||
## Local development
|
||||
|
||||
Since these docs autogenerate command output and import docs from binaries in `target/release` on `build` make sure you're branching off of `master` when making your branch.
|
||||
```
|
||||
npm i
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Changes merged to `master` will be autodeployed to the production site.
|
||||
Open http://localhost:3000 to browse the output that will hot-reload when you make changes.
|
||||
|
||||
### Contributing a new translation
|
||||
To contribute tranlsations in a new language, please get in touch via [Matrix](https://matrix.to/#/#general:nymtech.chat) or [Discord](https://nymtech.net/go/discord).
|
||||
## Build
|
||||
|
||||
### Variables
|
||||
There are some variables that are shared across the entire docs site, such as the current latest software version.
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
Variables are denoted in the `.md` files wrapped in `{{}}` (e.g `{{wallet_release_version}}`), and are located in the `book.toml` file under the `[preprocessor.variables.variables]` heading. If you are changing something like the software release version, minimum code versions in prerequisites, etc, **check in here first!**
|
||||
The static output will be in `./out`;
|
||||
|
||||
### Diagrams
|
||||
Most diagrams are simply ascii. Copies are kept in `/diagrams/` for ease of reproducability. Created using [textik](https://textik.com/#).
|
||||
## Template details
|
||||
|
||||
### Importing files and auto-generated command output
|
||||
|
||||
Example files are inserted as per normal with mdbook.
|
||||
|
||||
Some binary command outputs are generated using the [`cmdrun`](https://docs.rs/mdbook-cmdrun/latest/mdbook_cmdrun/) mdbook plugin.
|
||||
|
||||
## Building
|
||||
When working locally, it is recommended that you use `mdbook serve` to have a local version of the docs served on `localhost:3000`, with hot reloading on any changes made to files in the `src/` directory.
|
||||
|
||||
You can find other commands in the [mdBook CLI tool docs](https://rust-lang.github.io/mdBook/cli/index.html).
|
||||
|
||||
### I tried to edit files in `theme/` and they aren't taking effect / `mdbook serve` causes a looping reload on file changes after changing fields in `[preprocessor.theme]` config
|
||||
|
||||
Looping reload is a known issue with the `mdbook-theme` preprocessor used for the table of contents and layout of these docs. As outlined in the `mdbook-theme` [readme](https://github.com/zjp-CN/mdbook-theme#avoid-repeating-call-on-this-tool-when-mdbook-watch) one way to mitigate this is to set `turn-off = true` under `[preprocessor.theme]`. This means that `mdbook serve` or `mdbook watch` ignores changes to the `theme/` directory, which is the source of the looping reload. If you have changed or commented out this line, reintroduce it to remove the looping reload. If you are trying to edit the theme of the docs and want to apply the change, see [here](https://github.com/zjp-CN/mdbook-theme#avoid-repeating-call-on-this-tool-when-mdbook-watch) for more info on how to remove the block, change the theme, and reintroduce the block.
|
||||
|
||||
### Checking the mdBook version
|
||||
To check the version of mdBook installed on your system, you can use the `mdbook --version` command. This will print the version number of mdBook installed on your system in the terminal.
|
||||
|
||||
The latest release of the binary of the pre-compiled binaries can be found on [GitHub](https://github.com/rust-lang/mdBook/releases).
|
||||
This documentation was made with [Nextra](https://nextra.site) using the template from here https://github.com/shuding/nextra-docs-template.
|
||||
|
||||
@@ -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 (
|
||||
<ChainProvider
|
||||
chains={chainsFixedUp}
|
||||
assetLists={assetsFixedUp}
|
||||
wallets={[...ledger, ...keplr]}
|
||||
signerOptions={{
|
||||
preferredSignType: () => 'amino',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ChainProvider>
|
||||
);
|
||||
};
|
||||
|
||||
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<void>;
|
||||
}> = ({ wallet, disconnect, error, header, children }) => {
|
||||
if (error) {
|
||||
return (
|
||||
<Box mt={4} mb={2}>
|
||||
<Alert severity="error">
|
||||
{wallet && <AlertTitle>Failed to connect to {wallet.prettyName}</AlertTitle>}
|
||||
{wallet && walletRejectMessageOrError(wallet)}
|
||||
</Alert>
|
||||
<Box mt={2}>
|
||||
<Button variant="outlined" onClick={disconnect}>
|
||||
Retry
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box mt={4} mb={2}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
{header && header}
|
||||
<Button variant="outlined" onClick={disconnect}>
|
||||
Disconnect
|
||||
</Button>
|
||||
</Box>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const CosmosKitInner = () => {
|
||||
const {
|
||||
wallet,
|
||||
address,
|
||||
connect,
|
||||
disconnect,
|
||||
isWalletConnecting,
|
||||
isWalletDisconnected,
|
||||
isWalletError,
|
||||
isWalletNotExist,
|
||||
isWalletRejected,
|
||||
} = useChain('nyx');
|
||||
|
||||
if (isWalletError) {
|
||||
return <Wrapper wallet={wallet} error="Oh no! Something went wrong." disconnect={disconnect} />;
|
||||
}
|
||||
|
||||
if (isWalletNotExist) {
|
||||
return <Wrapper wallet={wallet} error="Oh no! Could not connect to that wallet." disconnect={disconnect} />;
|
||||
}
|
||||
|
||||
if (isWalletRejected) {
|
||||
return (
|
||||
<Wrapper
|
||||
wallet={wallet}
|
||||
error="Oh no! Did you authorize the connection to your wallet?"
|
||||
disconnect={disconnect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isWalletDisconnected) {
|
||||
return (
|
||||
<Button variant="outlined" onClick={connect} sx={{ mt: 4 }}>
|
||||
Connect your wallet
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (isWalletConnecting) {
|
||||
return <CircularProgress />;
|
||||
}
|
||||
|
||||
// Ledger Hardware Wallet
|
||||
if (wallet.mode === 'ledger') {
|
||||
return (
|
||||
<Wrapper
|
||||
header={
|
||||
<Box>
|
||||
<strong>Connected to {wallet.prettyName}</strong>
|
||||
<Typography>
|
||||
Address: <code>{address}</code>{' '}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
disconnect={disconnect}
|
||||
>
|
||||
<CosmosKitLedger />
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Extension or Wallet Connect
|
||||
return (
|
||||
<Wrapper
|
||||
header={
|
||||
<Box>
|
||||
<strong>Connected to {wallet.prettyName}</strong>
|
||||
<Typography>
|
||||
Address: <code>{address}</code>{' '}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
disconnect={disconnect}
|
||||
>
|
||||
<CosmosKitSign />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const CosmosKit = () => (
|
||||
<CosmosKitSetup>
|
||||
<CosmosKitInner />
|
||||
</CosmosKitSetup>
|
||||
);
|
||||
```
|
||||
@@ -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<string>(defaultUrl);
|
||||
const [html, setHtml] = useState<string>();
|
||||
const [busy, setBusy] = useState<boolean>(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 (
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
<Stack direction="row">
|
||||
<TextField
|
||||
disabled={busy}
|
||||
fullWidth
|
||||
label="URL"
|
||||
type="text"
|
||||
variant="outlined"
|
||||
defaultValue={defaultUrl}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
<Button variant="outlined" disabled={busy} sx={{ marginLeft: '1rem' }} onClick={handleFetch}>
|
||||
Fetch
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
{busy && (
|
||||
<Box mt={4}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
)}
|
||||
{html && (
|
||||
<>
|
||||
<Box mt={4}>
|
||||
<strong>Response</strong>
|
||||
</Box>
|
||||
<Paper sx={{ p: 2, mt: 1 }} elevation={4}>
|
||||
<Typography fontFamily="monospace" fontSize="small">
|
||||
{html}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -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<any>();
|
||||
|
||||
const getMixnodes = async () => {
|
||||
const client = await getClient();
|
||||
const { nodes } = await client.getMixNodesDetailed({});
|
||||
setMixnodes(nodes);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getMixnodes();
|
||||
}, []);
|
||||
|
||||
if (!mixnodes) {
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "1rem" }}>
|
||||
{mixnodes?.length &&
|
||||
mixnodes.map((mixnode: any) => (
|
||||
<Box className="codeBox" key={mixnode.bond_information.mix_id}>
|
||||
<span
|
||||
style={{ marginRight: "1rem" }}
|
||||
>{`id: ${mixnode.bond_information.mix_id}`}</span>
|
||||
<span>{`owner: ${mixnode.bond_information.owner}`}</span>
|
||||
</Box>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -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<NymMixnetClient>();
|
||||
const [selfAddress, setSelfAddress] = useState<string>();
|
||||
const [recipient, setRecipient] = useState<string>();
|
||||
const [payload, setPayload] = useState<Payload>();
|
||||
const [receivedMessage, setReceivedMessage] = useState<string>();
|
||||
|
||||
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 (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box padding={3}>
|
||||
<Paper style={{ marginTop: '1rem', padding: '2rem' }}>
|
||||
<Stack spacing={3}>
|
||||
<Typography variant="body1">My self address is:</Typography>
|
||||
<Typography variant="body1">{selfAddress || 'loading'}</Typography>
|
||||
<Typography variant="h5">Communication through the Mixnet</Typography>
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Recipient Address"
|
||||
onChange={(e) => setRecipient(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Message to send"
|
||||
multiline
|
||||
rows={4}
|
||||
onChange={(e) => setPayload({ message: e.target.value, mimeType: 'text/plain' })}
|
||||
size="small"
|
||||
/>
|
||||
<Button variant="outlined" onClick={() => send()} disabled={!payload || !recipient} sx={{width: 'fit-content'}}>
|
||||
Send
|
||||
</Button>
|
||||
</Stack>
|
||||
{receivedMessage && (
|
||||
<Stack spacing={3} style={{ marginTop: '1rem' }}>
|
||||
<Typography variant="h5">Message Received!</Typography>
|
||||
<Typography fontFamily="monospace">{receivedMessage}</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -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 (
|
||||
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
|
||||
<Typography variant="h5" textAlign="center">
|
||||
Connect to your account
|
||||
</Typography>
|
||||
<Box padding={3}>
|
||||
<Typography variant="h6">Your account</Typography>
|
||||
<Box marginY={3}>
|
||||
<Typography variant="body1" marginBottom={3}>
|
||||
Enter the mnemonic
|
||||
</Typography>
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="mnemonic"
|
||||
onChange={(e) => setMnemonic(e.target.value)}
|
||||
fullWidth
|
||||
multiline
|
||||
maxRows={4}
|
||||
sx={{ marginBottom: 3 }}
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => connect()}
|
||||
disabled={!mnemonic || accountLoading || clientLoading || balanceLoading}
|
||||
>
|
||||
{connectButtonText}
|
||||
</Button>
|
||||
</Box>
|
||||
{account && balance ? (
|
||||
<Box>
|
||||
<Typography variant="body1">Address: {account}</Typography>
|
||||
<Typography variant="body1">
|
||||
Balance: {balance?.amount} {balance?.denom}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box>
|
||||
<Typography variant="body1">Please, enter your mnemonic to receive your account information</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -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,
|
||||
<div key={JSON.stringify(res, null, 2)}>
|
||||
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
|
||||
<pre>{JSON.stringify(res, null, 2)}</pre>
|
||||
</div>,
|
||||
]);
|
||||
} 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,
|
||||
<div key={JSON.stringify(res, null, 2)}>
|
||||
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
|
||||
<pre>{JSON.stringify(res, null, 2)}</pre>
|
||||
</div>,
|
||||
]);
|
||||
} 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<string>();
|
||||
const [amountToBeDelegated, setAmountToBeDelegated] = useState<string>();
|
||||
|
||||
return (
|
||||
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
|
||||
<Box padding={3}>
|
||||
<Typography variant="h6">Delegations</Typography>
|
||||
<Box marginY={3}>
|
||||
<Box marginY={3} display="flex" flexDirection="column">
|
||||
<Typography marginBottom={3} variant="body1">
|
||||
Make a delegation
|
||||
</Typography>
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Mixnode ID"
|
||||
onChange={(e) => setDelegationNodeId(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<Box marginTop={3} display="flex" justifyContent="space-between">
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Amount"
|
||||
onChange={(e) => setAmountToBeDelegated(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() =>
|
||||
doDelegate({ mixId: parseInt(delegationNodeId, 10), amount: parseInt(amountToBeDelegated, 10) })
|
||||
}
|
||||
disabled={delegationLoader}
|
||||
>
|
||||
{delegationLoader ? 'Delegation in process...' : 'Delegate'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box marginTop={3}>
|
||||
<Typography variant="body1">Your delegations</Typography>
|
||||
<Box marginBottom={3} display="flex" flexDirection="column">
|
||||
{!delegations?.delegations?.length ? (
|
||||
<Typography>You do not have delegations</Typography>
|
||||
) : (
|
||||
<Box>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>MixId</TableCell>
|
||||
<TableCell>Owner</TableCell>
|
||||
<TableCell>Amount</TableCell>
|
||||
<TableCell>Cumulative Reward Ratio</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{delegations?.delegations.map((delegation: any) => (
|
||||
<TableRow key={delegation.mix_id}>
|
||||
<TableCell>{delegation.mix_id}</TableCell>
|
||||
<TableCell>{delegation.owner}</TableCell>
|
||||
<TableCell>{delegation.amount.amount}</TableCell>
|
||||
<TableCell>{delegation.cumulative_reward_ratio}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{delegations && (
|
||||
<Box marginBottom={3}>
|
||||
<Button variant="outlined" onClick={() => doUndelegateAll()} disabled={undeledationLoader}>
|
||||
{undeledationLoader ? 'Undelegating...' : 'Undelegate All'}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
<Button variant="outlined" onClick={() => doWithdrawRewards()} disabled={withdrawLoading}>
|
||||
{withdrawLoading ? 'Doing withdraw...' : 'Withdraw rewards'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
@@ -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,
|
||||
<div key={JSON.stringify(res, null, 2)}>
|
||||
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
|
||||
<pre>{JSON.stringify(res, null, 2)}</pre>
|
||||
</div>,
|
||||
]);
|
||||
} 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<string>();
|
||||
|
||||
return (
|
||||
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
|
||||
<Box padding={3}>
|
||||
<Typography variant="h6">Send Tokens</Typography>
|
||||
<Box marginTop={3} display="flex" flexDirection="column">
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Recipient Address"
|
||||
onChange={(e) => setRecipientAddress(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<Box marginY={3} display="flex" justifyContent="space-between">
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Amount"
|
||||
onChange={(e) => setTokensToSend(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<Button variant="outlined" onClick={() => doSendTokens(amount)} disabled={sendingTokensLoader}>
|
||||
{sendingTokensLoader ? 'Sending...' : 'SendTokens'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -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 (
|
||||
<Box padding={3}>
|
||||
<Box>
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Enter GitHub repo URL: https://github.com/nymtech/nym/"
|
||||
value={repoUrl}
|
||||
onChange={(e) => setRepoUrl(e.target.value)}
|
||||
size="small"
|
||||
sx={{width: "450px"}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleSearch}
|
||||
size="medium"
|
||||
sx={{ marginLeft: 2, marginTop: 0.2 }}
|
||||
>
|
||||
Check mixFetch
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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 (
|
||||
<ChainProvider
|
||||
chains={chainsFixedUp}
|
||||
assetLists={assetsFixedUp}
|
||||
wallets={[...ledger, ...keplr]}
|
||||
signerOptions={{
|
||||
preferredSignType: () => 'amino',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ChainProvider>
|
||||
);
|
||||
};
|
||||
|
||||
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<void>;
|
||||
}> = ({ wallet, disconnect, error, header, children }) => {
|
||||
if (error) {
|
||||
return (
|
||||
<Box mt={4} mb={2}>
|
||||
<Alert severity="error">
|
||||
{wallet && <AlertTitle>Failed to connect to {wallet.prettyName}</AlertTitle>}
|
||||
{wallet && walletRejectMessageOrError(wallet)}
|
||||
</Alert>
|
||||
<Box mt={2}>
|
||||
<Button variant="outlined" onClick={disconnect}>
|
||||
Retry
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box mt={4} mb={2}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
{header && header}
|
||||
<Button variant="outlined" onClick={disconnect}>
|
||||
Disconnect
|
||||
</Button>
|
||||
</Box>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const CosmosKitInner = () => {
|
||||
const {
|
||||
wallet,
|
||||
address,
|
||||
connect,
|
||||
disconnect,
|
||||
isWalletConnecting,
|
||||
isWalletDisconnected,
|
||||
isWalletError,
|
||||
isWalletNotExist,
|
||||
isWalletRejected,
|
||||
} = useChain('nyx');
|
||||
|
||||
if (isWalletError) {
|
||||
return <Wrapper wallet={wallet} error="Oh no! Something went wrong." disconnect={disconnect} />;
|
||||
}
|
||||
|
||||
if (isWalletNotExist) {
|
||||
return <Wrapper wallet={wallet} error="Oh no! Could not connect to that wallet." disconnect={disconnect} />;
|
||||
}
|
||||
|
||||
if (isWalletRejected) {
|
||||
return (
|
||||
<Wrapper
|
||||
wallet={wallet}
|
||||
error="Oh no! Did you authorize the connection to your wallet?"
|
||||
disconnect={disconnect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isWalletDisconnected) {
|
||||
return (
|
||||
<Button variant="outlined" onClick={connect} sx={{ mt: 4 }}>
|
||||
Connect your wallet
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (isWalletConnecting) {
|
||||
return <CircularProgress />;
|
||||
}
|
||||
|
||||
// Ledger Hardware Wallet
|
||||
if (wallet.mode === 'ledger') {
|
||||
return (
|
||||
<Wrapper
|
||||
header={
|
||||
<Box>
|
||||
<strong>Connected to {wallet.prettyName}</strong>
|
||||
<Typography>
|
||||
Address: <code>{address}</code>{' '}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
disconnect={disconnect}
|
||||
>
|
||||
<CosmosKitLedger />
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Extension or Wallet Connect
|
||||
return (
|
||||
<Wrapper
|
||||
header={
|
||||
<Box>
|
||||
<strong>Connected to {wallet.prettyName}</strong>
|
||||
<Typography>
|
||||
Address: <code>{address}</code>{' '}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
disconnect={disconnect}
|
||||
>
|
||||
<CosmosKitSign />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const CosmosKit = () => (
|
||||
<CosmosKitSetup>
|
||||
<CosmosKitInner />
|
||||
</CosmosKitSetup>
|
||||
);
|
||||
@@ -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<any>();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
|
||||
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 (
|
||||
<Box mt={4} mb={2}>
|
||||
<LinearProgress color="success" />
|
||||
<Alert severity="success">
|
||||
<AlertTitle>Please approve on the Ledger</AlertTitle>
|
||||
Follow the instructions on the Ledger to review the message
|
||||
</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!signResponse && (
|
||||
<Box mt={4} mb={2}>
|
||||
<Box mb={2}>Click the button below to sign a fake request with your Ledger</Box>
|
||||
<Button variant="outlined" onClick={handleSign}>
|
||||
Click to sign
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
{signResponse && (
|
||||
<Box mt={2}>
|
||||
<strong>Signature:</strong>
|
||||
<Box sx={{ overflowX: 'auto' }}>
|
||||
<pre>{JSON.stringify(signResponse?.signature, null, 2)}</pre>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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<any>();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
|
||||
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 (
|
||||
<Box mt={4} mb={2}>
|
||||
<LinearProgress color="success" />
|
||||
<Alert severity="success">
|
||||
<AlertTitle>Please approve in your wallet</AlertTitle>
|
||||
Review the message to sign
|
||||
</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!signResponse && (
|
||||
<Box mt={4} mb={2}>
|
||||
<Box mb={2}>Click the button below to sign a fake request with your wallet</Box>
|
||||
<Button variant="outlined" onClick={handleSign}>
|
||||
Click to sign
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
{signResponse && (
|
||||
<Box mt={2}>
|
||||
<strong>Signature:</strong>
|
||||
<Box sx={{ overflowX: 'auto' }}>
|
||||
<pre>{JSON.stringify(signResponse?.signature, null, 2)}</pre>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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 = () => (
|
||||
<Stack direction="row" spacing={3}>
|
||||
{links.map((link) => (
|
||||
<a key={link[1]} href={link[1]}>
|
||||
{link[0]}
|
||||
</a>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
@@ -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<string>(defaultUrl);
|
||||
const [html, setHtml] = useState<string>();
|
||||
const [busy, setBusy] = useState<boolean>(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 (
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
<Stack direction="row">
|
||||
<TextField
|
||||
disabled={busy}
|
||||
fullWidth
|
||||
label="URL"
|
||||
type="text"
|
||||
variant="outlined"
|
||||
defaultValue={defaultUrl}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
<Button variant="outlined" disabled={busy} sx={{ marginLeft: '1rem' }} onClick={handleFetch}>
|
||||
Fetch
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
{busy && (
|
||||
<Box mt={4}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
)}
|
||||
{html && (
|
||||
<>
|
||||
<Box mt={4}>
|
||||
<strong>Response</strong>
|
||||
</Box>
|
||||
<Paper sx={{ p: 2, mt: 1 }} elevation={4}>
|
||||
<Typography fontFamily="monospace" fontSize="small">
|
||||
{html}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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<any>();
|
||||
const [busy, setBusy] = useState<boolean>(false);
|
||||
|
||||
const getMixnodes = async () => {
|
||||
setBusy(true);
|
||||
const client = await getClient();
|
||||
const { nodes } = await client.getMixNodesDetailed({});
|
||||
|
||||
setMixnodes(nodes);
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
if (busy) {
|
||||
return (
|
||||
<Box pt={4}>
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<CircularProgress />
|
||||
<Typography>Loading...</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (!mixnodes) {
|
||||
return (
|
||||
<Box pt={4}>
|
||||
<Button variant="outlined" onClick={getMixnodes}>
|
||||
Query for mixnodes
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box pt={4}>
|
||||
{mixnodes?.length && (
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>MixId</TableCell>
|
||||
<TableCell>Owner Account</TableCell>
|
||||
<TableCell>Layer</TableCell>
|
||||
<TableCell>Bonded at Block Height</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{mixnodes.map((mixnode: any) => (
|
||||
<TableRow key={mixnode.bond_information.mix_id}>
|
||||
<TableCell>{mixnode.bond_information.mix_id}</TableCell>
|
||||
<TableCell>{mixnode.bond_information.owner}</TableCell>
|
||||
<TableCell>{mixnode.bond_information.layer}</TableCell>
|
||||
<TableCell>{mixnode.bond_information.bonding_height}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
}) => (
|
||||
<Link
|
||||
href={`https://www.npmjs.com/package/${packageName}`}
|
||||
target="_blank"
|
||||
sx={{ whiteSpace: 'nowrap', textDecoration: 'none' }}
|
||||
>
|
||||
{packageName} <Chip label={kind === 'cjs' ? 'CommonJS' : 'ESM'} size="small" />{' '}
|
||||
{preBundled && <Chip label="pre-bundled" size="small" color="info" className="chipContained" />}
|
||||
</Link>
|
||||
);
|
||||
@@ -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<NymMixnetClient>();
|
||||
const [selfAddress, setSelfAddress] = useState<string>();
|
||||
const [recipient, setRecipient] = useState<string>();
|
||||
const [payload, setPayload] = useState<Payload>();
|
||||
const [receivedMessage, setReceivedMessage] = useState<string>();
|
||||
const [buttonEnabled, setButtonEnabled] = useState<boolean>(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 (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box padding={3}>
|
||||
<Paper style={{ marginTop: '1rem', padding: '2rem' }}>
|
||||
<Stack spacing={3}>
|
||||
<Typography variant="body1">My self address is:</Typography>
|
||||
<Typography variant="body1">{selfAddress || 'loading'}</Typography>
|
||||
<Typography variant="h5">Communication through the Mixnet</Typography>
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Recipient Address"
|
||||
onChange={(e) => setRecipient(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Message to send"
|
||||
multiline
|
||||
rows={4}
|
||||
onChange={(e) => setPayload({ message: e.target.value, mimeType: 'text/plain' })}
|
||||
size="small"
|
||||
/>
|
||||
<Button variant="outlined" onClick={() => send()} disabled={!buttonEnabled} sx={{ width: 'fit-content' }}>
|
||||
Send
|
||||
</Button>
|
||||
</Stack>
|
||||
{receivedMessage && (
|
||||
<Stack spacing={3} style={{ marginTop: '1rem' }}>
|
||||
<Typography variant="h5">Message Received!</Typography>
|
||||
<Typography fontFamily="monospace">{receivedMessage}</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -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<string>();
|
||||
const [connectButtonText, setConnectButtonText] = useState<string>('Connect');
|
||||
|
||||
useEffect(() => {
|
||||
if (accountLoading || clientsAreLoading || balanceLoading) {
|
||||
setConnectButtonText('Loading...');
|
||||
} else if (balance) {
|
||||
setConnectButtonText('Connected');
|
||||
}
|
||||
setConnectButtonText('Connect');
|
||||
}, [accountLoading, clientsAreLoading, balanceLoading]);
|
||||
|
||||
return (
|
||||
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
|
||||
<Typography variant="h5" textAlign="center">
|
||||
Connect to your testnet account
|
||||
</Typography>
|
||||
<Box padding={3}>
|
||||
<Typography variant="h6">Your testnet account:</Typography>
|
||||
<Box marginY={3}>
|
||||
<Typography variant="body1" marginBottom={3}>
|
||||
Enter the mnemonic
|
||||
</Typography>
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="mnemonic"
|
||||
onChange={(e) => setMnemonic(e.target.value)}
|
||||
fullWidth
|
||||
multiline
|
||||
maxRows={4}
|
||||
sx={{ marginBottom: 3 }}
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => connect(mnemonic)}
|
||||
disabled={!mnemonic || accountLoading || clientsAreLoading || balanceLoading}
|
||||
>
|
||||
{connectButtonText}
|
||||
</Button>
|
||||
</Box>
|
||||
{account && balance ? (
|
||||
<Box>
|
||||
<Typography variant="body1">Address: {account}</Typography>
|
||||
<Typography variant="body1">
|
||||
Balance: {balance?.amount} {balance?.denom}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box>
|
||||
<Typography variant="body1">Please, enter your mnemonic to receive your account information</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
@@ -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<string>();
|
||||
const [amountToBeDelegated, setAmountToBeDelegated] = useState<string>();
|
||||
const [infoText, setInfoText] = useState<string>('');
|
||||
|
||||
const cleanFields = () => {
|
||||
setDelegationNodeId('');
|
||||
setAmountToBeDelegated('');
|
||||
setInfoText('');
|
||||
};
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
cleanFields();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
|
||||
<Box padding={3}>
|
||||
<Typography variant="h6">Delegations</Typography>
|
||||
<Box marginY={3}>
|
||||
<Box marginY={3} display="flex" flexDirection="column">
|
||||
<Typography marginBottom={3} variant="body1">
|
||||
Make a delegation
|
||||
</Typography>
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Mixnode ID"
|
||||
onChange={(e) => setDelegationNodeId(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<Box marginTop={3} display="flex" justifyContent="space-between">
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Amount"
|
||||
onChange={(e) => setAmountToBeDelegated(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
doDelegate(delegationNodeId, amountToBeDelegated);
|
||||
setInfoText('Changes will be visible after the next epoch');
|
||||
cleanFields();
|
||||
}}
|
||||
disabled={delegationLoader}
|
||||
>
|
||||
{delegationLoader ? 'Delegation in process...' : 'Delegate'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box marginTop={3}>
|
||||
<Typography variant="body1">Your delegations:</Typography>
|
||||
<Box marginBottom={3} display="flex" flexDirection="column">
|
||||
{!delegations?.delegations?.length ? (
|
||||
<Typography variant="body2">You do not have delegations</Typography>
|
||||
) : (
|
||||
<Box overflow="auto">
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>MixId</TableCell>
|
||||
<TableCell>Owner</TableCell>
|
||||
<TableCell>Amount</TableCell>
|
||||
<TableCell>Cumulative Reward Ratio</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{delegations?.delegations.map((delegation: any) => (
|
||||
<TableRow key={delegation.mix_id}>
|
||||
<TableCell>{delegation.mix_id}</TableCell>
|
||||
<TableCell>{delegation.owner}</TableCell>
|
||||
<TableCell>{delegation.amount.amount}</TableCell>
|
||||
<TableCell>{delegation.cumulative_reward_ratio}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{delegations?.delegations.length > 0 && (
|
||||
<Box marginBottom={3}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
unDelegateAll();
|
||||
setInfoText('Changes will be visible after the next epoch');
|
||||
}}
|
||||
disabled={unDelegateAllLoading}
|
||||
>
|
||||
Undelegate All
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{infoText && <Alert severity="info">{infoText}</Alert>}
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
{log?.node?.length > 0 && log.type === 'delegate' && (
|
||||
<Box marginTop={3}>
|
||||
<Typography variant="h5">Transaction Logs:</Typography>
|
||||
{log.node}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -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<string>();
|
||||
const [tokensToSend, setTokensToSend] = useState<string>();
|
||||
|
||||
const cleanFields = () => {
|
||||
setRecipientAddress('');
|
||||
setTokensToSend('');
|
||||
};
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
cleanFields();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
|
||||
<Box padding={3}>
|
||||
<Typography variant="h6">Send Tokens</Typography>
|
||||
<Box marginTop={3} display="flex" flexDirection="column">
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Recipient Address"
|
||||
onChange={(e) => setRecipientAddress(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<Box marginY={3} display="flex" justifyContent="space-between">
|
||||
<TextField
|
||||
type="text"
|
||||
placeholder="Amount"
|
||||
onChange={(e) => setTokensToSend(e.target.value)}
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
sendTokens(recipientAddress, tokensToSend);
|
||||
cleanFields();
|
||||
}}
|
||||
disabled={sendingTokensLoading}
|
||||
>
|
||||
{sendingTokensLoading ? 'Sending...' : 'Send tokens'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{log?.node?.length > 0 && log.type === 'sendTokens' && (
|
||||
<Box marginTop={3}>
|
||||
<Typography variant="h5">Transaction Logs:</Typography>
|
||||
{log.node}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -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<WalletState>({
|
||||
accountLoading: false,
|
||||
account: '',
|
||||
clientsAreLoading: false,
|
||||
balanceLoading: false,
|
||||
sendingTokensLoading: false,
|
||||
});
|
||||
|
||||
export const useWalletContext = (): React.ContextType<typeof WalletContext> => useContext<WalletState>(WalletContext);
|
||||
|
||||
export const WalletContextProvider = ({ children }: { children: JSX.Element }) => {
|
||||
const [cosmWasmSignerClient, setCosmWasmSignerClient] = useState<SigningCosmWasmClient>(null);
|
||||
const [nymWasmSignerClient, setNymWasmSignerClient] = useState<any>(null);
|
||||
const [account, setAccount] = useState<string>('');
|
||||
const [accountLoading, setAccountLoading] = useState<boolean>(false);
|
||||
const [delegations, setDelegations] = useState<{ delegations: any[]; start_next_after: any }>();
|
||||
const [clientsAreLoading, setClientsAreLoading] = useState<boolean>(false);
|
||||
const [balance, setBalance] = useState<Coin>(null);
|
||||
const [balanceLoading, setBalanceLoading] = useState<boolean>(false);
|
||||
const [sendingTokensLoading, setSendingTokensLoading] = useState<boolean>(false);
|
||||
const [log, setLog] = useState<{ type: 'delegate' | 'sendTokens'; node: React.ReactNode[] }>();
|
||||
const [delegationLoader, setDelegationLoader] = useState<boolean>(false);
|
||||
const [unDelegateAllLoading, setUnDelegateAllLoading] = useState<boolean>(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: [
|
||||
<div key={JSON.stringify(res, null, 2)}>
|
||||
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
|
||||
<pre>{JSON.stringify(res, null, 2)}</pre>
|
||||
</div>,
|
||||
],
|
||||
});
|
||||
} 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: [
|
||||
<div key={JSON.stringify(res, null, 2)}>
|
||||
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
|
||||
<pre>{JSON.stringify(res, null, 2)}</pre>
|
||||
</div>,
|
||||
],
|
||||
});
|
||||
} 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(
|
||||
<div key={JSON.stringify(res, null, 2)}>
|
||||
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
|
||||
<pre>{JSON.stringify(res, null, 2)}</pre>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
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: [
|
||||
// <div key={JSON.stringify(res, null, 2)}>
|
||||
// <code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
|
||||
// <pre>{JSON.stringify(res, null, 2)}</pre>
|
||||
// </div>,
|
||||
// ],
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error(error);
|
||||
// }
|
||||
// setWithdrawLoading(false);
|
||||
// };
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
Reset();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (cosmWasmSignerClient) {
|
||||
getBalance();
|
||||
}
|
||||
}, [cosmWasmSignerClient]);
|
||||
|
||||
useEffect(() => {
|
||||
if (nymWasmSignerClient) {
|
||||
getDelegations();
|
||||
}
|
||||
}, [nymWasmSignerClient]);
|
||||
|
||||
const state = useMemo<WalletState>(
|
||||
() => ({
|
||||
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 <WalletContext.Provider value={state}>{children}</WalletContext.Provider>;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
@@ -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;
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@nymproject/docs",
|
||||
"version": "1.0.0",
|
||||
"description": "Nym Docs Monorepo",
|
||||
"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
|
||||
}
|
||||
@@ -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<AppProps> = ({ Component, pageProps }) => {
|
||||
const muiTheme = useMemo(
|
||||
() =>
|
||||
createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
main: '#e67300',
|
||||
},
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const AnyComponent = Component as any;
|
||||
return (
|
||||
<ThemeProvider theme={muiTheme}>
|
||||
<AnyComponent {...pageProps} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyApp;
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"network": {
|
||||
"title": "Network",
|
||||
"type": "page"
|
||||
},
|
||||
"developers": {
|
||||
"title": "Developers",
|
||||
"type": "page"
|
||||
},
|
||||
"sdk": {
|
||||
"title": "SDKs",
|
||||
"type": "menu",
|
||||
"items": {
|
||||
"rust": {
|
||||
"title": "Rust SDK",
|
||||
"href": "/sdk/rust"
|
||||
},
|
||||
"typescript": {
|
||||
"title": "Typescript SDK",
|
||||
"href": "/sdk/typescript"
|
||||
}
|
||||
}
|
||||
},
|
||||
"operators": {
|
||||
"title": "Operators",
|
||||
"type": "page"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"network": {
|
||||
"title": "Network",
|
||||
"type": "page"
|
||||
},
|
||||
"developers": {
|
||||
"title": "Developers",
|
||||
"type": "menu",
|
||||
"items": {
|
||||
"concepts": {
|
||||
"title": "Core Concepts",
|
||||
"href": "/developers/concepts"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integrations",
|
||||
"href": "/developers/integrations"
|
||||
},
|
||||
"tools": {
|
||||
"title": "Tools",
|
||||
"href": "/developers/tools"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sdk": {
|
||||
"title": "SDKs",
|
||||
"type": "menu",
|
||||
"items": {
|
||||
"rust": {
|
||||
"title": "Rust SDK",
|
||||
"href": "/sdk/rust"
|
||||
},
|
||||
"typescript": {
|
||||
"title": "Typescript SDK",
|
||||
"href": "/sdk/typescript"
|
||||
}
|
||||
}
|
||||
},
|
||||
"operators": {
|
||||
"title": "Operators",
|
||||
"type": "page"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"concepts": "Core Concepts",
|
||||
"integrations": "Integrations",
|
||||
"tools": "Tools",
|
||||
"---": {
|
||||
"type": "separator"
|
||||
},
|
||||
"licensing": "Licensing"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
stub
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"overview": "Overview",
|
||||
"messages": "Message Based Paradigm",
|
||||
"abstractions": "Connection Abstractions"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
stub
|
||||
@@ -0,0 +1 @@
|
||||
stub
|
||||
@@ -0,0 +1 @@
|
||||
stub
|
||||
@@ -0,0 +1,3 @@
|
||||
# Tools
|
||||
|
||||
There are a few tools available to developers for chain interaction: the `nym-cli` tool, which operates as an easier-to-use wrapper around `nyxd`, and the CLI wallet.
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"nym-cli": "NYM-CLI tool",
|
||||
"cli-wallet": "CLI wallet"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
This should be the landing page with w/ever nav we want
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"index": "Introduction",
|
||||
"concepts": "Core Concepts",
|
||||
"architecture": "Architecture",
|
||||
"traffic": "Traffic Flow",
|
||||
"cryptography": "Crypto",
|
||||
"rewards": "Rewards",
|
||||
"---": {
|
||||
"type": "separator"
|
||||
},
|
||||
"licensing": "Licencing"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 383 KiB After Width: | Height: | Size: 383 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 20 MiB After Width: | Height: | Size: 20 MiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 446 KiB After Width: | Height: | Size: 446 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 432 B After Width: | Height: | Size: 432 B |
|
Before Width: | Height: | Size: 339 KiB After Width: | Height: | Size: 339 KiB |
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |