Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cd59a22b6 | |||
| ef06615ae3 | |||
| 8d468ffcc4 | |||
| 298779f6ea | |||
| 39f519b427 | |||
| b03708bf74 | |||
| 6aa448a191 | |||
| 7af5c4752a | |||
| 3931b7159e | |||
| 72f059227c | |||
| 39856e58b6 | |||
| cee16cd957 | |||
| 097ca5989f | |||
| 36781eaf15 | |||
| d6fd92094e | |||
| 448f52c4ea | |||
| e193e6728a | |||
| cbbe95e93c | |||
| b7a0cd81ec | |||
| ded45f15af | |||
| d2298757d7 | |||
| b17b3dcd44 | |||
| 9b97d22a4b | |||
| 04b311b75f | |||
| 6f2aeb3f48 | |||
| 02b812d0a3 | |||
| 8d9d790685 | |||
| a2d1aa626a | |||
| 5e1b5dce61 | |||
| ee8d4ab9e7 | |||
| d5272057b8 | |||
| 593a6efbe7 | |||
| 784501f357 | |||
| 07b75d48d5 | |||
| 786dcbd55f | |||
| 26efbc975e | |||
| 7beaf57376 | |||
| 8c00f7d26d | |||
| d1bd782fb2 | |||
| 9ca55bd046 | |||
| a4d6a8cef0 | |||
| 98ec346d74 | |||
| 80a2c52ac6 |
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
@@ -1,384 +0,0 @@
|
||||
```ts copy filename="WalletSigningClientExample.tsx"
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { contracts } from '@nymproject/contract-clients';
|
||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
|
||||
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
|
||||
import { Coin, GasPrice } from '@cosmjs/stargate';
|
||||
import Button from '@mui/material/Button';
|
||||
import Input from '@mui/material/Input';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Box from '@mui/material/Box';
|
||||
import { TableBody, TableCell, TableHead, TableRow, TextField, Typography } from '@mui/material';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Table from '@mui/material/Table';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import { settings } from './client';
|
||||
|
||||
const signerAccount = async (mnemonic) => {
|
||||
const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
|
||||
prefix: 'n',
|
||||
});
|
||||
|
||||
return signer;
|
||||
};
|
||||
|
||||
const fetchSignerCosmosWasmClient = async (mnemonic) => {
|
||||
const signer = await signerAccount(mnemonic);
|
||||
|
||||
const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, {
|
||||
gasPrice: GasPrice.fromString('0.025unym'),
|
||||
});
|
||||
|
||||
return cosmWasmClient;
|
||||
};
|
||||
|
||||
const fetchSignerClient = async (mnemonic) => {
|
||||
const signer = await signerAccount(mnemonic);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
export const Wallet = () => {
|
||||
const [mnemonic, setMnemonic] = useState<string>();
|
||||
const [signerCosmosWasmClient, setSignerCosmosWasmClient] = useState<any>();
|
||||
const [signerClient, setSignerClient] = useState<any>();
|
||||
const [account, setAccount] = useState<string>();
|
||||
const [accountLoading, setAccountLoading] = useState<boolean>(false);
|
||||
const [clientLoading, setClientLoading] = useState<boolean>(false);
|
||||
const [balance, setBalance] = useState<Coin>();
|
||||
const [balanceLoading, setBalanceLoading] = useState<boolean>(false);
|
||||
const [log, setLog] = useState<React.ReactNode[]>([]);
|
||||
const [tokensToSend, setTokensToSend] = useState<string>();
|
||||
const [sendingTokensLoader, setSendingTokensLoader] = useState<boolean>(false);
|
||||
const [delegations, setDelegations] = useState<any>();
|
||||
const [recipientAddress, setRecipientAddress] = useState<string>('');
|
||||
const [delegationNodeId, setDelegationNodeId] = useState<string>();
|
||||
const [amountToBeDelegated, setAmountToBeDelegated] = useState<string>();
|
||||
const [delegationLoader, setDelegationLoader] = useState<boolean>(false);
|
||||
const [undeledationLoader, setUndeledationLoader] = useState<boolean>(false);
|
||||
const [withdrawLoading, setWithdrawLoading] = useState<boolean>(false);
|
||||
|
||||
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 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);
|
||||
};
|
||||
|
||||
const getClients = async () => {
|
||||
setClientLoading(true);
|
||||
try {
|
||||
setSignerCosmosWasmClient(await fetchSignerCosmosWasmClient(mnemonic));
|
||||
setSignerClient(await fetchSignerClient(mnemonic));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
setClientLoading(false);
|
||||
};
|
||||
|
||||
const getDelegations = useCallback(async () => {
|
||||
const newDelegations = await signerClient.getDelegatorDelegations({
|
||||
delegator: settings.address,
|
||||
});
|
||||
setDelegations(newDelegations);
|
||||
}, [signerClient]);
|
||||
|
||||
const connect = () => {
|
||||
getSignerAccount();
|
||||
getClients();
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
// End delegate
|
||||
|
||||
// Sending tokens
|
||||
const doSendTokens = async () => {
|
||||
const memo = 'test sending tokens';
|
||||
setSendingTokensLoader(true);
|
||||
try {
|
||||
const res = await signerCosmosWasmClient.sendTokens(
|
||||
account,
|
||||
recipientAddress,
|
||||
[{ amount: tokensToSend, 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);
|
||||
};
|
||||
// End send tokens
|
||||
|
||||
// Withdraw Rewards
|
||||
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);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (account && signerCosmosWasmClient) {
|
||||
if (!balance) {
|
||||
setBalanceLoading(true);
|
||||
getBalance();
|
||||
setBalanceLoading(false);
|
||||
}
|
||||
}
|
||||
}, [account, signerCosmosWasmClient, balance, getBalance]);
|
||||
|
||||
useEffect(() => {
|
||||
if (signerClient && !delegations) {
|
||||
console.log('getDelegations');
|
||||
getDelegations();
|
||||
}
|
||||
}, [signerClient, getDelegations, delegations]);
|
||||
|
||||
return (
|
||||
<Box padding={3}>
|
||||
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
|
||||
<Typography variant="h5" textAlign="center">
|
||||
Basic Wallet
|
||||
</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}
|
||||
>
|
||||
{accountLoading || clientLoading ? 'Loading...' : !balanceLoading ? 'Connect' : 'Connected'}
|
||||
</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 nemonic to receive your account info</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Divider />
|
||||
<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()} disabled={sendingTokensLoader}>
|
||||
{sendingTokensLoader ? 'Sending...' : 'SendTokens'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Divider />
|
||||
<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>
|
||||
|
||||
<Box marginTop={3}>
|
||||
<Typography variant="h5">Transaction Logs:</Typography>
|
||||
{log}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -12,6 +12,6 @@ export const NPMLink: FC<{ packageName: string; kind: 'esm' | 'cjs'; preBundled?
|
||||
sx={{ whiteSpace: 'nowrap', textDecoration: 'none' }}
|
||||
>
|
||||
{packageName} <Chip label={kind === 'cjs' ? 'CommonJS' : 'ESM'} size="small" />{' '}
|
||||
{preBundled && <Chip label="pre-bundled" size="small" color="info" />}
|
||||
{preBundled && <Chip label="pre-bundled" size="small" color="info" className="chipContained" />}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -50,7 +50,7 @@ export const Traffic = () => {
|
||||
await nym?.client.stop();
|
||||
};
|
||||
|
||||
const send = () => nym.client.send({ payload, recipient });
|
||||
const send = () => payload && recipient && nym?.client.send({ payload, recipient });
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
|
||||
@@ -3,13 +3,12 @@ import { contracts } from '@nymproject/contract-clients';
|
||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
|
||||
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
|
||||
import { Coin, GasPrice } from '@cosmjs/stargate';
|
||||
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 Divider from '@mui/material/Divider';
|
||||
import Table from '@mui/material/Table';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { settings } from './client';
|
||||
import { ConnectWallet } from './wallet/connect';
|
||||
import { SendTokes } from './wallet/sendTokens';
|
||||
import { Delegations } from './wallet/delegations';
|
||||
|
||||
const signerAccount = async (mnemonic) => {
|
||||
// create a wallet to sign transactions with the mnemonic
|
||||
@@ -56,7 +55,7 @@ const fetchSignerClient = async (mnemonic) => {
|
||||
return mixnetClient;
|
||||
};
|
||||
|
||||
export const Wallet = () => {
|
||||
export const Wallet = ({ type }: { type: 'connect' | 'sendTokens' | 'delegations' }) => {
|
||||
const [mnemonic, setMnemonic] = useState<string>();
|
||||
const [signerCosmosWasmClient, setSignerCosmosWasmClient] = useState<any>();
|
||||
const [signerClient, setSignerClient] = useState<any>();
|
||||
@@ -66,12 +65,9 @@ export const Wallet = () => {
|
||||
const [balance, setBalance] = useState<Coin>();
|
||||
const [balanceLoading, setBalanceLoading] = useState<boolean>(false);
|
||||
const [log, setLog] = useState<React.ReactNode[]>([]);
|
||||
const [tokensToSend, setTokensToSend] = useState<string>();
|
||||
const [sendingTokensLoader, setSendingTokensLoader] = useState<boolean>(false);
|
||||
const [delegations, setDelegations] = useState<any>();
|
||||
const [recipientAddress, setRecipientAddress] = useState<string>('');
|
||||
const [delegationNodeId, setDelegationNodeId] = useState<string>();
|
||||
const [amountToBeDelegated, setAmountToBeDelegated] = useState<string>();
|
||||
const [delegationLoader, setDelegationLoader] = useState<boolean>(false);
|
||||
const [undeledationLoader, setUndeledationLoader] = useState<boolean>(false);
|
||||
const [withdrawLoading, setWithdrawLoading] = useState<boolean>(false);
|
||||
@@ -125,6 +121,7 @@ export const Wallet = () => {
|
||||
getClients();
|
||||
};
|
||||
|
||||
// Start Undelgate All
|
||||
const doUndelegateAll = async () => {
|
||||
if (!signerClient) {
|
||||
return;
|
||||
@@ -141,7 +138,9 @@ export const Wallet = () => {
|
||||
}
|
||||
setUndeledationLoader(false);
|
||||
};
|
||||
// End Undelgate All
|
||||
|
||||
// Start Delegate
|
||||
const doDelegate = async ({ mixId, amount }: { mixId: number; amount: number }) => {
|
||||
if (!signerClient) {
|
||||
return;
|
||||
@@ -167,14 +166,14 @@ export const Wallet = () => {
|
||||
// End delegate
|
||||
|
||||
// Sending tokens
|
||||
const doSendTokens = async () => {
|
||||
const doSendTokens = async (amount: string) => {
|
||||
const memo = 'test sending tokens';
|
||||
setSendingTokensLoader(true);
|
||||
try {
|
||||
const res = await signerCosmosWasmClient.sendTokens(
|
||||
account,
|
||||
recipientAddress,
|
||||
[{ amount: tokensToSend, denom: 'unym' }],
|
||||
[{ amount, denom: 'unym' }],
|
||||
'auto',
|
||||
memo,
|
||||
);
|
||||
@@ -192,7 +191,7 @@ export const Wallet = () => {
|
||||
};
|
||||
// End send tokens
|
||||
|
||||
// Withdraw Rewards
|
||||
// Start Withdraw Rewards
|
||||
const doWithdrawRewards = async () => {
|
||||
const delegatorAddress = '';
|
||||
const validatorAdress = '';
|
||||
@@ -212,6 +211,7 @@ export const Wallet = () => {
|
||||
}
|
||||
setWithdrawLoading(false);
|
||||
};
|
||||
// End Withdraw Rewards
|
||||
|
||||
useEffect(() => {
|
||||
if (account && signerCosmosWasmClient) {
|
||||
@@ -241,152 +241,43 @@ export const Wallet = () => {
|
||||
|
||||
return (
|
||||
<Box padding={3}>
|
||||
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
|
||||
<Typography variant="h5" textAlign="center">
|
||||
Basic Wallet
|
||||
</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 nemonic to receive your account info</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{type === 'connect' && (
|
||||
<ConnectWallet
|
||||
setMnemonic={setMnemonic}
|
||||
connect={connect}
|
||||
mnemonic={mnemonic}
|
||||
accountLoading={accountLoading}
|
||||
clientLoading={clientLoading}
|
||||
balanceLoading={balanceLoading}
|
||||
account={account}
|
||||
balance={balance}
|
||||
connectButtonText={connectButtonText}
|
||||
/>
|
||||
)}
|
||||
{type === 'sendTokens' && (
|
||||
<SendTokes
|
||||
setRecipientAddress={setRecipientAddress}
|
||||
doSendTokens={doSendTokens}
|
||||
sendingTokensLoader={sendingTokensLoader}
|
||||
/>
|
||||
)}
|
||||
{type === 'delegations' && (
|
||||
<Delegations
|
||||
delegations={delegations}
|
||||
doDelegate={doDelegate}
|
||||
delegationLoader={delegationLoader}
|
||||
undeledationLoader={undeledationLoader}
|
||||
doUndelegateAll={doUndelegateAll}
|
||||
doWithdrawRewards={doWithdrawRewards}
|
||||
withdrawLoading={withdrawLoading}
|
||||
/>
|
||||
)}
|
||||
{log.length > 0 && (
|
||||
<Box marginTop={3}>
|
||||
<Typography variant="h5">Transaction Logs:</Typography>
|
||||
{log}
|
||||
</Box>
|
||||
<Divider />
|
||||
<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()} disabled={sendingTokensLoader}>
|
||||
{sendingTokensLoader ? 'Sending...' : 'SendTokens'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Divider />
|
||||
<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>
|
||||
|
||||
<Box marginTop={3}>
|
||||
<Typography variant="h5">Transaction Logs:</Typography>
|
||||
{log}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
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';
|
||||
|
||||
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 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()}
|
||||
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,108 @@
|
||||
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,3 @@
|
||||
export { ConnectWallet } from './connect';
|
||||
export { SendTokes } from './sendTokens';
|
||||
export { Delegations } from './delegations';
|
||||
@@ -0,0 +1,45 @@
|
||||
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';
|
||||
|
||||
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(tokensToSend)} disabled={sendingTokensLoader}>
|
||||
{sendingTokensLoader ? 'Sending...' : 'SendTokens'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nymproject/ts-sdk-docs",
|
||||
"version": "1.2.0-rc.9",
|
||||
"version": "1.2.0-rc.10",
|
||||
"description": "Nym Typescript SDK Docs",
|
||||
"license": "Apache-2.0",
|
||||
"author": "Nym Technologies SA",
|
||||
@@ -28,10 +28,10 @@
|
||||
"@mui/icons-material": "^5.14.9",
|
||||
"@mui/lab": "^5.0.0-alpha.145",
|
||||
"@mui/material": "^5.14.8",
|
||||
"@nymproject/contract-clients": "^1.2.0-rc.9",
|
||||
"@nymproject/mix-fetch": "^1.2.0-rc.9",
|
||||
"@nymproject/mix-fetch-full-fat": "^1.2.0-rc.9",
|
||||
"@nymproject/sdk-full-fat": "^1.2.0-rc.9",
|
||||
"@nymproject/contract-clients": "^1.2.0-rc.10",
|
||||
"@nymproject/mix-fetch": "^1.2.0-rc.10",
|
||||
"@nymproject/mix-fetch-full-fat": "^1.2.0-rc.10",
|
||||
"@nymproject/sdk-full-fat": "^1.2.0-rc.10",
|
||||
"chain-registry": "^1.19.0",
|
||||
"cosmjs-types": "^0.8.0",
|
||||
"next": "^13.4.19",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"general": "General FAQ"
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
@@ -0,0 +1,90 @@
|
||||
# Integrations page
|
||||
|
||||
### Existing resources
|
||||
If you'd like to learn more about potential integrations, please first 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;
|
||||
|
||||
### Integrations options
|
||||
If you're unsure where to start, the following set of questions should help you determine which path to follow in regards to integrations:
|
||||
|
||||
__1. Does your app rely on using `fetch` for its network traffic and remote connections?__
|
||||
|
||||
If yes, explore implementing `mixfetch`to route app traffic through the mixnet.
|
||||
|
||||
If not:
|
||||
__2. Is your app developed in TS/JS or Rust?__
|
||||
|
||||
If yes, you can use one of our SDKs and leverage either `mixfetch` (note that this only works for JS/TS at the moment, as we do not currently have a RUST implementation of it) or the `sdk client`to route app traffic through the mixnet.
|
||||
|
||||
If it is developed in another language:
|
||||
__3. You can use one of our standalone Nym clients__
|
||||
|
||||
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 encrypt Sphinx packets.
|
||||
You can find more information about the different standalone clients and the ways to interact with them [in this page](https://nymtech.net/developers/integrations/mixnet-integration.html).
|
||||
|
||||
|
||||
```
|
||||
For integration entry points:
|
||||
|
||||
,----------------------.
|
||||
|SPA/WebApp fetch-based|
|
||||
|----------------------|
|
||||
`----------------------'
|
||||
| |
|
||||
yes no
|
||||
| |
|
||||
,--------. ,----------.
|
||||
|mixFetch| |Rust or TS|
|
||||
|--------| |----------| ------------------.
|
||||
`--------' `----------' |
|
||||
| |
|
||||
yes----------. no:other language
|
||||
| | |
|
||||
| | |
|
||||
yes:TS/JS yes:RUST |
|
||||
| | ,----------------------.
|
||||
,------. ,--------. |Nym standalone client|
|
||||
|TS SDK| |Rust SDK| |----------------------|
|
||||
|------| |--------| | |
|
||||
`------' `--------' `----------------------'
|
||||
|
||||
```
|
||||
|
||||
### How to deal with Service Providers?
|
||||
In a nutshell: that 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).
|
||||
|
||||
```
|
||||
,--------------------.
|
||||
|App supports SOCKS5?|
|
||||
|--------------------|
|
||||
`--------------------'
|
||||
| |
|
||||
yes no
|
||||
| |
|
||||
,------------. ,----------------------------.
|
||||
|App use case| | Set standalone Client + SP |
|
||||
|------------| |----------------------------|
|
||||
`------------' `----------------------------'
|
||||
| |
|
||||
needs needs
|
||||
| |
|
||||
,----------------. ,-------------------.
|
||||
|Simple whitelist| |Extensive whitelist|
|
||||
|----------------| |-------------------|
|
||||
`----------------' `-------------------'
|
||||
| |
|
||||
| |
|
||||
,-----------. ,-------------.
|
||||
|Existing NR| |Run own SP/NR|
|
||||
|-----------| |-------------|
|
||||
`-----------' `-------------'
|
||||
|
||||
```
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
"overview": "SDK overview",
|
||||
"installation": "Installation",
|
||||
"start": "Getting started",
|
||||
"guides": "Examples",
|
||||
"examples": "Step-by-step examples",
|
||||
"playground": "Live Playground",
|
||||
|
||||
"bundling": "Bundling",
|
||||
"FAQ": "FAQ",
|
||||
|
||||
"contact": {
|
||||
"title": "Contact ↗",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"bundling": "General troubleshooting",
|
||||
"esbuild": "ESbuild",
|
||||
"webpack": "Webpack"
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,4 +1,4 @@
|
||||
# Troubleshooting Bundling
|
||||
# Troubleshooting bundling
|
||||
|
||||
You might need some help bundling packages from the Nym Typescript SDK into your package.
|
||||
|
||||
@@ -41,7 +41,7 @@ list. Use `[name][ext]` to preserve the output filename, because the package exp
|
||||
|
||||
## ESM not supported
|
||||
|
||||
If your bundler does not support ECMAScript Modules (ESM) we provide CommonJS packages for most parts of the SDK.
|
||||
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.
|
||||
@@ -52,4 +52,3 @@ If you are using a `*-full-fat` package, or if you inline WASM or web workers, y
|
||||
[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.
|
||||
|
||||
@@ -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 >
|
||||
```
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
Remember that the CosmosKit example will require you to make use of polyfills.
|
||||
</Callout>
|
||||
|
||||
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!
|
||||
|
||||
@@ -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
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
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:
|
||||
</Callout>
|
||||
|
||||
##### 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 <<EOF > 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"
|
||||
},
|
||||
```
|
||||
@@ -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 (
|
||||
<div>
|
||||
<div>
|
||||
{wallet &&
|
||||
<div>
|
||||
<div>Connected to {wallet?.prettyName} </div>
|
||||
<div>Address: <code>{address}</code></div>
|
||||
</div>}
|
||||
</div>
|
||||
{wallet ? (
|
||||
<div>
|
||||
<button onClick={() => disconnect()}>Disconnect wallet</button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<button onClick={() => connect()}>Connect wallet</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<ChainProvider
|
||||
chains={[chains.find((c) => c.chain_id === 'nyx')!]}
|
||||
assetLists={assetsFixedUp}
|
||||
wallets={[...ledger, ...keplr]}
|
||||
signerOptions={{
|
||||
preferredSignType: () => 'amino',
|
||||
}}
|
||||
>
|
||||
|
||||
<MyComponent/>
|
||||
</ChainProvider>
|
||||
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,166 @@
|
||||
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 in the [standard allow list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt). If that's the case, [send a PR](https://github.com/nymtech/nym/pulls) if you need changes.
|
||||
|
||||
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. Workaround for Mixed Content Errors because you might be using `mixFetch` from web app served from HTTPS while
|
||||
connecting a gateway that only listens on a plain websocket, without HTTPS/TLS.
|
||||
|
||||
Read [this article](https://blog.nymtech.net/mixfetch-like-the-fetch-api-but-via-the-mixnet-82acfd435c62) to learn more about mixFetch.
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
We are currently working on a feature that adds a Secure Websocket (WSS) listener with HTTPS (automatically generated with LetsEncrypt) to Nym's
|
||||
gateways.
|
||||
While we are adding this feature, you can use a gateway that has Caddy providing HTTPS/WSS by adding this to the options when setting up `mixFetch`:
|
||||
</Callout>
|
||||
|
||||
```ts
|
||||
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
|
||||
|
||||
const extra = {
|
||||
hiddenGateways: [
|
||||
{
|
||||
owner: 'n1kymvkx6vsq7pvn6hfurkpg06h3j4gxj4em7tlg',
|
||||
host: 'gateway1.nymtech.net',
|
||||
explicitIp: '213.219.38.119',
|
||||
identityKey: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM',
|
||||
sphinxKey: 'CYcrjoJ8GT7Dp54zViUyyRUfegeRCyPifWQZHRgMZrfX',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mixFetchOptions: SetupMixFetchOps = {
|
||||
preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', // with WSS
|
||||
preferredNetworkRequester:
|
||||
'GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW',
|
||||
mixFetchOverride: {
|
||||
requestTimeoutMs: 60_000,
|
||||
},
|
||||
forceTls: true, // force WSS
|
||||
extra, // manually set the gateway details for WSS so certificates will work for hostname
|
||||
};
|
||||
```
|
||||
|
||||
##### 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:
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
Again, for this example, we will be using the `full-fat` version of the ESM SDK.
|
||||
</Callout>
|
||||
|
||||
`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 extra = {
|
||||
hiddenGateways: [
|
||||
{
|
||||
owner: 'n1kymvkx6vsq7pvn6hfurkpg06h3j4gxj4em7tlg',
|
||||
host: 'gateway1.nymtech.net',
|
||||
explicitIp: '213.219.38.119',
|
||||
identityKey: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM',
|
||||
sphinxKey: 'CYcrjoJ8GT7Dp54zViUyyRUfegeRCyPifWQZHRgMZrfX',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mixFetchOptions: SetupMixFetchOps = {
|
||||
preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', // with WSS
|
||||
preferredNetworkRequester:
|
||||
'GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW',
|
||||
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 (
|
||||
<>
|
||||
<button onClick={() => { get() }}>Get</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
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://postman-echo.com/post', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ foo: 'bar' }),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}, mixFetchOptions)
|
||||
console.log(apiResponse)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => { post() }}>Post</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<>
|
||||
<HttpGET/>
|
||||
<HttpPOST/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
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!
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
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.
|
||||
</Callout>
|
||||
|
||||
|
||||
```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<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);
|
||||
|
||||
// Start the client and connect to a gateway
|
||||
await client?.client.start({
|
||||
clientId: crypto.randomUUID(),
|
||||
nymApiUrl,
|
||||
});
|
||||
|
||||
// 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 <div>Waiting for the mixnet client...</div>;
|
||||
|
||||
if (!selfAddress) return <div>Connecting...</div>;
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Send messages through the Nym mixnet</h1>
|
||||
<p style={{ border: "1px solid black" }}>
|
||||
My self address is: {selfAddress ? selfAddress : "loading"}
|
||||
</p>
|
||||
<div style={{ border: "1px solid black" }}>
|
||||
<label>Recipient Address: </label>
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => setRecipient(e.target.value)}
|
||||
></input>
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) =>
|
||||
setPayload({ message: e.target.value, mimeType: "text/plain" })
|
||||
}
|
||||
></input>
|
||||
<button onClick={() => send()}>Send</button>
|
||||
</div>
|
||||
<p>Received message: {receivedMessage}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function App () {
|
||||
return (
|
||||
<>
|
||||
<MixnetClient/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
If you experience a Gateway client error and a hard refresh isn't enough, you may need to open your console, go to `Application` and delete the databases under "IndexedDB".
|
||||
</Callout>
|
||||
+122
-184
@@ -5,34 +5,49 @@ import { Callout } from 'nextra/components'
|
||||
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 diffent available clients and methods from the `Contract Clients` can be found in the `.client.ts` files:
|
||||
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 partcipating 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 rewarding | [Mixnet](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Mixnet.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) |
|
||||
|
||||
|
||||
Depending on your app or project's architecture, this could be any of the ESM or CJS versions of the `Contract Clients`.
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
This and the following examples will use the ESbuild bundler.
|
||||
If you'd like to use another one, we will document different bundlers and polyfills in the [bundling](https://sdk.nymtech.net/bundling) page.
|
||||
</Callout>
|
||||
|
||||
##### Set-up your environment
|
||||
Create your directory and set-up your app environment:
|
||||
##### Environment Setup
|
||||
Begin by creating a directory and configuring your application environment:
|
||||
|
||||
```bash
|
||||
npm create vite@latest
|
||||
```
|
||||
npx create-react-app my-app
|
||||
|
||||
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 package and its dependencies from Cosmos Stargate:
|
||||
```
|
||||
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing
|
||||
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
|
||||
@@ -40,57 +55,10 @@ import { contracts } from '@nymproject/contract-clients';
|
||||
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
|
||||
````
|
||||
|
||||
##### Polyfills
|
||||
##### 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.
|
||||
|
||||
You will need to install:
|
||||
|
||||
`npm install --save url fs assert crypto-browserify stream-http https-browserify os-browserify buffer stream-browserify process react-app-rewired`
|
||||
|
||||
and create a `config-overrides.js`file:
|
||||
```js
|
||||
const webpack = require('webpack');
|
||||
module.exports = function override(config, env) {
|
||||
config.resolve.fallback = {
|
||||
url: require.resolve('url'),
|
||||
fs: require.resolve('fs'),
|
||||
assert: require.resolve('assert'),
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
http: require.resolve('stream-http'),
|
||||
https: require.resolve('https-browserify'),
|
||||
os: require.resolve('os-browserify/browser'),
|
||||
buffer: require.resolve('buffer'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
};
|
||||
config.plugins.push(
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
}),
|
||||
);
|
||||
|
||||
return config;
|
||||
}
|
||||
```
|
||||
Update your `package.json` file:
|
||||
```json
|
||||
"scripts": {
|
||||
"start": "react-app-rewired start",
|
||||
"build": "react-app-rewired build",
|
||||
"test": "react-app-rewired test",
|
||||
"eject": "react-scripts eject" // don't change the eject
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##### 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.
|
||||
|
||||
```js
|
||||
```ts
|
||||
import "./App.css";
|
||||
import { contracts } from "@nymproject/contract-clients";
|
||||
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
|
||||
@@ -98,50 +66,57 @@ import { useEffect, useState } from "react";
|
||||
|
||||
export default function Mixnodes() {
|
||||
|
||||
const [mixnodes, setMixnodes] = useState(null);
|
||||
const [mixnodes, setMixnodes] = useState<any>([]);
|
||||
|
||||
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 contract address (which is different on mainnet, QA, etc)
|
||||
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr" // The mainnet mixnet contract address (which will be different on mainnet, QA, etc)
|
||||
);
|
||||
console.log("client:", client)
|
||||
const result = await client.getMixNodesDetailed({});
|
||||
console.log(result)
|
||||
setMixnodes(result.nodes)
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchMixnodes();
|
||||
}, [])
|
||||
|
||||
return(
|
||||
<>
|
||||
<table>
|
||||
<tbody>
|
||||
{mixnodes?.map((value, index) => {
|
||||
return(
|
||||
<tr key={index}>
|
||||
<td> {value?.bond_information?.mix_node?.identity_key} </td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
return(
|
||||
<>
|
||||
<table>
|
||||
<tbody>
|
||||
{mixnodes?.map((value: any, index: number) => {
|
||||
return(
|
||||
<tr key={index}>
|
||||
<td> {value?.bond_information?.mix_node?.identity_key} </td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
@@ -153,81 +128,63 @@ 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 for the `settings.ts` file, we have used the following structure:
|
||||
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: <ENTER MIXNET CONTACT ADDRESS HERE>,
|
||||
mnemonic: '<ENTER MNEMONIC HERE>,
|
||||
address: <ENTER NYM ADDRESS HERE>
|
||||
mixnetContractAddress: '<ENTER MIXNET CONTACT ADDRESS HERE>',
|
||||
mnemonic: '<ENTER MNEMONIC HERE>',
|
||||
address: '<ENTER NYM ADDRESS HERE>'
|
||||
};
|
||||
|
||||
export const settings = mySettings;
|
||||
```
|
||||
|
||||
|
||||
```js
|
||||
|
||||
```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.ts";
|
||||
|
||||
import { settings } from "./settings";
|
||||
|
||||
export default function Exec() {
|
||||
let signer = null;
|
||||
let address = null;
|
||||
let signerMixnetClient = null;
|
||||
let cosmWasmSigningClient = null;
|
||||
let mixId = null;
|
||||
let amountToDelegate = null;
|
||||
let balance = null;
|
||||
let nodeAddress = null;
|
||||
let amountToSend = null;
|
||||
let delegations = null;
|
||||
|
||||
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() {
|
||||
// Signer
|
||||
try {
|
||||
// Generate a signer from a mnemonic
|
||||
signer = await DirectSecp256k1HdWallet.fromMnemonic(settings.mnemonic, {
|
||||
prefix: "n",
|
||||
});
|
||||
const accounts = await signer.getAccounts();
|
||||
address = accounts[0].address;
|
||||
} catch (error) {
|
||||
console.error("Problem getting the signer: ", error);
|
||||
}
|
||||
|
||||
try {
|
||||
const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(
|
||||
settings.url,
|
||||
signer,
|
||||
{
|
||||
gasPrice: GasPrice.fromString("0.025unym"),
|
||||
}
|
||||
);
|
||||
cosmWasmSigningClient = cosmWasmClient;
|
||||
try {
|
||||
balance = await cosmWasmSigningClient?.getBalance(address, "unym");
|
||||
console.log("balance", balance);
|
||||
} catch (error) {
|
||||
console.error("problem geting the balance: ", error);
|
||||
// Cosmos client
|
||||
signer = await DirectSecp256k1HdWallet.fromMnemonic(settings.mnemonic, {
|
||||
prefix: "n",
|
||||
});
|
||||
const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(
|
||||
settings.url,
|
||||
signer,
|
||||
{
|
||||
gasPrice: GasPrice.fromString("0.025unym"),
|
||||
}
|
||||
|
||||
const mixnetClient = new contracts.Mixnet.MixnetClient(
|
||||
cosmWasmSigningClient,
|
||||
settings.address, // sender (that account of the signer)
|
||||
settings.mixnetContractAddress // contract address (different on mainnet, QA, etc)
|
||||
);
|
||||
signerMixnetClient = mixnetClient;
|
||||
} catch (error) {
|
||||
console.error("Problem getting the cosmWasmSigningClient: ", error);
|
||||
}
|
||||
);
|
||||
// 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) {
|
||||
@@ -238,64 +195,47 @@ export default function Exec() {
|
||||
});
|
||||
delegations = delegationsObject;
|
||||
};
|
||||
|
||||
|
||||
// Make delegation
|
||||
const doDelegation = async () => {
|
||||
if (!signerMixnetClient) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await signerMixnetClient.delegateToMixnode(
|
||||
{ mixId },
|
||||
"auto",
|
||||
undefined,
|
||||
[{ amount: `${amountToDelegate}`, denom: "unym" }]
|
||||
);
|
||||
console.log("delegations: ", res);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
const res = await signerMixnetClient.delegateToMixnode(
|
||||
{ mixId },
|
||||
"auto",
|
||||
undefined,
|
||||
[{ amount: `${amountToDelegate}`, denom: "unym" }]
|
||||
);
|
||||
console.log(res);
|
||||
};
|
||||
|
||||
|
||||
// Undelegate all
|
||||
const doUndelegateAll = async () => {
|
||||
if (!signerMixnetClient) {
|
||||
return;
|
||||
}
|
||||
console.log("delegations", delegations);
|
||||
try {
|
||||
for (const delegation of delegations.delegations) {
|
||||
await signerMixnetClient.undelegateFromMixnode(
|
||||
{ mixId: delegation.mix_id },
|
||||
"auto"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
for (const delegation of delegations.delegations) {
|
||||
await signerMixnetClient.undelegateFromMixnode(
|
||||
{ mixId: delegation.mix_id },
|
||||
"auto"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Sending tokens
|
||||
const doSendTokens = async () => {
|
||||
const memo = "test sending tokens";
|
||||
|
||||
try {
|
||||
const res = await cosmWasmSigningClient.sendTokens(
|
||||
settings.address,
|
||||
nodeAddress,
|
||||
[{ amount: amountToSend, denom: "unym" }],
|
||||
"auto",
|
||||
memo
|
||||
);
|
||||
console.log("res", res);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
const res = await cosmWasmSigningClient.sendTokens(
|
||||
settings.address,
|
||||
nodeAddress,
|
||||
[{ amount: amountToSend, denom: "unym" }],
|
||||
"auto",
|
||||
memo
|
||||
);
|
||||
console.log(res);
|
||||
};
|
||||
|
||||
|
||||
ExecuteOnNyx();
|
||||
setTimeout(() => getDelegations(), 1000);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Exec</p>
|
||||
@@ -320,7 +260,7 @@ export default function Exec() {
|
||||
<input
|
||||
type="number"
|
||||
placeholder="Mixnode Id"
|
||||
onChange={(e) => (mixId = e.target.value)}
|
||||
onChange={(e) => (mixId = +e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
@@ -337,6 +277,4 @@ export default function Exec() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# 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 the browser
|
||||
- any wallet that supports [Wallet Connect v2.0](https://docs.cosmoskit.com/integrating-wallets/adding-new-wallets)
|
||||
|
||||
```ts
|
||||
import React from 'react';
|
||||
import { useChain } from '@cosmos-kit/react';
|
||||
import { assets, chains } from 'chain-registry';
|
||||
import { wallets } from '@cosmos-kit/keplr';
|
||||
|
||||
export const MyComponent = () => {
|
||||
const { wallet, address, connect, getOfflineSignerDirect } =
|
||||
useChain('nyx');
|
||||
|
||||
React.useEffect(() => {
|
||||
connect();
|
||||
}, []);
|
||||
|
||||
const sign = async () => {
|
||||
const doc = { ... };
|
||||
return getOfflineSignerDirect().signDirect(address, doc);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<strong>Connected to {wallet.prettyName}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Address: <code>{address}</code>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
# `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`:
|
||||
|
||||
```
|
||||
npm install @nymproject/mix-fetch
|
||||
```
|
||||
|
||||
And then:
|
||||
|
||||
```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}` }
|
||||
});
|
||||
```
|
||||
|
||||
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, you
|
||||
have two choices:
|
||||
- run your own Network Requester and locally configure it to allow the hosts you need to connect to
|
||||
- get in touch with us and give us more information about the sites you want included in the standard allow list
|
||||
|
||||
2. We periodically update the CA certificates in `mixFetch` so if you get a certificate error, we may not have the
|
||||
root CA certificate you need in our list. [Send us a PR](https://github.com/nymtech/nym/pulls) if you need changes.
|
||||
|
||||
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. Workaround for Mixed Content Errors because you might be using `mixFetch` from web app served from HTTPS while
|
||||
connecting a gateway that only listens on a plain websocket, without HTTPS/TLS.
|
||||
|
||||
We are currently working on a feature that adds a Secure Websocket (WSS) listener with HTTPS (automatically generated with LetsEncrypt) to Nym's
|
||||
gateways.
|
||||
|
||||
While we are adding this feature, you can use a gateway that has Caddy providing HTTPS/WSS by adding this to the options when settings up `mixFetch`:
|
||||
|
||||
```ts
|
||||
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
|
||||
|
||||
const extra = {
|
||||
hiddenGateways: [
|
||||
{
|
||||
owner: 'n1kymvkx6vsq7pvn6hfurkpg06h3j4gxj4em7tlg',
|
||||
host: 'gateway1.nymtech.net',
|
||||
explicitIp: '213.219.38.119',
|
||||
identityKey: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM',
|
||||
sphinxKey: 'CYcrjoJ8GT7Dp54zViUyyRUfegeRCyPifWQZHRgMZrfX',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mixFetchOptions: SetupMixFetchOps = {
|
||||
preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', // with WSS
|
||||
preferredNetworkRequester:
|
||||
'GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW',
|
||||
mixFetchOverride: {
|
||||
requestTimeoutMs: 60_000,
|
||||
},
|
||||
forceTls: true, // force WSS
|
||||
extra, // manually set the gateway details for WSS so certificates will work for hostname
|
||||
};
|
||||
```
|
||||
@@ -1,106 +0,0 @@
|
||||
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 done 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
|
||||
|
||||
##### Imports
|
||||
Import the SDK's Mixnet Client as well as the payload 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
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
For this example, we will be using the `full-fat` version of the ESM SDK. If you'd like to use the unbundled ESM one, make sure your [bundler configuration](../../bundling) copies the WebAssembly (WASM) and web worker files to the output bundle.
|
||||
</Callout>
|
||||
|
||||
|
||||
```ts
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
createNymMixnetClient,
|
||||
NymMixnetClient,
|
||||
Payload,
|
||||
} from "@nymproject/sdk-full-fat";
|
||||
|
||||
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 nym = await createNymMixnetClient();
|
||||
setNym(nym);
|
||||
|
||||
// start the client and connect to a gateway
|
||||
await nym?.client.start({
|
||||
clientId: crypto.randomUUID(),
|
||||
nymApiUrl,
|
||||
});
|
||||
|
||||
// check when is connected and set the self address
|
||||
nym?.events.subscribeToConnected((e) => {
|
||||
const { address } = e.args;
|
||||
setSelfAddress(address);
|
||||
});
|
||||
|
||||
// show whether the client is ready or not
|
||||
nym?.events.subscribeToLoaded((e) => {
|
||||
console.log("Client ready: ", e.args);
|
||||
});
|
||||
|
||||
// show message payload content when received
|
||||
nym?.events.subscribeToTextMessageReceivedEvent((e) => {
|
||||
console.log(e.args.payload);
|
||||
setReceivedMessage(e.args.payload);
|
||||
});
|
||||
};
|
||||
|
||||
const send = () => nym.client.send({ payload, recipient });
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
}, []);
|
||||
|
||||
if (!nym) return <div>waiting for the mixnet client...</div>;
|
||||
|
||||
if (!selfAddress) return <div>connecting...</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Send messages through the Mixnet</h1>
|
||||
<p style={{ border: "1px solid black" }}>
|
||||
My self address is: {selfAddress ? selfAddress : "loading"}
|
||||
</p>
|
||||
<div style={{ border: "1px solid black" }}>
|
||||
<label>Recipient Address</label>
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => setRecipient(e.target.value)}
|
||||
></input>
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) =>
|
||||
setPayload({ message: e.target.value, mimeType: "text/plain" })
|
||||
}
|
||||
></input>
|
||||
<button onClick={() => send()}>Send</button>
|
||||
</div>
|
||||
<p>Received message: {receivedMessage}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
```
|
||||
@@ -1,15 +1,16 @@
|
||||
# Introduction
|
||||
|
||||
Welcome to the documentation for Nym's TypeScript SDK! <br/>
|
||||
Welcome to the documentation for Nym's TypeScript SDK!
|
||||
|
||||
This guide contains valuable information about the various TypeScript SDK modules that facilitate interaction with different components of the Nym stack, including the mixnet, Nyx chain, and Coconut credentials.
|
||||
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
|
||||
|
||||
### Our 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.
|
||||
|
||||
If you're new to the Nym ecosystem and aiming to understand the mixnet concept, 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.
|
||||
|
||||
For a more in-depth exploration of Nym's architecture, clients, nodes, and SDK examples, we recommend referring 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).
|
||||
|
||||
@@ -33,4 +34,3 @@ Read more to understand the differences in between Nym, TOR (and other mixnets)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,25 +2,25 @@ import { Callout } from 'nextra/components'
|
||||
|
||||
# Overview
|
||||
|
||||
The Typescript SDK's different modules allow developers to start building browser-based applications quickly, by simply importing the SDK module of their choice - depending on the component from the Nym architecture they want to use - into their code via NPM as they would any other Typescript library.
|
||||
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.
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
SDK modules come in four different flavours (ESM, CJS and full-fat for ESM and CJS).
|
||||
This documentation only shows instructions and examples for the unbundled ESM variant.
|
||||
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.
|
||||
</Callout>
|
||||
|
||||
#### Install all
|
||||
|
||||
```
|
||||
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk @nymproject/mix-fetch --save
|
||||
```bash
|
||||
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk-full-fat @nymproject/mix-fetch-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.
|
||||
Information about the chain can be found on the [Nyx blockchain explorer](https://nym.explorers.guru/).
|
||||
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.
|
||||
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)*.
|
||||
|
||||
@@ -29,15 +29,15 @@ In order to query or execute on any of the Nym smart contracts, you'll need to u
|
||||
|
||||
First install the package and its dependencies from Cosmos Stargate:
|
||||
|
||||
```
|
||||
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing --save
|
||||
```bash
|
||||
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing
|
||||
```
|
||||
|
||||
## Mixnet
|
||||
#### Overview
|
||||
The [Nym mixnet](https://nymtech.net/docs/architecture/network-overview.html) provides very strong security guarantees against network-level surveillance. It wraps into packets and mixes together IP traffic from many users inside the mixnet. It encrypts and mixes [Sphinx packet](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf) traffic so that it cannot be determined who is communicating with whom. Our mixnet is based on a modified version of the Loopix design.
|
||||
The [Nym mixnet](https://nymtech.net/docs/architecture/network-overview.html) provides extremely robust protection against network-level surveillance. 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 our mixnet using our [mixnet explorer](https://nymtech.net/docs/explorers/mixnet-explorer.html) here.*
|
||||
*You can explore the Nym mixnet using the [mixnet explorer](https://nymtech.net/docs/explorers/mixnet-explorer.html) here.*
|
||||
|
||||
|
||||
#### Installation: Mixnet Client
|
||||
@@ -45,21 +45,21 @@ In order to send or receive traffic over the mixnet, you'll need to use the [`Mi
|
||||
|
||||
First install the package and its dependencies:
|
||||
|
||||
```
|
||||
npm install @nymproject/sdk --save
|
||||
```bash
|
||||
npm install @nymproject/sdk-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 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).
|
||||
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:
|
||||
|
||||
```
|
||||
npm install @nymproject/mix-fetch --save
|
||||
```bash
|
||||
npm install @nymproject/mix-fetch-full-fat
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import { TableContainer, Table, TableBody, TableCell, TableRow, Paper } from '@m
|
||||
import { NPMLink } from '../components/npm';
|
||||
|
||||
## SDK overview
|
||||
The Typescript SDK allows developers to start building browser-based mixnet applications quickly, by simply importing the SDK modules into their code via NPM as they would any other Typescript library.
|
||||
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 do the following entirely in the browser:
|
||||
Currently developers can use different packages from the Typescript SDK to run the following entirely in browser:
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
@@ -57,10 +57,10 @@ Currently developers can use different packages from the Typescript SDK to do th
|
||||
## 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**: 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 as is;
|
||||
- **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;
|
||||
|
||||
<Callout type="warning" emoji="🥛">
|
||||
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.
|
||||
|
||||
@@ -5,12 +5,12 @@ import FormattedCosmoskitExampleCode from '../../code-examples/cosmoskit-example
|
||||
|
||||
# Cosmos Kit
|
||||
|
||||
Below is an example that uses [CosmosKit](https://cosmoskit.com/) to connect your [Keplr wallet](https://www.keplr.app/) or
|
||||
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:
|
||||
|
||||
<CosmosKit />
|
||||
|
||||
Once you connect either Keplr or your hardware Ledger, you can request a fake transaction to be signed. The hash
|
||||
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.
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
@@ -19,10 +19,10 @@ of the message will be displayed.
|
||||
|
||||
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)
|
||||
- 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);
|
||||
|
||||
<FormattedCosmoskitExampleCode />
|
||||
|
||||
@@ -4,7 +4,7 @@ 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!
|
||||
Use this tool to experiment with the mixnet: send and receive messages!
|
||||
|
||||
<Traffic />
|
||||
<FormattedTrafficExampleCode />
|
||||
@@ -1,11 +1,19 @@
|
||||
# Wallet
|
||||
|
||||
import { Wallet } from '../../components/wallet'
|
||||
import { Wallet } from '../../components/wallet';
|
||||
import Box from '@mui/material/Box';
|
||||
import FormattedWalletExampleCode from '../../code-examples/wallet-example-code.mdx';
|
||||
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 for you to test some of the methods of our clients.
|
||||
You can use the testnet address provided as recipient address, and to delegate just go to the Mixnodes list and paste in the Mix ID number of the Mixnode you'd like to delegate to.
|
||||
Here's a small wallet example using testnet for you to test out!
|
||||
|
||||
<Wallet />
|
||||
<FormattedWalletExampleCode />
|
||||
|
||||
<Wallet type="connect"/>
|
||||
<FormattedWalletConnectCode />
|
||||
|
||||
<Wallet type="sendTokens"/>
|
||||
<FormattedWalletSendTokensCode />
|
||||
|
||||
<Wallet type="delegations"/>
|
||||
<FormattedWalletDelegationsCode />
|
||||
@@ -60,7 +60,7 @@ const main = async () => {
|
||||
|
||||
## 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:
|
||||
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';
|
||||
@@ -78,7 +78,7 @@ const apiResponse = await mixFetch('https://api.example.com', {
|
||||
```
|
||||
|
||||
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 allowed.
|
||||
if the host you want to `mixFetch` from is whitelisted.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ body {
|
||||
}
|
||||
|
||||
div.nextra-code-block > div {
|
||||
background:var(--colorPrimary)!important;
|
||||
background:var(--colorPrimary) !important;
|
||||
}
|
||||
|
||||
.nextra-code-block > pre {
|
||||
max-height: 350px;
|
||||
scroll-y: auto;
|
||||
max-height: 350px !important;
|
||||
scroll-y: auto !important;
|
||||
}
|
||||
|
||||
/* Code blocks*/
|
||||
@@ -20,7 +20,7 @@ div.nextra-code-block > div {
|
||||
|
||||
|
||||
:is(html[class~=dark] .dark\:nx-bg-primary-300\/10) {
|
||||
background-color: hsl(var(black)100% 77%/.1);
|
||||
background-color: hsl(var(black)100% 77%/.1) !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,23 +68,23 @@ div.nextra-code-block > div {
|
||||
}
|
||||
|
||||
/* Chips*/
|
||||
.css-sv2u65-MuiChip-root {
|
||||
background-color: var(--colorPrimary);
|
||||
.chipContained{
|
||||
background-color: var(--colorPrimary) !important;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.MuiButton-root {
|
||||
color: var(--colorPrimary);
|
||||
border-color: var(--colorPrimary);
|
||||
color: var(--colorPrimary) !important;
|
||||
border-color: var(--colorPrimary) !important;
|
||||
}
|
||||
|
||||
.MuiButton-root:hover {
|
||||
color: white;
|
||||
background-color: var(--colorPrimary);
|
||||
color: white !important;
|
||||
background-color: var(--colorPrimary) !important;
|
||||
}
|
||||
|
||||
.MuiCircularProgress-root {
|
||||
color: var(--colorPrimary);
|
||||
color: var(--colorPrimary) !important;
|
||||
}
|
||||
|
||||
.nextra-scrollbar.nx-sticky{
|
||||
@@ -103,3 +103,6 @@ input:focus-visible {
|
||||
border-color: var(--colorPrimary) !important;
|
||||
}
|
||||
|
||||
a.MuiLink-root {
|
||||
color: var(--colorPrimary) !important;
|
||||
}
|
||||
Reference in New Issue
Block a user