Add dialog for sending tokens
This commit is contained in:
parent
a3dddb7b77
commit
18f8194d5a
|
@ -24,6 +24,7 @@ import IconButton from '@material-ui/core/IconButton';
|
||||||
import Tooltip from '@material-ui/core/Tooltip';
|
import Tooltip from '@material-ui/core/Tooltip';
|
||||||
import { preloadResource } from 'use-async-resource/lib';
|
import { preloadResource } from 'use-async-resource/lib';
|
||||||
import AddTokenDialog from './AddTokenDialog';
|
import AddTokenDialog from './AddTokenDialog';
|
||||||
|
import SendDialog from './SendDialog';
|
||||||
|
|
||||||
const balanceFormat = new Intl.NumberFormat(undefined, {
|
const balanceFormat = new Intl.NumberFormat(undefined, {
|
||||||
minimumFractionDigits: 4,
|
minimumFractionDigits: 4,
|
||||||
|
@ -56,6 +57,7 @@ export default function BalancesList() {
|
||||||
<Tooltip title="Refresh" arrow>
|
<Tooltip title="Refresh" arrow>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => resourceCache(wallet.getAccountBalance).clear()}
|
onClick={() => resourceCache(wallet.getAccountBalance).clear()}
|
||||||
|
style={{ marginRight: -12 }}
|
||||||
>
|
>
|
||||||
<RefreshIcon />
|
<RefreshIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -100,8 +102,10 @@ function BalanceListItem({ index }) {
|
||||||
const [getBalance] = useAsyncResource(wallet.getAccountBalance, index);
|
const [getBalance] = useAsyncResource(wallet.getAccountBalance, index);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const [sendDialogOpen, setSendDialogOpen] = useState(false);
|
||||||
|
|
||||||
const account = wallet.getAccount(index);
|
const account = wallet.getAccount(index);
|
||||||
|
const balanceInfo = getBalance();
|
||||||
let {
|
let {
|
||||||
amount,
|
amount,
|
||||||
decimals,
|
decimals,
|
||||||
|
@ -109,7 +113,7 @@ function BalanceListItem({ index }) {
|
||||||
tokenName,
|
tokenName,
|
||||||
tokenTicker,
|
tokenTicker,
|
||||||
initialized,
|
initialized,
|
||||||
} = getBalance();
|
} = balanceInfo;
|
||||||
|
|
||||||
if (!initialized && index !== 0) {
|
if (!initialized && index !== 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -140,7 +144,12 @@ function BalanceListItem({ index }) {
|
||||||
>
|
>
|
||||||
Receive
|
Receive
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outlined" color="primary" startIcon={<SendIcon />}>
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<SendIcon />}
|
||||||
|
onClick={() => setSendDialogOpen(true)}
|
||||||
|
>
|
||||||
Send
|
Send
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -169,6 +178,12 @@ function BalanceListItem({ index }) {
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
<SendDialog
|
||||||
|
open={sendDialogOpen}
|
||||||
|
onClose={() => setSendDialogOpen(false)}
|
||||||
|
balanceInfo={balanceInfo}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import DialogActions from '@material-ui/core/DialogActions';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
|
import DialogContent from '@material-ui/core/DialogContent';
|
||||||
|
import TextField from '@material-ui/core/TextField';
|
||||||
|
import DialogForm from './DialogForm';
|
||||||
|
import { useWallet } from '../utils/wallet';
|
||||||
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
import { abbreviateAddress } from '../utils/utils';
|
||||||
|
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||||
|
|
||||||
|
export default function SendDialog({ open, onClose, index, balanceInfo }) {
|
||||||
|
const wallet = useWallet();
|
||||||
|
const [destinationAddress, setDestinationAddress] = useState('');
|
||||||
|
const [transferAmountString, setTransferAmountString] = useState('');
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
let {
|
||||||
|
amount: balanceAmount,
|
||||||
|
decimals,
|
||||||
|
mint,
|
||||||
|
tokenName,
|
||||||
|
tokenTicker,
|
||||||
|
} = balanceInfo;
|
||||||
|
|
||||||
|
async function onSubmit() {
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
let amount = Math.round(
|
||||||
|
parseFloat(transferAmountString) * Math.pow(10, decimals),
|
||||||
|
);
|
||||||
|
if (!amount || amount <= 0) {
|
||||||
|
throw new Error('Invalid amount');
|
||||||
|
}
|
||||||
|
await wallet.transferToken(
|
||||||
|
index,
|
||||||
|
new PublicKey(destinationAddress),
|
||||||
|
amount,
|
||||||
|
);
|
||||||
|
onClose();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogForm open={open} onClose={onClose} onSubmit={onSubmit}>
|
||||||
|
<DialogTitle>
|
||||||
|
Send {tokenName ?? abbreviateAddress(mint)}
|
||||||
|
{tokenTicker ? ` (${tokenTicker})` : null}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<TextField
|
||||||
|
label="Recipient Address"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
value={destinationAddress}
|
||||||
|
onChange={(e) => setDestinationAddress(e.target.value.trim())}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Amount"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
type="number"
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: tokenTicker ? (
|
||||||
|
<InputAdornment position="end">{tokenTicker}</InputAdornment>
|
||||||
|
) : null,
|
||||||
|
inputProps: {
|
||||||
|
step: Math.pow(10, -decimals),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={transferAmountString}
|
||||||
|
onChange={(e) => setTransferAmountString(e.target.value.trim())}
|
||||||
|
helperText={`Max: ${balanceAmount / Math.pow(10, decimals)}`}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
|
<Button type="submit" color="primary" disabled={submitting}>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</DialogForm>
|
||||||
|
);
|
||||||
|
}
|
|
@ -82,3 +82,18 @@ export function initializeAccount({ account, mint, owner }) {
|
||||||
programId: TOKEN_PROGRAM_ID,
|
programId: TOKEN_PROGRAM_ID,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transfer({ source, destination, amount, owner }) {
|
||||||
|
let keys = [
|
||||||
|
{ pubkey: source, isSigner: false, isWritable: true },
|
||||||
|
{ pubkey: destination, isSigner: false, isWritable: true },
|
||||||
|
{ pubkey: owner, isSigner: true, isWritable: false },
|
||||||
|
];
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys,
|
||||||
|
data: encodeTokenInstructionData({
|
||||||
|
transfer: { amount },
|
||||||
|
}),
|
||||||
|
programId: TOKEN_PROGRAM_ID,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import { sendAndConfirmTransaction, SystemProgram } from '@solana/web3.js';
|
import {
|
||||||
|
sendAndConfirmTransaction,
|
||||||
|
SystemProgram,
|
||||||
|
Transaction,
|
||||||
|
} from '@solana/web3.js';
|
||||||
import {
|
import {
|
||||||
initializeAccount,
|
initializeAccount,
|
||||||
initializeMint,
|
initializeMint,
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
|
transfer,
|
||||||
} from './token-instructions';
|
} from './token-instructions';
|
||||||
import { ACCOUNT_LAYOUT, MINT_LAYOUT } from './token-state';
|
import { ACCOUNT_LAYOUT, MINT_LAYOUT } from './token-state';
|
||||||
|
|
||||||
|
@ -87,3 +92,24 @@ export async function createAndInitializeTokenAccount({
|
||||||
confirmations: 1,
|
confirmations: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function transferTokens({
|
||||||
|
connection,
|
||||||
|
owner,
|
||||||
|
sourcePublicKey,
|
||||||
|
destinationPublicKey,
|
||||||
|
amount,
|
||||||
|
}) {
|
||||||
|
let transaction = new Transaction().add(
|
||||||
|
transfer({
|
||||||
|
source: sourcePublicKey,
|
||||||
|
destination: destinationPublicKey,
|
||||||
|
owner: owner.publicKey,
|
||||||
|
amount,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let signers = [owner];
|
||||||
|
return await sendAndConfirmTransaction(connection, transaction, signers, {
|
||||||
|
confirmations: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as bip32 from 'bip32';
|
||||||
import { Account } from '@solana/web3.js';
|
import { Account } from '@solana/web3.js';
|
||||||
import nacl from 'tweetnacl';
|
import nacl from 'tweetnacl';
|
||||||
import { useConnection } from './connection';
|
import { useConnection } from './connection';
|
||||||
import { createAndInitializeTokenAccount } from './tokens';
|
import { createAndInitializeTokenAccount, transferTokens } from './tokens';
|
||||||
import { resourceCache } from 'use-async-resource';
|
import { resourceCache } from 'use-async-resource';
|
||||||
import { TOKEN_PROGRAM_ID } from './token-instructions';
|
import { TOKEN_PROGRAM_ID } from './token-instructions';
|
||||||
import {
|
import {
|
||||||
|
@ -90,6 +90,17 @@ export class Wallet {
|
||||||
ACCOUNT_LAYOUT.span,
|
ACCOUNT_LAYOUT.span,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
transferToken = async (index, destination, amount) => {
|
||||||
|
let tokenAccount = this.getAccount(index);
|
||||||
|
await transferTokens({
|
||||||
|
connection: this.connection,
|
||||||
|
owner: this.account,
|
||||||
|
sourcePublicKey: tokenAccount.publicKey,
|
||||||
|
destinationPublicKey: destination,
|
||||||
|
amount,
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const WalletContext = React.createContext(null);
|
const WalletContext = React.createContext(null);
|
||||||
|
|
Loading…
Reference in New Issue