Skip to content
This repository has been archived by the owner on Aug 1, 2023. It is now read-only.

Commit

Permalink
feat: improve send tx flow, adds receive modal, closes #160
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Sep 25, 2020
1 parent 1325e18 commit 7fa64e5
Show file tree
Hide file tree
Showing 25 changed files with 392 additions and 146 deletions.
18 changes: 17 additions & 1 deletion app/api/get-account-details.ts → app/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import axios from 'axios';
import { TransactionResults } from '@blockstack/stacks-blockchain-sidecar-types';
import {
Transaction,
TransactionResults,
MempoolTransaction,
} from '@blockstack/stacks-blockchain-sidecar-types';

const api = 'https://sidecar.staging.blockstack.xyz/sidecar';

Expand All @@ -23,7 +27,19 @@ async function getAddressTransactions(address: string) {
return await axios.get<TransactionResults>(api + `/v1/address/${address}/transactions`);
}

type AnyTransaction = Transaction | MempoolTransaction;

async function getTxDetails(txid: string) {
return await axios.get<AnyTransaction>(api + `/v1/tx/${txid}/transactions`);
}
// async function getStxPriceInDollars() {
// return await axios.get(
// 'https://api.coingecko.com/api/v3/simple/price?ids=blockstack&vs_currencies=usd'
// );
// }

