mirror of https://github.com/trezor/trezor-wallet
commit
70d7c73a77
@ -0,0 +1,50 @@
|
|||||||
|
# TREZOR REFERENCE SOURCE LICENSE (T-RSL)
|
||||||
|
|
||||||
|
This license governs use of the accompanying software. If you use the software,
|
||||||
|
you accept this license. If you do not accept the license, do not use the
|
||||||
|
software.
|
||||||
|
|
||||||
|
## 1. Definitions
|
||||||
|
|
||||||
|
The terms "reproduce," "reproduction" and "distribution" have the same meaning
|
||||||
|
here as under U.S. copyright law.
|
||||||
|
|
||||||
|
"You" means the licensee of the software.
|
||||||
|
|
||||||
|
"Your company" means the company you worked for when you downloaded the
|
||||||
|
software.
|
||||||
|
|
||||||
|
"Reference use" means use of the software within your company as a reference,
|
||||||
|
in read only form, for the sole purposes of debugging your products,
|
||||||
|
maintaining your products, or enhancing the interoperability of your products
|
||||||
|
with the software, and specifically excludes the right to distribute the
|
||||||
|
software outside of your company.
|
||||||
|
|
||||||
|
"Licensed patents" means any Licensor patent claims which read directly on the
|
||||||
|
software as distributed by the Licensor under this license.
|
||||||
|
|
||||||
|
## 2. Grant of Rights
|
||||||
|
|
||||||
|
(A) Copyright Grant - Subject to the terms of this license, the Licensor grants
|
||||||
|
you a non-transferable, non-exclusive, worldwide, royalty-free copyright
|
||||||
|
license to reproduce the software for reference use.
|
||||||
|
|
||||||
|
(B) Patent Grant - Subject to the terms of this license, the Licensor grants
|
||||||
|
you a non-transferable, non-exclusive, worldwide, royalty-free patent license
|
||||||
|
under licensed patents for reference use.
|
||||||
|
|
||||||
|
## 3. Limitations
|
||||||
|
|
||||||
|
(A) No Trademark License - This license does not grant you any rights to use
|
||||||
|
the Licensor's name, logo, or trademarks.
|
||||||
|
|
||||||
|
(B) If you begin patent litigation against the Licensor over patents that you
|
||||||
|
think may apply to the software (including a cross-claim or counterclaim in
|
||||||
|
a lawsuit), your license to the software ends automatically.
|
||||||
|
|
||||||
|
(C) The software is licensed "as-is." You bear the risk of using it. The
|
||||||
|
Licensor gives no express warranties, guarantees or conditions. You may have
|
||||||
|
additional consumer rights under your local laws which this license cannot
|
||||||
|
change. To the extent permitted under your local laws, the Licensor excludes
|
||||||
|
the implied warranties of merchantability, fitness for a particular purpose and
|
||||||
|
non-infringement.
|
@ -0,0 +1,210 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import Web3 from 'web3';
|
||||||
|
import HDKey from 'hdkey';
|
||||||
|
|
||||||
|
import EthereumjsUtil from 'ethereumjs-util';
|
||||||
|
import EthereumjsUnits from 'ethereumjs-units';
|
||||||
|
import EthereumjsTx from 'ethereumjs-tx';
|
||||||
|
import TrezorConnect from 'trezor-connect';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
|
import type { EstimateGasOptions } from 'web3';
|
||||||
|
import type { TransactionStatus, TransactionReceipt } from 'web3';
|
||||||
|
import { strip } from 'utils/ethUtils';
|
||||||
|
import * as BLOCKCHAIN from 'actions/constants/blockchain';
|
||||||
|
import * as WEB3 from 'actions/constants/web3';
|
||||||
|
import * as PENDING from 'actions/constants/pendingTx';
|
||||||
|
|
||||||
|
import * as AccountsActions from './AccountsActions';
|
||||||
|
import * as Web3Actions from './Web3Actions';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
TrezorDevice,
|
||||||
|
Dispatch,
|
||||||
|
GetState,
|
||||||
|
Action,
|
||||||
|
AsyncAction,
|
||||||
|
PromiseAction,
|
||||||
|
ThunkAction,
|
||||||
|
} from 'flowtype';
|
||||||
|
import type { EthereumAccount } from 'trezor-connect';
|
||||||
|
import type { Token } from 'reducers/TokensReducer';
|
||||||
|
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
||||||
|
|
||||||
|
export type BlockchainAction = {
|
||||||
|
type: typeof BLOCKCHAIN.READY,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const discoverAccount = (device: TrezorDevice, address: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch, getState: GetState): Promise<EthereumAccount> => {
|
||||||
|
// get data from connect
|
||||||
|
// Temporary disabled, enable after trezor-connect@5.0.32 release
|
||||||
|
const txs = await TrezorConnect.ethereumGetAccountInfo({
|
||||||
|
account: {
|
||||||
|
address,
|
||||||
|
block: 0,
|
||||||
|
transactions: 0,
|
||||||
|
balance: "0",
|
||||||
|
nonce: 0
|
||||||
|
},
|
||||||
|
coin: network,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!txs.success) {
|
||||||
|
throw new Error(txs.payload.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockbook web3 fallback
|
||||||
|
const web3account = await dispatch( Web3Actions.discoverAccount(address, network) );
|
||||||
|
// return { transactions: txs.payload, ...web3account };
|
||||||
|
return {
|
||||||
|
address,
|
||||||
|
transactions: txs.payload.transactions,
|
||||||
|
block: txs.payload.block,
|
||||||
|
balance: web3account.balance,
|
||||||
|
nonce: web3account.nonce,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTokenInfo = (input: string, network: string): PromiseAction<NetworkToken> => async (dispatch: Dispatch, getState: GetState): Promise<NetworkToken> => {
|
||||||
|
return await dispatch( Web3Actions.getTokenInfo(input, network) );
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTokenBalance = (token: Token): PromiseAction<string> => async (dispatch: Dispatch, getState: GetState): Promise<string> => {
|
||||||
|
return await dispatch( Web3Actions.getTokenBalance(token) );
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getGasPrice = (network: string, defaultGasPrice: number): PromiseAction<BigNumber> => async (dispatch: Dispatch, getState: GetState): Promise<BigNumber> => {
|
||||||
|
try {
|
||||||
|
const gasPrice = await dispatch( Web3Actions.getCurrentGasPrice(network) );
|
||||||
|
return new BigNumber(gasPrice);
|
||||||
|
} catch (error) {
|
||||||
|
return new BigNumber(defaultGasPrice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const estimateGasLimit = (network: string, data: string, value: string, gasPrice: string): PromiseAction<number> => async (dispatch: Dispatch, getState: GetState): Promise<number> => {
|
||||||
|
return await dispatch( Web3Actions.estimateGasLimit(network, { to: '', data, value, gasPrice }) );
|
||||||
|
}
|
||||||
|
|
||||||
|
export const onBlockMined = (coinInfo: any): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
// incoming "coinInfo" from TrezorConnect is CoinInfo | EthereumNetwork type
|
||||||
|
const network: string = coinInfo.shortcut.toLowerCase();
|
||||||
|
|
||||||
|
// try to resolve pending transactions
|
||||||
|
await dispatch( Web3Actions.resolvePendingTransactions(network) );
|
||||||
|
|
||||||
|
await dispatch( Web3Actions.updateGasPrice(network) );
|
||||||
|
|
||||||
|
const accounts: Array<any> = getState().accounts.filter(a => a.network === network);
|
||||||
|
if (accounts.length > 0) {
|
||||||
|
// find out which account changed
|
||||||
|
const response = await TrezorConnect.ethereumGetAccountInfo({
|
||||||
|
accounts,
|
||||||
|
coin: network,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
response.payload.forEach((a, i) => {
|
||||||
|
if (a.transactions > 0) {
|
||||||
|
// load additional data from Web3 (balance, nonce, tokens)
|
||||||
|
dispatch( Web3Actions.updateAccount(accounts[i], a, network) )
|
||||||
|
} else {
|
||||||
|
// there are no new txs, just update block
|
||||||
|
dispatch( AccountsActions.update( { ...accounts[i], block: a.block }) );
|
||||||
|
|
||||||
|
// HACK: since blockbook can't work with smart contracts for now
|
||||||
|
// try to update tokens balances added to this account using Web3
|
||||||
|
dispatch( Web3Actions.updateAccountTokens(accounts[i]) );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// not used for now, waiting for fix in blockbook
|
||||||
|
export const onNotification = (payload: any): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
|
||||||
|
// this event can be triggered multiple times
|
||||||
|
// 1. check if pair [txid + address] is already in reducer
|
||||||
|
const network: string = payload.coin.shortcut.toLowerCase();
|
||||||
|
const address: string = EthereumjsUtil.toChecksumAddress(payload.tx.address);
|
||||||
|
const txInfo = await dispatch( Web3Actions.getPendingInfo(network, payload.tx.txid) );
|
||||||
|
|
||||||
|
// const exists = getState().pending.filter(p => p.id === payload.tx.txid && p.address === address);
|
||||||
|
const exists = getState().pending.filter(p => {
|
||||||
|
return p.address === address;
|
||||||
|
});
|
||||||
|
if (exists.length < 1) {
|
||||||
|
if (txInfo) {
|
||||||
|
dispatch({
|
||||||
|
type: PENDING.ADD,
|
||||||
|
payload: {
|
||||||
|
type: 'send',
|
||||||
|
id: payload.tx.txid,
|
||||||
|
network,
|
||||||
|
currency: "tETH",
|
||||||
|
amount: txInfo.value,
|
||||||
|
total: "0",
|
||||||
|
tx: {},
|
||||||
|
nonce: txInfo.nonce,
|
||||||
|
address,
|
||||||
|
rejected: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// tx info not found (yet?)
|
||||||
|
// dispatch({
|
||||||
|
// type: PENDING.ADD_UNKNOWN,
|
||||||
|
// payload: {
|
||||||
|
// network,
|
||||||
|
// ...payload.tx,
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const subscribe = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
const addresses: Array<string> = getState().accounts.filter(a => a.network === network).map(a => a.address);
|
||||||
|
// $FlowIssue: trezor-connect@5.0.32
|
||||||
|
return await TrezorConnect.blockchainSubscribe({
|
||||||
|
// accounts: addresses,
|
||||||
|
accounts: [],
|
||||||
|
coin: network
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conditionally subscribe to blockchain backend
|
||||||
|
// called after TrezorConnect.init successfully emits TRANSPORT.START event
|
||||||
|
// checks if there are discovery processes loaded from LocalStorage
|
||||||
|
// if so starts subscription to proper networks
|
||||||
|
export const init = (): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
if (getState().discovery.length > 0) {
|
||||||
|
// get unique networks
|
||||||
|
const networks: Array<string> = [];
|
||||||
|
getState().discovery.forEach(discovery => {
|
||||||
|
if (networks.indexOf(discovery.network) < 0) {
|
||||||
|
networks.push(discovery.network);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// subscribe
|
||||||
|
for (let i = 0; i < networks.length; i++) {
|
||||||
|
await dispatch( subscribe(networks[i]) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue wallet initialization
|
||||||
|
dispatch({
|
||||||
|
type: BLOCKCHAIN.READY
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
|
||||||
|
// disconnect and remove Web3 webscocket instance if exists
|
||||||
|
export const error = (payload: any): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
dispatch( Web3Actions.disconnect(payload.coin) );
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import EthereumjsTx from 'ethereumjs-tx';
|
||||||
|
import EthereumjsUnits from 'ethereumjs-units';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { toHex } from 'web3-utils';
|
||||||
|
import { initWeb3 } from './Web3Actions';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Dispatch,
|
||||||
|
GetState,
|
||||||
|
PromiseAction,
|
||||||
|
} from 'flowtype';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
EthereumTransaction
|
||||||
|
} from 'trezor-connect';
|
||||||
|
|
||||||
|
import type { Token } from 'reducers/TokensReducer';
|
||||||
|
|
||||||
|
type EthereumTxRequest = {
|
||||||
|
network: string;
|
||||||
|
token: ?Token;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
amount: string;
|
||||||
|
data: string;
|
||||||
|
gasLimit: string;
|
||||||
|
gasPrice: string;
|
||||||
|
nonce: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction<EthereumTransaction> => async (dispatch: Dispatch, getState: GetState): Promise<EthereumTransaction> => {
|
||||||
|
const instance = await dispatch( initWeb3(tx.network) );
|
||||||
|
const token = tx.token;
|
||||||
|
let data: string = `0x${tx.data}`; // TODO: check if already prefixed
|
||||||
|
let value: string = toHex( EthereumjsUnits.convert(tx.amount, 'ether', 'wei') );
|
||||||
|
let to: string = tx.to;
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
// smart contract transaction
|
||||||
|
const contract = instance.erc20.clone();
|
||||||
|
contract.options.address = token.address;
|
||||||
|
const tokenAmount: string = new BigNumber(tx.amount).times(Math.pow(10, token.decimals)).toString(10);
|
||||||
|
data = instance.erc20.methods.transfer(to, tokenAmount).encodeABI();
|
||||||
|
value = '0x00';
|
||||||
|
to = token.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
chainId: instance.chainId,
|
||||||
|
nonce: toHex(tx.nonce),
|
||||||
|
gasLimit: toHex(tx.gasLimit),
|
||||||
|
gasPrice: toHex( EthereumjsUnits.convert(tx.gasPrice, 'gwei', 'wei') ),
|
||||||
|
r: '',
|
||||||
|
s: '',
|
||||||
|
v: '',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const serializeEthereumTx = (tx: EthereumTransaction): PromiseAction<string> => async (dispatch: Dispatch, getState: GetState): Promise<string> => {
|
||||||
|
const ethTx = new EthereumjsTx(tx);
|
||||||
|
return `0x${ ethTx.serialize().toString('hex') }`;
|
||||||
|
// return toHex( ethTx.serialize() );
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
export const READY: 'blockchain__ready' = 'blockchain__ready';
|
||||||
|
export const CONNECTING: 'blockchain__connecting' = 'blockchain__connecting';
|
||||||
|
export const CONNECTED: 'blockchain__connected' = 'blockchain__connected';
|
||||||
|
export const DISCONNECTED: 'blockchain__disconnected' = 'blockchain__disconnected';
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
@ -0,0 +1,64 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import { BLOCKCHAIN } from 'trezor-connect';
|
||||||
|
|
||||||
|
import type { Action } from 'flowtype';
|
||||||
|
|
||||||
|
export type BlockchainNetwork = {
|
||||||
|
+name: string;
|
||||||
|
connected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type State = Array<BlockchainNetwork>;
|
||||||
|
|
||||||
|
export const initialState: State = [];
|
||||||
|
|
||||||
|
const find = (state: State, name: string): number => {
|
||||||
|
return state.findIndex(b => b.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const connect = (state: State, action: any): State => {
|
||||||
|
const name = action.payload.coin.shortcut.toLowerCase();
|
||||||
|
const network: BlockchainNetwork = {
|
||||||
|
name,
|
||||||
|
connected: true,
|
||||||
|
}
|
||||||
|
const newState: State = [...state];
|
||||||
|
const index: number = find(newState, name);
|
||||||
|
if (index >= 0) {
|
||||||
|
newState[index] = network;
|
||||||
|
} else {
|
||||||
|
newState.push(network);
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
};
|
||||||
|
|
||||||
|
const disconnect = (state: State, action: any): State => {
|
||||||
|
const name = action.payload.coin.shortcut.toLowerCase();
|
||||||
|
const network: BlockchainNetwork = {
|
||||||
|
name,
|
||||||
|
connected: false,
|
||||||
|
}
|
||||||
|
const newState: State = [...state];
|
||||||
|
const index: number = find(newState, name);
|
||||||
|
if (index >= 0) {
|
||||||
|
newState[index] = network;
|
||||||
|
} else {
|
||||||
|
newState.push(network);
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default (state: State = initialState, action: Action): State => {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case BLOCKCHAIN.CONNECT:
|
||||||
|
return connect(state, action);
|
||||||
|
case BLOCKCHAIN.ERROR:
|
||||||
|
return disconnect(state, action);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in new issue