export const Api = {
getAddressBalance,
getAddressTransactions,
getTxDetails,
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import React, { FC } from 'react';
import { useHover } from 'use-events';
import { Box, Flex, Text } from '@blockstack/ui';

import { PendingTransaction } from '../../../store/pending-transaction';
import { listHoverProps, EnableBefore } from './transaction-list-item-hover';
import { TransactionIcon } from './transaction-icon';

interface TransactionListItemPendingProps {
txId: string;
tx: PendingTransaction;
onSelectTx: (txId: string) => void;
}

export const TransactionListItemPending: FC<TransactionListItemPendingProps> = ({
txId,
tx,
onSelectTx,
}) => {
const [hovered, bind] = useHover();
Expand All @@ -23,22 +24,22 @@ export const TransactionListItemPending: FC<TransactionListItemPendingProps> = (
cursor="pointer"
position="relative"
_before={listHoverProps(hovered)}
onClick={() => onSelectTx(txId)}
data-txid={txId}
onClick={() => onSelectTx(tx.txId)}
data-txid={tx.txId}
{...bind}
>
<TransactionIcon variant="pending" mr="base-loose" />
<Box flex={1}>
<Text textStyle="body.large.medium" display="block">
Pending
Sending
</Text>
<Text textStyle="body.small" color="ink.600">
{'0x' + txId.substr(0, 14)}
{tx.txId.substr(0, 28)}
</Text>
</Box>
<Box textAlign="right">
<Text textStyle="body.large" color="ink.900" display="block">
xxx STX
{tx.amount} STX
</Text>
<Text textStyle="body.small" color="ink.600">
Pending
Expand Down
8 changes: 8 additions & 0 deletions app/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
export const MNEMONIC_ENTROPY = 256;

type Environments = 'development' | 'testing' | 'production';

export const ENV = (process.env.NODE_ENV ?? 'production') as Environments;

export const features = {
stackingEnabled: false,
};
23 changes: 23 additions & 0 deletions app/crypto/create-stx-tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import BN from 'bn.js';
import { deriveRootKeychainFromMnemonic } from '@blockstack/keychain';
import { makeSTXTokenTransfer } from '@blockstack/stacks-transactions';

import { stacksNetwork } from './environment';
import { deriveStxAddressKeychain } from './derive-address-keychain';

interface CreateStxTxArgs {
mnemonic: string;
recipient: string;
amount: BN;
}

export async function createStxTransaction({ mnemonic, recipient, amount }: CreateStxTxArgs) {
const rootNode = await deriveRootKeychainFromMnemonic(mnemonic);
const { privateKey } = deriveStxAddressKeychain(rootNode);
return await makeSTXTokenTransfer({
recipient,
amount,
senderKey: privateKey,
network: stacksNetwork,
});
}
4 changes: 4 additions & 0 deletions app/crypto/derive-address-keychain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { deriveStxAddressChain } from '@blockstack/keychain';
import { chain } from './environment';

export const deriveStxAddressKeychain = deriveStxAddressChain(chain);
14 changes: 14 additions & 0 deletions app/crypto/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
ChainID,
StacksNetwork,
StacksTestnet,
StacksMainnet,
} from '@blockstack/stacks-transactions';
import { ENV } from '../constants';

export { ChainID };

export const chain = ENV === 'development' || ENV === 'testing' ? ChainID.Testnet : ChainID.Mainnet;

export const stacksNetwork: StacksNetwork =
ENV === 'development' || ENV === 'testing' ? new StacksTestnet() : new StacksMainnet();
12 changes: 12 additions & 0 deletions app/crypto/validate-address-net.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { chain, ChainID } from './environment';

export function validateAddressChain(address: string) {
const prefix = address.substr(0, 2);
if (chain === ChainID.Testnet) {
return prefix === 'SN' || prefix === 'ST';
}
if (chain === ChainID.Mainnet) {
return prefix === 'SM' || prefix === 'SP';
}
return false;
}
1 change: 1 addition & 0 deletions app/hooks/use-interval.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useRef } from 'react';

export function useInterval(callback: () => void, delay: number) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const savedCallback = useRef(() => {});

useEffect(() => {
Expand Down
24 changes: 1 addition & 23 deletions app/main.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,7 @@ export default class AppUpdater {
}
}

contextMenu({
prepend: (defaultActions, params, browserWindow) => [
// {
// label: 'Rainbow',
// // Only show it when right-clicking images
// visible: params.mediaType === 'image',
// },
{
label: 'Search Google for “{selection}”',
// Only show it when right-clicking text
visible: params.selectionText.trim().length > 0,
click: (menuItem, browserWindow, event) => {
console.log({ menuItem, browserWindow, event });
// shell.openExternal(
// `https://google.com/search?q=${encodeURIComponent(params.selectionText)}`
// );
},
},
// {
// label: 'Open in Explorer',
// }
],
});
contextMenu();

let mainWindow: BrowserWindow | null = null;

Expand Down
64 changes: 64 additions & 0 deletions app/modals/receive-stx/receive-stx-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { FC, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Qr from 'qrcode.react';
import { Text, Modal, Button, Flex, Box, useClipboard } from '@blockstack/ui';

import { TxModalHeader, TxModalFooter } from '../transaction/transaction-modal-layout';
import { homeActions, selectReceiveModalOpen } from '../../store/home/home.reducer';

interface ReceiveStxModalProps {
address: string;
}

export const ReceiveStxModal: FC<ReceiveStxModalProps> = ({ address }) => {
const dispatch = useDispatch();
const modalOpen = useSelector(selectReceiveModalOpen);
const copyAddressToClipboard = useClipboard(address);
const [buttonText, setButtonText] = useState('Copy address');
const onCopyAddress = () => {
copyAddressToClipboard.onCopy();
setButtonText('Copied');
setTimeout(() => setButtonText('Copy address'), 1000);
};
const closeModal = () => dispatch(homeActions.closeReceiveModal());
if (!modalOpen) return null;
return (
<Modal
minWidth="488px"
isOpen={modalOpen}
headerComponent={<TxModalHeader onSelectClose={closeModal}>Receive STX</TxModalHeader>}
footerComponent={
<TxModalFooter>
<Button size="lg" onClick={closeModal}>
Close
</Button>
</TxModalFooter>
}
>
<Flex flexDirection="column" alignItems="center" mx="extra-loose">
<Box border="1px solid #F0F0F5" p="base" mt="extra-loose" borderRadius="8px">
<Qr value={address} shapeRendering="sharp-edges" />
</Box>
<Text textStyle="body.large.medium" mt="loose">
Wallet address
</Text>
<Flex
mt="base-tight"
justifyContent="center"
alignItems="center"
border="1px solid #E1E3E8"
height="48px"
borderRadius="6px"
width="100%"
>
<Text color="ink" fontSize="14px">
{address}
</Text>
</Flex>
<Button variant="link" mt="tight" mb="loose" onClick={onCopyAddress}>
{buttonText}
</Button>
</Flex>
</Modal>
);
};
Loading

0 comments on commit 7fa64e5

Please sign in to comment.