Merge pull request #5149 from finned-palmer/switch-to-typescript

btc-wallet: Port btc-wallet to typescript.
This commit is contained in:
ixv 2021-08-18 09:59:01 -07:00 committed by GitHub
commit 3f38506125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1284 additions and 875 deletions

View File

@ -0,0 +1,2 @@
config/webpack.dev.js
config/webpack.prod.js

View File

@ -6,9 +6,10 @@ const urbitrc = require('./urbitrc');
const fs = require('fs-extra');
const _ = require('lodash');
function copy(src,dest) {
return new Promise((res,rej) =>
fs.copy(src,dest, err => err ? rej(err) : res()));
function copy(src, dest) {
return new Promise((res, rej) =>
fs.copy(src, dest, (err) => (err ? rej(err) : res()))
);
}
class UrbitShipPlugin {
@ -35,9 +36,12 @@ let devServer = {
historyApiFallback: true,
};
const router = _.mapKeys(urbitrc.FLEET || {}, (value, key) => `${key}.localhost:9000`);
const router = _.mapKeys(
urbitrc.FLEET || {},
(value, key) => `${key}.localhost:9000`
);
if(urbitrc.URL) {
if (urbitrc.URL) {
devServer = {
...devServer,
index: '',
@ -45,17 +49,17 @@ if(urbitrc.URL) {
'/~btc/js/bundle/index.*.js': {
target: 'http://localhost:9000',
pathRewrite: (req, path) => {
return '/index.js'
}
return '/index.js';
},
},
'**': {
changeOrigin: true,
target: urbitrc.URL,
router,
// ensure proxy doesn't timeout channels
proxyTimeout: 0
}
}
proxyTimeout: 0,
},
},
};
}
@ -63,30 +67,16 @@ module.exports = {
node: { fs: 'empty' },
mode: 'development',
entry: {
app: './src/index.js'
app: './src/index.tsx',
},
module: {
rules: [
{
test: /\.(j|t)sx?$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', ['@babel/preset-react', {
runtime: 'automatic',
development: 'true',
importSource: '@welldone-software/why-did-you-render',
}]],
plugins: [
'@babel/transform-runtime',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-class-properties',
'react-hot-loader/babel'
]
}
loader: 'ts-loader',
},
exclude: /node_modules/
exclude: /node_modules/,
},
{
test: /\.css$/i,
@ -96,33 +86,31 @@ module.exports = {
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader'
]
}
]
'sass-loader',
],
},
],
},
resolve: {
extensions: ['.js', '.ts', '.tsx']
extensions: ['.js', '.ts', '.tsx'],
},
devtool: 'inline-source-map',
devServer: devServer,
plugins: [
new UrbitShipPlugin(urbitrc)
],
plugins: [new UrbitShipPlugin(urbitrc)],
watch: true,
watchOptions: {
poll: true,
ignored: '/node_modules/'
ignored: '/node_modules/',
},
output: {
filename: 'index.js',
chunkFilename: 'index.js',
path: path.resolve(__dirname, '../dist'),
publicPath: '/',
globalObject: 'this'
globalObject: 'this',
},
optimization: {
minimize: false,
usedExports: true
}
usedExports: true,
},
};

View File

@ -6,55 +6,46 @@ module.exports = {
node: { fs: 'empty' },
mode: 'production',
entry: {
app: './src/index.js'
app: './src/index.tsx',
},
module: {
rules: [
{
test: /\.jsx?$/,
test: /\.(j|t)sx?$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/transform-runtime',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-class-properties'
]
}
loader: 'ts-loader',
},
exclude: /node_modules/
exclude: /node_modules/,
},
{
test: /\.css$/i,
test: /\.css$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader'
]
}
]
'sass-loader',
],
},
],
},
resolve: {
extensions: ['.js', '.ts', '.tsx']
extensions: ['.js', '.ts', '.tsx'],
},
devtool: 'source-map',
plugins: [
new CleanWebpackPlugin()
],
plugins: [new CleanWebpackPlugin()],
output: {
filename: (pathData) => {
return pathData.chunk.name === 'app' ? 'index.[contenthash].js' : '[name].js';
return pathData.chunk.name === 'app'
? 'index.[contenthash].js'
: '[name].js';
},
path: path.resolve(__dirname, `../../arvo/app/btc-wallet/js/bundle`),
publicPath: '/',
},
optimization: {
minimize: true,
usedExports: true
}
usedExports: true,
},
};

View File

@ -1430,6 +1430,22 @@
"@types/node": "*"
}
},
"@types/history": {
"version": "4.7.9",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
"integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==",
"dev": true
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dev": true,
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/html-minifier-terser": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz",
@ -1441,6 +1457,12 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA=="
},
"@types/lodash": {
"version": "4.14.171",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz",
"integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz",
@ -1459,12 +1481,76 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
},
"@types/prop-types": {
"version": "15.7.4",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
"integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==",
"dev": true
},
"@types/react": {
"version": "17.0.15",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.15.tgz",
"integrity": "sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"@types/react-dom": {
"version": "17.0.9",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz",
"integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-router": {
"version": "5.1.16",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.16.tgz",
"integrity": "sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==",
"dev": true,
"requires": {
"@types/history": "*",
"@types/react": "*"
}
},
"@types/react-router-dom": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.8.tgz",
"integrity": "sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==",
"dev": true,
"requires": {
"@types/history": "*",
"@types/react": "*",
"@types/react-router": "*"
}
},
"@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
"dev": true
},
"@types/source-list-map": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
"integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
"dev": true
},
"@types/styled-components": {
"version": "5.1.11",
"resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.11.tgz",
"integrity": "sha512-u8g3bSw9KUiZY+S++gh+LlURGraqBe3MC5I5dygrNjGDHWWQfsmZZRTJ9K9oHU2CqWtxChWmJkDI/gp+TZPQMw==",
"dev": true,
"requires": {
"@types/hoist-non-react-statics": "*",
"@types/react": "*",
"csstype": "^3.0.2"
}
},
"@types/tapable": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.7.tgz",
@ -1499,6 +1585,12 @@
"source-map": "^0.6.0"
}
},
"@types/webpack-env": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.2.tgz",
"integrity": "sha512-vKx7WNQNZDyJveYcHAm9ZxhqSGLYwoyLhrHjLBOkw3a7cT76sTdjgtwyijhk1MaHyRIuSztcVwrUOO/NEu68Dw==",
"dev": true
},
"@types/webpack-sources": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.1.0.tgz",
@ -3472,6 +3564,12 @@
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
},
"csstype": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
"integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==",
"dev": true
},
"cycle": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
@ -10240,6 +10338,130 @@
"resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.1.1.tgz",
"integrity": "sha512-74MoNHhwLVuzwaPDcAecFjSkOA9vwWqyOdkeB0Be8Jc/IWSS5SNZKapFllqzkTliqZptkvqX5CZnVeDvfhN8cw=="
},
"ts-loader": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.2.0.tgz",
"integrity": "sha512-ebXBFrNyMSmbWgjnb3WBloUBK+VSx1xckaXsMXxlZRDqce/OPdYBVN5efB0W3V0defq0Gcy4YuzvPGqRgjj85A==",
"dev": true,
"requires": {
"chalk": "^4.1.0",
"enhanced-resolve": "^4.0.0",
"loader-utils": "^2.0.0",
"micromatch": "^4.0.0",
"semver": "^7.3.4"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"json5": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true,
"requires": {
"braces": "^3.0.1",
"picomatch": "^2.2.3"
}
},
"picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
"dev": true
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
@ -10293,9 +10515,9 @@
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
},
"typescript": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
"integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"dev": true
},
"unbox-primitive": {

View File

@ -20,6 +20,11 @@
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.13.0",
"@types/lodash": "^4.14.171",
"@types/react-dom": "^17.0.9",
"@types/react-router-dom": "^5.1.8",
"@types/styled-components": "^5.1.11",
"@types/webpack-env": "^1.16.2",
"@welldone-software/why-did-you-render": "^6.1.1",
"babel-loader": "^8.1.0",
"babel-plugin-root-import": "^6.5.0",
@ -36,7 +41,8 @@
"react-hot-loader": "^4.12.21",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"typescript": "^4.2.3",
"ts-loader": "8.2.0",
"typescript": "^4.3.5",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"

View File

@ -3,11 +3,11 @@ import { BrowserRouter } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';
import light from './themes/light';
import { Box, Reset } from '@tlon/indigo-react';
import StartupModal from './lib/startupModal.js';
import Body from './lib/body.js';
import { useSettings } from '../hooks/useSettings.js';
import StartupModal from './components/StartupModal';
import { useSettings } from './hooks/useSettings';
import Body from './components/Body';
const Root = () => {
const App: React.FC = () => {
const { loaded, wallet, provider, scanProgress } = useSettings();
const scanning = scanProgress?.main !== null || scanProgress?.change !== null;
const blur = !loaded || scanning ? false : !(wallet && provider);
@ -37,4 +37,4 @@ const Root = () => {
);
};
export default Root;
export default App;

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { Row, Text, Button, Col } from '@tlon/indigo-react';
import Send from './send.js';
import CurrencyPicker from './currencyPicker.js';
import { copyToClipboard, satsToCurrency } from '../../lib/util.js';
import { useSettings } from '../../hooks/useSettings.js';
import { api } from '../../api';
import Send from './Send/Send';
import CurrencyPicker from './CurrencyPicker';
import { copyToClipboard, satsToCurrency } from '../lib/util';
import { useSettings } from '../hooks/useSettings';
import { api } from '../lib/api';
const Balance = () => {
const {
@ -23,7 +23,7 @@ const Balance = () => {
const [copiedString, setCopiedString] = useState(false);
const scanning = scanProgress?.main !== null || scanProgress?.change !== null;
const copyAddress = async (arg) => {
const copyAddress = async (arg: 'string' | 'button') => {
await copyToClipboard(address);
api.btcWalletCommand({ 'gen-new-address': null });

View File

@ -1,14 +1,14 @@
import React from 'react';
import { Box, LoadingSpinner, Col } from '@tlon/indigo-react';
import { Switch, Route } from 'react-router-dom';
import Balance from './balance.js';
import Transactions from './transactions.js';
import Warning from './warning.js';
import Header from './header.js';
import Settings from './settings.js';
import { useSettings } from '../../hooks/useSettings.js';
import Balance from './Balance';
import Transactions from './Transactions/Transactions';
import Warning from './Warning';
import Header from './Header';
import Settings from './Settings';
import { useSettings } from '../hooks/useSettings';
const Body = () => {
const Body: React.FC = () => {
const { loaded, showWarning: warning } = useSettings();
const cardWidth = window.innerWidth <= 475 ? '350px' : '400px';
return !loaded ? (
@ -19,12 +19,7 @@ const Body = () => {
alignItems="center"
justifyContent="center"
>
<LoadingSpinner
width={7}
height={7}
background="midOrange"
foreground="orange"
/>
<LoadingSpinner background="midOrange" foreground="orange" />
</Box>
) : (
<Switch>

View File

@ -1,19 +1,20 @@
import React from 'react';
import { Icon, Row, Text } from '@tlon/indigo-react';
import { api } from '../../api';
import { useSettings } from '../../hooks/useSettings';
import { api } from '../lib/api';
import { useSettings } from '../hooks/useSettings';
const CurrencyPicker = () => {
const { denomination, currencyRates } = useSettings();
const switchCurrency = () => {
let newCurrency;
if (denomination === 'BTC') {
if (currencyRates['USD']) {
if ((currencyRates as any)['USD']) {
newCurrency = 'USD';
}
} else if (denomination === 'USD') {
newCurrency = 'BTC';
}
console.log({ newCurrency, denomination });
let setCurrency = {
'put-entry': {
value: newCurrency,

View File

@ -0,0 +1,32 @@
import React from 'react';
import { Text } from '@tlon/indigo-react';
enum ErrorTypes {
'cant-pay-ourselves' = 'Cannot pay ourselves',
'no-comets' = 'Cannot pay comets',
'no-dust' = 'Cannot send dust',
'tx-being-signed' = 'Cannot pay when transaction is being signed',
'insufficient-balance' = 'Insufficient confirmed balance',
'broadcast-fail' = 'Transaction broadcast failed',
'invalid-master-ticker' = 'Invalid master ticket',
'invalid-signed' = 'Invalid signed bitcoin transaction',
}
const Error = ({
error,
fontSize,
...rest
}: {
error: string;
fontSize?: string;
}) => (
<Text color="red" style={{ fontSize }} {...rest}>
{
(ErrorTypes as any)[
Object.keys(ErrorTypes).filter((et) => et === error)[0]
]
}
</Text>
);
export default Error;

View File

@ -1,9 +1,9 @@
import React from 'react';
import { Box, Icon, Row, Text } from '@tlon/indigo-react';
import { Link } from 'react-router-dom';
import { useSettings } from '../../hooks/useSettings';
import { useSettings } from '../hooks/useSettings';
const Header = ({ settings }) => {
const Header = ({ settings }: { settings: boolean }) => {
const { provider } = useSettings();
let icon = settings ? 'X' : 'Adjust';
let iconColor = settings ? 'black' : 'orange';

View File

@ -9,15 +9,15 @@ import {
LoadingSpinner,
} from '@tlon/indigo-react';
import { isValidPatp } from 'urbit-ob';
import { api } from '../../api';
import { useSettings } from '../../hooks/useSettings';
import { api } from '../lib/api';
import { useSettings } from '../hooks/useSettings';
const providerStatuses = {
checking: 'checking',
failed: 'failed',
ready: 'ready',
initial: '',
};
enum providerStatuses {
checking,
failed,
ready,
initial = '',
}
const ProviderModal = () => {
const { providerPerms } = useSettings();
@ -28,7 +28,7 @@ const ProviderModal = () => {
const [provider, setProvider] = useState(null);
const [connecting, setConnecting] = useState(false);
const checkProvider = (e) => {
const checkProvider = (e: React.ChangeEvent<HTMLInputElement>) => {
// TODO: loading states
setProviderStatus(providerStatuses.initial);
let givenProvider = e.target.value;
@ -103,7 +103,7 @@ const ProviderModal = () => {
to set a provider node. A provider node is an urbit which maintains a
synced Bitcoin ledger.
<a
fontSize="14px"
style={{ fontSize: '14px' }}
target="_blank"
href="https://urbit.org/bitcoin-wallet"
rel="noreferrer"
@ -132,7 +132,9 @@ const ProviderModal = () => {
backgroundColor={workingBg}
color={workingColor}
borderColor={workingColor}
onChange={(e) => checkProvider(e)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
checkProvider(e)
}
/>
{providerStatus === providerStatuses.checking ? (
<LoadingSpinner />
@ -150,7 +152,7 @@ const ProviderModal = () => {
providerStatus === providerStatuses.ready ? 'pointer' : 'default',
}}
onClick={() => {
submitProvider(provider);
submitProvider();
}}
>
Set Peer Node

View File

@ -9,16 +9,22 @@ import {
Col,
LoadingSpinner,
} from '@tlon/indigo-react';
import { Sigil } from './sigil.js';
import Sigil from '../Sigil';
import * as bitcoin from 'bitcoinjs-lib';
import { isValidPatp } from 'urbit-ob';
import Sent from './sent.js';
import Error from './error.js';
import { satsToCurrency } from '../../lib/util.js';
import { useSettings } from '../../hooks/useSettings.js';
import { api } from '../../api';
import Sent from './Sent';
import Error from '../Error';
import { satsToCurrency } from '../../lib/util';
import { useSettings } from '../../hooks/useSettings';
import { api } from '../../lib/api';
const BridgeInvoice = ({ payee, stopSending, satsAmount }) => {
type Props = {
payee: string;
stopSending: () => void;
satsAmount: number;
};
const BridgeInvoice: React.FC<Props> = ({ payee, stopSending, satsAmount }) => {
const { error, currencyRates, fee, broadcastSuccess, denomination, psbt } =
useSettings();
const [txHex, setTxHex] = useState('');
@ -40,14 +46,14 @@ const BridgeInvoice = ({ payee, stopSending, satsAmount }) => {
window.open('https://bridge.urbit.org/?kind=btc&utx=' + psbt);
});
const broadCastTx = (hex) => {
const broadCastTx = (hex: string) => {
let command = {
'broadcast-tx': hex,
};
return api.btcWalletCommand(command);
};
const sendBitcoin = (hex) => {
const sendBitcoin = (hex: string) => {
try {
bitcoin.Transaction.fromHex(hex);
broadCastTx(hex);
@ -58,7 +64,7 @@ const BridgeInvoice = ({ payee, stopSending, satsAmount }) => {
}
};
const checkTxHex = (e) => {
const checkTxHex = (e: React.ChangeEvent<HTMLInputElement>) => {
setTxHex(e.target.value);
setReady(txHex.length > 0);
setLocalError('');
@ -169,11 +175,11 @@ const BridgeInvoice = ({ payee, stopSending, satsAmount }) => {
backgroundColor={inputBg}
borderColor={inputBorder}
style={{ lineHeight: '4' }}
onChange={(e) => checkTxHex(e)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => checkTxHex(e)}
/>
{localError !== '' && (
<Row>
<Error error={localError} fontSize="14px" mt={2} />
<Error error={localError} fontSize="14px" />
</Row>
)}
<Row flexDirection="row-reverse" mt={4} alignItems="center">
@ -200,7 +206,10 @@ const BridgeInvoice = ({ payee, stopSending, satsAmount }) => {
>
Send BTC
</Button>
{broadcasting ? <LoadingSpinner mr={3} /> : null}
{
// @ts-ignore
broadcasting ? <LoadingSpinner mr={3} /> : null
}
</Row>
</Col>
)}

View File

@ -9,16 +9,26 @@ import {
Col,
LoadingSpinner,
} from '@tlon/indigo-react';
import { Sigil } from './sigil.js';
import Sigil from '../Sigil';
import * as bitcoin from 'bitcoinjs-lib';
import { isValidPatp } from 'urbit-ob';
import Sent from './sent.js';
import Error from './error.js';
import Error from '../Error';
import { copyToClipboard, satsToCurrency } from '../../lib/util.js';
import { useSettings } from '../../hooks/useSettings.js';
import { api } from '../../api';
import { api } from '../../lib/api';
const ExternalInvoice = ({ payee, stopSending, satsAmount }) => {
type Props = {
payee: string;
stopSending: () => void;
satsAmount: number;
};
const ExternalInvoice: React.FC<Props> = ({
payee,
stopSending,
satsAmount,
}) => {
const { error, currencyRates, fee, broadcastSuccess, denomination, psbt } =
useSettings();
const [txHex, setTxHex] = useState('');
@ -36,14 +46,14 @@ const ExternalInvoice = ({ payee, stopSending, satsAmount }) => {
}
}, [error, broadcasting, setBroadcasting]);
const broadCastTx = (hex) => {
const broadCastTx = (hex: string) => {
let command = {
'broadcast-tx': hex,
};
return api.btcWalletCommand(command);
};
const sendBitcoin = (hex) => {
const sendBitcoin = (hex: string) => {
try {
bitcoin.Transaction.fromHex(hex);
broadCastTx(hex);
@ -54,7 +64,7 @@ const ExternalInvoice = ({ payee, stopSending, satsAmount }) => {
}
};
const checkTxHex = (e) => {
const checkTxHex = (e: React.ChangeEvent<HTMLInputElement>) => {
setTxHex(e.target.value);
setReady(txHex.length > 0);
setLocalError('');
@ -211,11 +221,13 @@ const ExternalInvoice = ({ payee, stopSending, satsAmount }) => {
backgroundColor={inputBg}
borderColor={inputBorder}
style={{ lineHeight: '4' }}
onChange={(e) => checkTxHex(e)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
checkTxHex(e)
}
/>
{localError !== '' && (
<Row>
<Error error={localError} fontSize="14px" mt={2} />
<Error error={localError} fontSize="14px" />
</Row>
)}
</Row>
@ -249,7 +261,7 @@ const ExternalInvoice = ({ payee, stopSending, satsAmount }) => {
>
Send BTC
</Button>
{broadcasting ? <LoadingSpinner mr={3} /> : null}
{broadcasting ? <LoadingSpinner /> : null}
</Row>
</Col>
)}

View File

@ -0,0 +1,84 @@
import React from 'react';
import {
Box,
Text,
Col,
StatelessRadioButtonField as RadioButton,
Label,
} from '@tlon/indigo-react';
import { FeeChoices, feeLevels } from './Send';
type Props = {
feeChoices: FeeChoices;
feeValue: number;
setFeeValue: React.Dispatch<feeLevels>;
feeDismiss: () => void;
};
const FeePicker: React.FC<Props> = ({
feeChoices,
feeValue,
setFeeValue,
feeDismiss,
}) => (
<Box
position="absolute"
p={4}
border="1px solid green"
zIndex={10}
backgroundColor="white"
borderRadius={3}
>
<Text fontSize={1} color="black" fontWeight="bold" mb={4}>
Transaction Speed
</Text>
<Col mt={4}>
<RadioButton
name="feeRadio"
selected={feeValue === feeLevels.low}
p="2"
onChange={() => {
setFeeValue(feeLevels.low);
feeDismiss();
}}
>
<Label fontSize="14px">
Slow: {feeChoices[feeLevels.low][1]} sats/vbyte ~
{feeChoices[feeLevels.low][0]}m
</Label>
</RadioButton>
<RadioButton
name="feeRadio"
selected={feeValue === feeLevels.mid}
p="2"
onChange={() => {
setFeeValue(feeLevels.mid);
feeDismiss();
}}
>
<Label fontSize="14px">
Normal: {feeChoices[feeLevels.mid][1]} sats/vbyte ~
{feeChoices[feeLevels.mid][0]}m
</Label>
</RadioButton>
<RadioButton
name="feeRadio"
selected={feeValue === feeLevels.high}
p="2"
onChange={() => {
setFeeValue(feeLevels.high);
feeDismiss();
}}
>
<Label fontSize="14px">
Fast: {feeChoices[feeLevels.high][1]} sats/vbyte ~
{feeChoices[feeLevels.high][0]}m
</Label>
</RadioButton>
</Col>
</Box>
);
export default FeePicker;

View File

@ -9,15 +9,16 @@ import {
Col,
LoadingSpinner,
} from '@tlon/indigo-react';
import { Sigil } from './sigil.js';
import * as bitcoin from 'bitcoinjs-lib';
import * as kg from 'urbit-key-generation';
import Sent from './sent.js';
import { patp2dec, isValidPatq, isValidPatp } from 'urbit-ob';
import { satsToCurrency } from '../../lib/util.js';
import Error from './error.js';
import { useSettings } from '../../hooks/useSettings.js';
import { api } from '../../api';
import * as bitcoin from 'bitcoinjs-lib';
import Sigil from '../Sigil';
import Sent from './Sent';
import { satsToCurrency } from '../../lib/util';
import Error from '../Error';
import { useSettings } from '../../hooks/useSettings';
import { api } from '../../lib/api';
import { UrbitWallet } from '../../types';
const BITCOIN_MAINNET_INFO = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
@ -43,7 +44,13 @@ const BITCOIN_TESTNET_INFO = {
wif: 0xef,
};
const Invoice = ({ stopSending, payee, satsAmount }) => {
type Props = {
stopSending: () => void;
payee: string;
satsAmount: number;
};
const Invoice: React.FC<Props> = ({ stopSending, payee, satsAmount }) => {
const {
error,
currencyRates,
@ -64,20 +71,20 @@ const Invoice = ({ stopSending, payee, satsAmount }) => {
}
}, [error, broadcasting, setBroadcasting]);
const broadCastTx = (psbtHex) => {
const broadCastTx = (psbtHex: string) => {
let command = {
'broadcast-tx': psbtHex,
};
return api.btcWalletCommand(command);
};
const sendBitcoin = (ticket, psbt) => {
const sendBitcoin = (ticket: string, psbt: string) => {
const newPsbt = bitcoin.Psbt.fromBase64(psbt);
setBroadcasting(true);
kg.generateWallet({
ticket,
ship: parseInt(patp2dec('~' + window.ship)),
}).then((urbitWallet) => {
ship: parseInt(patp2dec('~' + (window as any).ship)),
}).then((urbitWallet: UrbitWallet) => {
// this wasn't being used, not clear why it was pulled out.
// const { xpub } =
// network === 'testnet'
@ -116,7 +123,9 @@ const Invoice = ({ stopSending, payee, satsAmount }) => {
});
};
const checkTicket = ({ target: { value } }) => {
const checkTicket = ({
target: { value },
}: React.ChangeEvent<HTMLInputElement>) => {
// TODO: port over bridge ticket validation logic
setMasterTicket(value);
setReady(isValidPatq(value));
@ -216,19 +225,21 @@ const Invoice = ({ stopSending, payee, satsAmount }) => {
fontSize="14px"
type="password"
name="masterTicket"
obscure={(value) => value.replace(/[^~-]+/g, '••••••')}
obscure={(value: string) => value.replace(/[^~-]+/g, '••••••')}
placeholder="••••••-••••••-••••••-••••••"
autoCapitalize="none"
autoCorrect="off"
color={inputColor}
backgroundColor={inputBg}
borderColor={inputBorder}
onChange={(e) => checkTicket(e)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
checkTicket(e)
}
/>
</Row>
{error !== '' && (
<Row>
<Error fontSize="14px" color="red" error={error} mt={2} />
<Error fontSize="14px" error={error} />
</Row>
)}
<Row flexDirection="row-reverse" mt={4} alignItems="center">
@ -252,7 +263,10 @@ const Invoice = ({ stopSending, payee, satsAmount }) => {
>
Send BTC
</Button>
{broadcasting ? <LoadingSpinner mr={3} /> : null}
{
// @ts-ignore
broadcasting ? <LoadingSpinner mr={3} /> : null
}
</Row>
</Col>
)}

View File

@ -9,81 +9,99 @@ import {
Col,
LoadingSpinner,
} from '@tlon/indigo-react';
import Invoice from './invoice.js';
import BridgeInvoice from './bridgeInvoice.js';
import FeePicker from './feePicker.js';
import Error from './error.js';
import Signer from './signer.js';
import Invoice from './Invoice';
import BridgeInvoice from './BridgeInvoice';
import ExternalInvoice from './ExternalInvoice';
import FeePicker from './FeePicker';
import Error from '../Error';
import Signer from './Signer';
import { validate } from 'bitcoin-address-validation';
import * as ob from 'urbit-ob';
import { useSettings } from '../../hooks/useSettings.js';
import { api } from '../../api';
import { deSig } from '../../lib/util.js';
import ExternalInvoice from './externalInvoice.js';
import { useSettings } from '../../hooks/useSettings';
import { api } from '../../lib/api';
import { deSig } from '../../lib/util';
const focusFields = {
empty: '',
payee: 'payee',
currency: 'currency',
sats: 'sats',
note: 'note',
enum focusFields {
payee,
currency,
sats,
note,
empty = '',
}
export enum feeLevels {
low,
mid,
high,
}
export enum signMethods {
bridge,
masterTicket,
external,
}
enum payeeTypes {
ship,
address,
initial = '',
}
export type FeeChoices = {
[feeLevels.low]: [number, number];
[feeLevels.mid]: [number, number];
[feeLevels.high]: [number, number];
};
export const feeLevels = {
low: 'low',
mid: 'mid',
high: 'high',
type Props = {
stopSending: () => void;
value: string;
conversion: number;
};
export const signMethods = {
bridge: 'bridge',
masterTicket: 'masterTicket',
external: 'external',
};
const Send = ({ stopSending, value, conversion }) => {
const Send: React.FC<Props> = ({ stopSending, value, conversion }) => {
const { error, setError, network, psbt, denomination, shipWallets } =
useSettings();
const [signing, setSigning] = useState(false);
const [denomAmount, setDenomAmount] = useState('0.00');
const [satsAmount, setSatsAmount] = useState('0');
const [denomAmount, setDenomAmount] = useState(0.0);
const [satsAmount, setSatsAmount] = useState(0);
const [payee, setPayee] = useState('');
const [checkingPatp, setCheckingPatp] = useState(false);
const [payeeType, setPayeeType] = useState('');
const [payeeType, setPayeeType] = useState<payeeTypes>(payeeTypes.initial);
const [ready, setReady] = useState(false);
const [validPayee, setValidPayee] = useState(false);
const [focusedField, setFocusedField] = useState(focusFields.empty);
const [feeChoices, setFeeChoices] = useState({
low: [10, 1],
mid: [10, 1],
high: [10, 1],
const [feeChoices, setFeeChoices] = useState<FeeChoices>({
[feeLevels.low]: [10, 1],
[feeLevels.mid]: [10, 1],
[feeLevels.high]: [10, 1],
});
const [feeValue, setFeeValue] = useState(feeLevels.mid);
const [showFeePicker, setShowFeePicker] = useState(false);
const [note, setNote] = useState('');
const [choosingSignMethod, setChoosingSignMethod] = useState(false);
const [signMethod, setSignMethod] = useState(signMethods.bridge);
const [signMethod, setSignMethod] = useState<signMethods>(signMethods.bridge);
const feeDismiss = () => {
setShowFeePicker(false);
};
const handleSetSignMethod = (signMethod) => {
const handleSetSignMethod = (signMethod: signMethods) => {
setSignMethod(signMethod);
setChoosingSignMethod(false);
};
const checkPayee = (e) => {
const checkPayee = (e: React.ChangeEvent<HTMLInputElement>) => {
setError('');
const validPatPCommand = (validPatP) => {
const validPatPCommand = (validPatP: string) => {
let command = { 'check-payee': validPatP };
api.btcWalletCommand(command);
setTimeout(() => {
setCheckingPatp(false);
}, 5000);
setCheckingPatp(true);
setPayeeType('ship');
setPayeeType(payeeTypes.ship);
setPayee(validPatP);
};
@ -96,13 +114,13 @@ const Send = ({ stopSending, value, conversion }) => {
setPayee(payeeReceived);
setReady(true);
setCheckingPatp(false);
setPayeeType('address');
setPayeeType(payeeTypes.address);
setValidPayee(true);
} else {
setPayee(payeeReceived);
setReady(false);
setCheckingPatp(false);
setPayeeType('');
setPayeeType(payeeTypes.initial);
setValidPayee(false);
}
};
@ -112,22 +130,22 @@ const Send = ({ stopSending, value, conversion }) => {
};
const initPayment = () => {
if (payeeType === 'ship') {
if (payeeType === payeeTypes.ship) {
let command = {
'init-payment': {
payee,
value: parseInt(satsAmount),
value: satsAmount,
feyb: feeChoices[feeValue][1],
note: note || null,
},
};
api.btcWalletCommand(command).then(() => setSigning(true));
} else if (payeeType === 'address') {
} else if (payeeType === payeeTypes.address) {
let command = {
'init-payment-external': {
address: payee,
value: parseInt(satsAmount),
value: satsAmount,
feyb: 1,
note: note || null,
},
@ -146,9 +164,9 @@ const Send = ({ stopSending, value, conversion }) => {
// let mid = Math.floor(estimates.length / 2);
// let high = estimates.length - 1;
setFeeChoices({
high: [30, n.estimates[30]['sat_per_vbyte']],
mid: [180, n.estimates[180]['sat_per_vbyte']],
low: [360, n.estimates[360]['sat_per_vbyte']],
[feeLevels.high]: [30, n.estimates[30]['sat_per_vbyte']],
[feeLevels.mid]: [180, n.estimates[180]['sat_per_vbyte']],
[feeLevels.low]: [360, n.estimates[360]['sat_per_vbyte']],
});
});
}
@ -175,7 +193,7 @@ const Send = ({ stopSending, value, conversion }) => {
payeeColor = 'green';
payeeBorder = 'green';
payeeBg = 'veryLightGreen';
} else if (!focusedField === focusFields.payee && validPayee) {
} else if (focusedField !== focusFields.payee && validPayee) {
payeeColor = 'blue';
payeeBorder = 'white';
payeeBg = 'white';
@ -184,17 +202,17 @@ const Send = ({ stopSending, value, conversion }) => {
payeeBorder = 'red';
payeeBg = 'veryLightRed';
} else if (
focusedField === 'payee' &&
focusedField === focusFields.payee &&
!validPayee &&
!checkingPatp &&
payeeType === 'ship'
payeeType === payeeTypes.ship
) {
payeeColor = 'red';
payeeBorder = 'red';
payeeBg = 'veryLightRed';
}
const signReady = ready && parseInt(satsAmount) > 0 && !signing;
const signReady = ready && satsAmount > 0 && !signing;
let invoice = null;
@ -286,20 +304,16 @@ const Send = ({ stopSending, value, conversion }) => {
value={payee}
fontFamily="mono"
disabled={signing}
onChange={(e) => checkPayee(e)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
checkPayee(e)
}
/>
</Row>
{error && (
<Row alignItems="center" justifyContent="space-between">
{/* yes this is a hack */}
<Box width="calc(40% - 30px)" />
<Error
error={error}
fontSize="14px"
ml={2}
mt={2}
width="100%"
/>
<Error error={error} fontSize="14px" />
</Row>
)}
<Row alignItems="center" mt={4} justifyContent="space-between">
@ -321,8 +335,8 @@ const Send = ({ stopSending, value, conversion }) => {
}
disabled={signing}
value={denomAmount}
onChange={(e) => {
setDenomAmount(e.target.value);
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setDenomAmount(parseFloat(e.target.value));
setSatsAmount(
Math.round(
(parseFloat(e.target.value) / conversion) * 100000000
@ -352,11 +366,11 @@ const Send = ({ stopSending, value, conversion }) => {
}
disabled={signing}
value={satsAmount}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setDenomAmount(
parseFloat(e.target.value) * (conversion / 100000000)
);
setSatsAmount(e.target.value);
setSatsAmount(parseInt(e.target.value, 10));
}}
/>
<Text color="lightGray" fontSize={1} ml={3}>
@ -431,7 +445,7 @@ const Send = ({ stopSending, value, conversion }) => {
}
disabled={signing}
value={note}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setNote(e.target.value);
}}
/>
@ -444,11 +458,7 @@ const Send = ({ stopSending, value, conversion }) => {
justifyContent="flex-end"
>
{!(signing && !error) ? null : (
<LoadingSpinner
mr={2}
background="midOrange"
foreground="orange"
/>
<LoadingSpinner background="midOrange" foreground="orange" />
)}
<Signer
signReady={signReady}
@ -464,7 +474,7 @@ const Send = ({ stopSending, value, conversion }) => {
fontWeight="bold"
borderRadius="24px"
height="48px"
onClick={() => toggleSignMethod(choosingSignMethod)}
onClick={() => toggleSignMethod()}
color={signReady ? 'white' : 'lighterGray'}
backgroundColor={
signReady ? 'rgba(33, 157, 255, 0.2)' : 'veryLightGray'
@ -479,7 +489,7 @@ const Send = ({ stopSending, value, conversion }) => {
/>
</Button>
</Row>
{signMethod === signMethod.masterTicket && (
{signMethod === signMethods.masterTicket && (
<Row mt={4} alignItems="center">
<Icon icon="Info" color="yellow" height={4} width={4} />
<Text fontSize="14px" fontWeight="regular" color="gray" ml={2}>

View File

@ -1,9 +1,15 @@
import React from 'react';
import { Icon, Row, Col, Center, Text } from '@tlon/indigo-react';
import { satsToCurrency } from '../../lib/util.js';
import { satsToCurrency } from '../../lib/util';
import { useSettings } from '../../hooks/useSettings';
const Sent = ({ payee, stopSending, satsAmount }) => {
type Props = {
payee: string;
stopSending: () => void;
satsAmount: number;
};
const Sent: React.FC<Props> = ({ payee, stopSending, satsAmount }) => {
const { denomination, currencyRates } = useSettings();
return (
<Col

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Box, Button, Icon, Row } from '@tlon/indigo-react';
import { signMethods } from './send';
import { signMethods } from './Send';
const signMethodLabels = {
bridge: 'Sign with Bridge',
@ -8,7 +8,15 @@ const signMethodLabels = {
external: 'Sign Externally (PSBT)',
};
const Signer = ({
type Props = {
signReady: boolean;
initPayment: () => void;
choosingSignMethod: boolean;
signMethod: signMethods;
setSignMethod: (arg: signMethods) => void;
};
const Signer: React.FC<Props> = ({
signReady,
initPayment,
choosingSignMethod,
@ -24,13 +32,15 @@ const Signer = ({
backgroundColor="transparent"
fontWeight="bold"
cursor="pointer"
color={signMethod === signMethods[method] ? 'blue' : 'lightBlue'}
color={
signMethod === (signMethods as any)[method] ? 'blue' : 'lightBlue'
}
height="48px"
onClick={() => setSignMethod(signMethods[method])}
onClick={() => setSignMethod((signMethods as any)[method])}
>
{signMethodLabels[method]}
{(signMethodLabels as any)[method]}
</Button>
{signMethod === signMethods[method] && (
{signMethod === (signMethods as any)[method] && (
<Button
borderRadius="24px"
width="24px"
@ -61,7 +71,7 @@ const Signer = ({
border="none"
style={{ cursor: signReady ? 'pointer' : 'default' }}
>
{signMethodLabels[signMethod]}
{(signMethodLabels as any)[signMethod]}
</Button>
);
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Row, Text, Button, Col } from '@tlon/indigo-react';
import { useSettings } from '../../hooks/useSettings';
import { api } from '../../api';
import { useSettings } from '../hooks/useSettings';
import { api } from '../lib/api';
const Settings = () => {
const { wallet, provider } = useSettings();

View File

@ -2,11 +2,11 @@ import React, { memo } from 'react';
import { sigil, reactRenderer } from '@tlon/sigil-js';
import { Box } from '@tlon/indigo-react';
export const foregroundFromBackground = (background) => {
export const foregroundFromBackground = (background: string) => {
const rgb = {
r: parseInt(background.slice(1, 3), 16),
g: parseInt(background.slice(3, 5), 16),
b: parseInt(background.slice(5, 7), 16)
b: parseInt(background.slice(5, 7), 16),
};
const brightness = (299 * rgb.r + 587 * rgb.g + 114 * rgb.b) / 1000;
const whiteBrightness = 255;
@ -14,7 +14,19 @@ export const foregroundFromBackground = (background) => {
return whiteBrightness - brightness < 50 ? 'black' : 'white';
};
export const Sigil = memo(
type Props = {
classes?: string;
color: string;
foreground?: string;
ship: string;
size: number;
svgClass?: string;
icon?: boolean;
padding?: number;
display?: string;
};
const Sigil: React.FC<Props> = memo(
({
classes = '',
color,
@ -24,7 +36,7 @@ export const Sigil = memo(
svgClass = '',
icon = false,
padding = 0,
display = 'inline-block'
display = 'inline-block',
}) => {
const innerSize = Number(size) - 2 * padding;
const paddingPx = `${padding}px`;
@ -55,7 +67,7 @@ export const Sigil = memo(
size: innerSize,
icon,
colors: [color, foregroundColor],
class: svgClass
class: svgClass,
})}
</Box>
);

View File

@ -1,10 +1,10 @@
import React from 'react';
import { Box } from '@tlon/indigo-react';
import WalletModal from './walletModal.js';
import ProviderModal from './providerModal.js';
import { useSettings } from '../../hooks/useSettings.js';
import WalletModal from './WalletModal';
import ProviderModal from './ProviderModal';
import { useSettings } from '../hooks/useSettings';
const StartupModal = () => {
const StartupModal: React.FC = () => {
const { wallet, provider } = useSettings();
let modal = null;

View File

@ -1,23 +1,28 @@
import React from 'react';
import { Box, Row, Text, Col } from '@tlon/indigo-react';
import _ from 'lodash';
import TxAction from './tx-action.js';
import TxCounterparty from './tx-counterparty.js';
import { satsToCurrency } from '../../lib/util.js';
import { useSettings } from '../../hooks/useSettings.js';
import TxAction from './TxAction';
import TxCounterparty from './TxCounterparty';
import { satsToCurrency } from '../../lib/util';
import { useSettings } from '../../hooks/useSettings';
import { Transaction as TransactionType } from '../../types';
const Transaction = ({ tx }) => {
const Transaction = ({ tx }: { tx: TransactionType }) => {
const { denomination, currencyRates } = useSettings();
const pending = !tx.recvd;
let weSent = _.find(tx.inputs, (input) => {
return input.ship === window.ship;
return input.ship === (window as any).ship;
});
let weRecv = tx.outputs.every((output) => {
return output.ship === window.ship;
return output.ship === (window as any).ship;
});
let action = weRecv ? 'recv' : weSent ? 'sent' : 'recv';
let action: 'sent' | 'recv' | 'fail' = weRecv
? 'recv'
: weSent
? 'sent'
: 'recv';
let counterShip = null;
let counterAddress = null;
@ -26,7 +31,7 @@ const Transaction = ({ tx }) => {
if (action === 'sent') {
let counter = _.find(tx.outputs, (output) => {
return output.ship !== window.ship;
return output.ship !== (window as any).ship;
});
counterShip = _.get(counter, 'ship', null);
counterAddress = _.get(counter, 'val.address', null);
@ -36,7 +41,7 @@ const Transaction = ({ tx }) => {
value = _.reduce(
tx.outputs,
(sum, output) => {
if (output.ship === window.ship) {
if (output.ship === (window as any).ship) {
return sum + output.val.value;
} else {
return sum;
@ -48,14 +53,14 @@ const Transaction = ({ tx }) => {
if (weSent && weRecv) {
counterAddress = _.get(
_.find(tx.inputs, (input) => {
return input.ship === window.ship;
return input.ship === (window as any).ship;
}),
'val.address',
null
);
} else {
let counter = _.find(tx.inputs, (input) => {
return input.ship !== window.ship;
return input.ship !== (window as any).ship;
});
counterShip = _.get(counter, 'ship', null);
counterAddress = _.get(counter, 'val.address', null);

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Box, Text, Col } from '@tlon/indigo-react';
import Transaction from './transaction.js';
import { useSettings } from '../../hooks/useSettings.js';
import Transaction from './Transaction';
import { useSettings } from '../../hooks/useSettings';
const Transactions = () => {
const { history } = useSettings();

View File

@ -2,7 +2,13 @@ import React from 'react';
import { Box, Icon, Row, Text, LoadingSpinner } from '@tlon/indigo-react';
import { useSettings } from '../../hooks/useSettings';
const TxAction = ({ action, pending, txid }) => {
type Props = {
action: 'sent' | 'recv' | 'fail';
pending: boolean;
txid: string;
};
const TxAction: React.FC<Props> = ({ action, pending, txid }) => {
const { network } = useSettings();
const leftIcon =
action === 'sent'

View File

@ -1,8 +1,13 @@
import React from 'react';
import { Box, Icon, Row, Text } from '@tlon/indigo-react';
import { Sigil } from './sigil.js';
import Sigil from '../Sigil';
const TxCounterparty = ({ ship, address }) => {
type Props = {
ship: string;
address: string;
};
const TxCounterparty: React.FC<Props> = ({ ship, address }) => {
const icon = ship ? (
<Sigil ship={ship} size={24} color="black" classes={''} icon padding={5} />
) : (

View File

@ -10,10 +10,11 @@ import {
} from '@tlon/indigo-react';
import { patp2dec, isValidPatq } from 'urbit-ob';
import * as kg from 'urbit-key-generation';
import { useSettings } from '../../hooks/useSettings';
import { api } from '../../api';
import { useSettings } from '../hooks/useSettings';
import { api } from '../lib/api';
import { UrbitWallet } from '../types';
const WalletModal = () => {
const WalletModal: React.FC = () => {
const { network } = useSettings();
const [mode, setMode] = useState('xpub');
const [masterTicket, setMasterTicket] = useState('');
@ -24,7 +25,9 @@ const WalletModal = () => {
const [confirmingMasterTicket, setConfirmingMasterTicket] = useState(false);
const [error, setError] = useState(false);
const checkTicket = ({ target: { value } }) => {
const checkTicket = ({
target: { value },
}: React.ChangeEvent<HTMLInputElement>) => {
// TODO: port over bridge ticket validation logic
if (confirmingMasterTicket) {
setConfirmedMasterTicket(value);
@ -35,13 +38,24 @@ const WalletModal = () => {
}
};
const checkXPub = ({ target: { value: xpubGiven } }) => {
const checkXPub = ({
target: { value: xpubGiven },
}: React.ChangeEvent<HTMLInputElement>) => {
setXpub(xpubGiven);
setReadyToSubmit(xpubGiven.length > 0);
};
const submitXPub = (givenXpub) => {
const command = {
const submitXPub = (givenXpub: string) => {
type AddWalletCommand = {
'add-wallet': {
xpub: string;
fprint: number[];
'scan-to': number | null;
'max-gap': number;
confs: number;
};
};
const command: AddWalletCommand = {
'add-wallet': {
xpub: givenXpub,
fprint: [4, 0],
@ -54,12 +68,12 @@ const WalletModal = () => {
setProcessingSubmission(true);
};
const submitMasterTicket = (ticket) => {
const submitMasterTicket = (ticket: string) => {
setProcessingSubmission(true);
kg.generateWallet({
ticket,
ship: parseInt(patp2dec('~' + window.ship)),
}).then((urbitWallet) => {
ship: parseInt(patp2dec('~' + (window as any).ship)),
}).then((urbitWallet: UrbitWallet) => {
const { xpub: xpubFromWallet } =
network === 'testnet'
? urbitWallet.bitcoinTestnet.keys
@ -117,11 +131,13 @@ const WalletModal = () => {
fontSize="14px"
type="password"
name="masterTicket"
obscure={(value) => value.replace(/[^~-]+/g, '••••••')}
obscure={(value: string) => value.replace(/[^~-]+/g, '••••••')}
placeholder="••••••-••••••-••••••-••••••"
autoCapitalize="none"
autoCorrect="off"
onChange={(e) => checkTicket(e)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
checkTicket(e)
}
/>
{!inputDisabled ? null : <LoadingSpinner />}
</Row>
@ -210,7 +226,7 @@ const WalletModal = () => {
name="xpub"
autoCapitalize="none"
autoCorrect="off"
onChange={(e) => checkXPub(e)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => checkXPub(e)}
mr={1}
/>
{!inputDisabled ? null : <LoadingSpinner />}

View File

@ -1,12 +1,12 @@
import React from 'react';
import { Box, Text, Button, Col, Anchor } from '@tlon/indigo-react';
import { api } from '../../api';
import { useSettings } from '../../hooks/useSettings';
import { api } from '../lib/api';
import { useSettings } from '../hooks/useSettings';
const Warning = () => {
const { setWarning } = useSettings();
const { setShowWarning } = useSettings();
const understand = () => {
setWarning(false);
setShowWarning(false);
let removeWarning = {
'put-entry': {
value: false,

View File

@ -1,19 +1,76 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import _ from 'lodash';
import { api } from '../api';
import { api } from '../lib/api';
import { mapDenominationToSymbol, reduceHistory } from '../lib/util';
import {
CurrencyRate,
Denomination,
Network,
Provider,
ProviderPerms,
ScanProgress,
ShipWallets,
Transaction,
TxidType,
} from '../types';
export const SettingsContext = createContext({
type SettingsContextType = {
network: Network;
setNetwork: React.Dispatch<React.SetStateAction<Network>>;
loadedBtc: boolean;
setLoadedBtc: React.Dispatch<React.SetStateAction<boolean>>;
loadedSettings: boolean;
setLoadedSettings: React.Dispatch<React.SetStateAction<boolean>>;
loaded: boolean;
setLoaded: React.Dispatch<React.SetStateAction<boolean>>;
providerPerms: ProviderPerms;
setProviderPerms: React.Dispatch<React.SetStateAction<ProviderPerms>>;
shipWallets: ShipWallets;
setShipWallets: React.Dispatch<React.SetStateAction<ShipWallets>>;
provider: Provider;
setProvider: React.Dispatch<React.SetStateAction<string | null>>;
wallet: string | null;
setWallet: React.Dispatch<React.SetStateAction<string | null>>;
confirmedBalance: number;
setConfirmedBalance: React.Dispatch<React.SetStateAction<number>>;
unconfirmedBalance: number;
setUnconfirmedBalance: React.Dispatch<React.SetStateAction<number>>;
btcState: any;
setBtcState: React.Dispatch<React.SetStateAction<any>>;
history: Transaction[];
setHistory: React.Dispatch<React.SetStateAction<Transaction[]>>;
fee: number;
setFee: React.Dispatch<React.SetStateAction<number>>;
psbt: string;
setPsbt: React.Dispatch<React.SetStateAction<string>>;
address: string | null;
setAddress: React.Dispatch<React.SetStateAction<string | null>>;
currencyRates: CurrencyRate;
setCurrencyRates: React.Dispatch<React.SetStateAction<{}>>;
denomination: Denomination;
setDenomination: React.Dispatch<React.SetStateAction<Denomination>>;
showWarning: boolean;
setShowWarning: React.Dispatch<React.SetStateAction<boolean>>;
error: string;
setError: React.Dispatch<React.SetStateAction<string>>;
broadcastSuccess: boolean;
setBroadcastSuccess: React.Dispatch<React.SetStateAction<boolean>>;
scanProgress: ScanProgress;
setScanProgress: React.Dispatch<React.SetStateAction<ScanProgress>>;
};
export const SettingsContext = createContext<SettingsContextType>({
network: 'bitcoin',
setNetwork: () => {},
loadedBtc: false,
setLoadedBtc: () => {},
loadedSettings: false,
setLoadedSettings: () => {},
loaded: false,
setLoaded: () => {},
providerPerms: {},
providerPerms: { provider: '', permitted: false },
setProviderPerms: () => {},
shipWallets: {},
shipWallets: { payee: '', hasWallet: false },
setShipWallets: () => {},
provider: null,
setProvider: () => {},
@ -36,7 +93,9 @@ export const SettingsContext = createContext({
currencyRates: {
BTC: { last: 1, symbol: 'BTC' },
},
setCurrencyRates: () => {},
denomination: 'BTC',
setDenomination: () => {},
showWarning: true,
setShowWarning: () => {},
error: '',
@ -47,14 +106,24 @@ export const SettingsContext = createContext({
setScanProgress: () => {},
});
export const SettingsProvider = ({ channel, children }) => {
const [network, setNetwork] = useState('bitcoin');
type Props = {
channel: { setOnChannelError: (arg: () => void) => void };
};
export const SettingsProvider: React.FC<Props> = ({ channel, children }) => {
const [network, setNetwork] = useState<Network>('bitcoin');
const [channelData, setChannelData] = useState(null);
const [loadedBtc, setLoadedBtc] = useState(false);
const [loadedSettings, setLoadedSettings] = useState(false);
const [loaded, setLoaded] = useState(false);
const [providerPerms, setProviderPerms] = useState({});
const [shipWallets, setShipWallets] = useState({});
const [providerPerms, setProviderPerms] = useState<ProviderPerms>({
provider: '',
permitted: false,
});
const [shipWallets, setShipWallets] = useState<ShipWallets>({
payee: '',
hasWallet: false,
});
const [provider, setProvider] = useState(null);
const [wallet, setWallet] = useState(null);
const [confirmedBalance, setConfirmedBalance] = useState(0);
@ -67,7 +136,7 @@ export const SettingsProvider = ({ channel, children }) => {
const [currencyRates, setCurrencyRates] = useState({
BTC: { last: 1, symbol: 'BTC' },
});
const [denomination, setDenomination] = useState('BTC');
const [denomination, setDenomination] = useState<Denomination>('BTC');
const [showWarning, setShowWarning] = useState(false);
const [error, setError] = useState('');
const [broadcastSuccess, setBroadcastSuccess] = useState(false);
@ -78,11 +147,11 @@ export const SettingsProvider = ({ channel, children }) => {
const { Provider } = SettingsContext;
const success = (event) => {
const success = (event: any) => {
console.log({ event });
setChannelData(event);
};
const fail = (error) => console.log({ error });
const fail = (error: any) => console.log({ error });
const initializeBtcWallet = () => {
api.bind('/all', 'PUT', api.ship, 'btc-wallet', success, fail);
@ -123,7 +192,7 @@ export const SettingsProvider = ({ channel, children }) => {
fetch('https://blockchain.info/ticker')
.then((res) => res.json())
.then((n) => {
const newCurrencyRates = currencyRates;
const newCurrencyRates: any = currencyRates;
for (let c in n) {
newCurrencyRates[c] = n[c];
newCurrencyRates[c].symbol = mapDenominationToSymbol(c);
@ -141,13 +210,13 @@ export const SettingsProvider = ({ channel, children }) => {
}
};
const handleNewTx = (newTx) => {
const handleNewTx = (newTx: Transaction) => {
const { txid, recvd } = newTx;
let old = _.findIndex(history, (h) => {
let old = _.findIndex(history, (h: Transaction) => {
return h.txid.dat === txid.dat && h.txid.wid === txid.wid;
});
if (old !== -1) {
const newHistory = history.filter((o, i) => i !== old);
const newHistory = history.filter((_, i) => i !== old);
setHistory(newHistory);
}
if (recvd === null && old === -1) {
@ -156,7 +225,7 @@ export const SettingsProvider = ({ channel, children }) => {
} else if (recvd !== null && old === -1) {
// we expect history to have null recvd values first, and the rest in
// descending order
let insertionIndex = _.findIndex(history, (h) => {
let insertionIndex = _.findIndex(history, (h: Transaction) => {
return h.recvd < recvd && h.recvd !== null;
});
const newHistory = history.map((o, i) =>
@ -166,8 +235,8 @@ export const SettingsProvider = ({ channel, children }) => {
}
};
const handleCancelTx = ({ wid, dat }) => {
let entryIndex = _.findIndex(history, (h) => {
const handleCancelTx = ({ wid, dat }: TxidType) => {
let entryIndex = _.findIndex(history, (h: Transaction) => {
return wid === h.txid.wid && dat === h.txid.dat;
});
if (entryIndex > -1) {
@ -221,14 +290,14 @@ export const SettingsProvider = ({ channel, children }) => {
handleNewTx(newTx);
}
if (providerStatus) {
let newProviderPerms = providerPerms;
let newProviderPerms: any = providerPerms;
for (let c in providerStatus) {
newProviderPerms[c] = providerStatus[c];
}
setProviderPerms(newProviderPerms);
}
if (checkPayee) {
let newShipWallets = shipWallets;
let newShipWallets: any = shipWallets;
for (let c in checkPayee) {
newShipWallets[c] = checkPayee[c];

View File

@ -1,17 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './js/components/root.js';
import { api } from './js/api.js';
import { SettingsProvider } from './js/hooks/useSettings';
import { api } from './lib/api';
import { SettingsProvider } from './hooks/useSettings';
import App from './App';
import './css/indigo-static.css';
import './css/fonts.css';
import './css/custom.css';
// rebuild x3
const channel = new window.channel();
api.setChannel(window.ship, channel);
const channel = new (window as any).channel();
api.setChannel((window as any).ship, channel);
if (module.hot) {
module.hot.accept();
@ -19,7 +17,7 @@ if (module.hot) {
ReactDOM.render(
<SettingsProvider channel={channel}>
<Root />
<App />
</SettingsProvider>,
document.querySelectorAll('#root')[0]
);

View File

@ -1,37 +0,0 @@
import React from 'react';
import { Text } from '@tlon/indigo-react';
const errorToString = (error) => {
if (error === 'cant-pay-ourselves') {
return 'Cannot pay ourselves';
}
if (error === 'no-comets') {
return 'Cannot pay comets';
}
if (error === 'no-dust') {
return 'Cannot send dust';
}
if (error === 'tx-being-signed') {
return 'Cannot pay when transaction is being signed';
}
if (error === 'insufficient-balance') {
return 'Insufficient confirmed balance';
}
if (error === 'broadcast-fail') {
return 'Transaction broadcast failed';
}
if (error === 'invalid-master-ticket') {
return 'Invalid master ticket';
}
if (error === 'invalid-signed') {
return 'Invalid signed bitcoin transaction';
}
};
const Error = ({ error, ...rest }) => (
<Text color="red" {...rest}>
{errorToString(error)}
</Text>
);
export default Error;

View File

@ -1,73 +0,0 @@
import React from 'react';
import {
Box,
Text,
Col,
StatelessRadioButtonField as RadioButton,
Label,
} from '@tlon/indigo-react';
import { feeLevels } from './send';
const FeePicker = ({ feeChoices, feeValue, setFeeValue, feeDismiss }) => {
const select = (which) => {
setFeeValue(feeLevels[which]);
feeDismiss();
};
return (
<Box
position="absolute"
p={4}
border="1px solid green"
zIndex={10}
backgroundColor="white"
borderRadius={3}
>
<Text fontSize={1} color="black" fontWeight="bold" mb={4}>
Transaction Speed
</Text>
<Col mt={4}>
<RadioButton
name="feeRadio"
selected={feeValue === feeLevels.low}
p="2"
onChange={() => {
select(feeLevels.low);
}}
>
<Label fontSize="14px">
Slow: {feeChoices.low[1]} sats/vbyte ~{feeChoices.low[0]}m
</Label>
</RadioButton>
<RadioButton
name="feeRadio"
selected={feeValue === feeLevels.mid}
p="2"
onChange={() => {
select(feeLevels.low);
}}
>
<Label fontSize="14px">
Normal: {feeChoices.mid[1]} sats/vbyte ~{feeChoices.mid[0]}m
</Label>
</RadioButton>
<RadioButton
name="feeRadio"
selected={feeValue === feeLevels.high}
p="2"
onChange={() => {
select(feeLevels.high);
}}
>
<Label fontSize="14px">
Fast: {feeChoices.high[1]} sats/vbyte ~{feeChoices.high[0]}m
</Label>
</RadioButton>
</Col>
</Box>
);
};
export default FeePicker;

View File

@ -1,183 +0,0 @@
import baseStyled from "styled-components";
const base = {
white: "rgba(255,255,255,1)",
black: "rgba(0,0,0,1)",
red: "rgba(255,65,54,1)",
yellow: "rgba(255,199,0,1)",
green: "rgba(0,159,101,1)",
blue: "rgba(0,142,255,1)",
};
const scales = {
white05: "rgba(255,255,255,0.05)",
white10: "rgba(255,255,255,0.1)",
white20: "rgba(255,255,255,0.2)",
white30: "rgba(255,255,255,0.3)",
white40: "rgba(255,255,255,0.4)",
white50: "rgba(255,255,255,0.5)",
white60: "rgba(255,255,255,0.6)",
white70: "rgba(255,255,255,0.7)",
white80: "rgba(255,255,255,0.8)",
white90: "rgba(255,255,255,0.9)",
white100: "rgba(255,255,255,1)",
black05: "rgba(0,0,0,0.05)",
black10: "rgba(0,0,0,0.1)",
black20: "rgba(0,0,0,0.2)",
black30: "rgba(0,0,0,0.3)",
black40: "rgba(0,0,0,0.4)",
black50: "rgba(0,0,0,0.5)",
black60: "rgba(0,0,0,0.6)",
black70: "rgba(0,0,0,0.7)",
black80: "rgba(0,0,0,0.8)",
black90: "rgba(0,0,0,0.9)",
black100: "rgba(0,0,0,1)",
red05: "rgba(255,65,54,0.05)",
red10: "rgba(255,65,54,0.1)",
red20: "rgba(255,65,54,0.2)",
red30: "rgba(255,65,54,0.3)",
red40: "rgba(255,65,54,0.4)",
red50: "rgba(255,65,54,0.5)",
red60: "rgba(255,65,54,0.6)",
red70: "rgba(255,65,54,0.7)",
red80: "rgba(255,65,54,0.8)",
red90: "rgba(255,65,54,0.9)",
red100: "rgba(255,65,54,1)",
yellow05: "rgba(255,199,0,0.05)",
yellow10: "rgba(255,199,0,0.1)",
yellow20: "rgba(255,199,0,0.2)",
yellow30: "rgba(255,199,0,0.3)",
yellow40: "rgba(255,199,0,0.4)",
yellow50: "rgba(255,199,0,0.5)",
yellow60: "rgba(255,199,0,0.6)",
yellow70: "rgba(255,199,0,0.7)",
yellow80: "rgba(255,199,0,0.8)",
yellow90: "rgba(255,199,0,0.9)",
yellow100: "rgba(255,199,0,1)",
green05: "rgba(0,159,101,0.05)",
green10: "rgba(0,159,101,0.1)",
green20: "rgba(0,159,101,0.2)",
green30: "rgba(0,159,101,0.3)",
green40: "rgba(0,159,101,0.4)",
green50: "rgba(0,159,101,0.5)",
green60: "rgba(0,159,101,0.6)",
green70: "rgba(0,159,101,0.7)",
green80: "rgba(0,159,101,0.8)",
green90: "rgba(0,159,101,0.9)",
green100: "rgba(0,159,101,1)",
blue05: "rgba(0,142,255,0.05)",
blue10: "rgba(0,142,255,0.1)",
blue20: "rgba(0,142,255,0.2)",
blue30: "rgba(0,142,255,0.3)",
blue40: "rgba(0,142,255,0.4)",
blue50: "rgba(0,142,255,0.5)",
blue60: "rgba(0,142,255,0.6)",
blue70: "rgba(0,142,255,0.7)",
blue80: "rgba(0,142,255,0.8)",
blue90: "rgba(0,142,255,0.9)",
blue100: "rgba(0,142,255,1)",
};
const util = {
cyan: "#00FFFF",
magenta: "#FF00FF",
yellow: "#FFFF00",
black: "#000000",
gray0: "#333333"
};
const theme = {
colors: {
white: util.gray0,
black: base.white,
gray: scales.white60,
lightGray: scales.white30,
washedGray: scales.white05,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red05,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
washedGreen: scales.green10,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: "rgba(0,0,0,0)",
scales: scales,
util: util,
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ["none", "1px solid"],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ["550px", "750px", "960px"],
};
export const styled = baseStyled;
export default theme;

View File

@ -1,184 +0,0 @@
import baseStyled from "styled-components";
const base = {
white: "rgba(255,255,255,1)",
black: "rgba(0,0,0,1)",
red: "rgba(255,65,54,1)",
yellow: "rgba(255,199,0,1)",
green: "rgba(0,159,101,1)",
blue: "rgba(0,142,255,1)",
none: "rgba(0,0,0,0)",
};
const scales = {
white10: "rgba(255,255,255,0.1)",
white20: "rgba(255,255,255,0.2)",
white30: "rgba(255,255,255,0.3)",
white40: "rgba(255,255,255,0.4)",
white50: "rgba(255,255,255,0.5)",
white60: "rgba(255,255,255,0.6)",
white70: "rgba(255,255,255,0.7)",
white80: "rgba(255,255,255,0.8)",
white90: "rgba(255,255,255,0.9)",
white100: "rgba(255,255,255,1)",
black04: "rgba(0,0,0,0.04)",
black10: "rgba(0,0,0,0.1)",
black20: "rgba(0,0,0,0.2)",
black30: "rgba(0,0,0,0.3)",
black40: "rgba(0,0,0,0.4)",
black50: "rgba(0,0,0,0.5)",
black60: "rgba(0,0,0,0.6)",
black70: "rgba(0,0,0,0.7)",
black80: "rgba(0,0,0,0.8)",
black90: "rgba(0,0,0,0.9)",
black100: "rgba(0,0,0,1)",
red05: "rgba(255,65,54,0.05)",
red10: "rgba(255,65,54,0.1)",
red20: "rgba(255,65,54,0.2)",
red30: "rgba(255,65,54,0.3)",
red40: "rgba(255,65,54,0.4)",
red50: "rgba(255,65,54,0.5)",
red60: "rgba(255,65,54,0.6)",
red70: "rgba(255,65,54,0.7)",
red80: "rgba(255,65,54,0.8)",
red90: "rgba(255,65,54,0.9)",
red100: "rgba(255,65,54,1)",
yellow10: "rgba(255,199,0,0.1)",
yellow20: "rgba(255,199,0,0.2)",
yellow30: "rgba(255,199,0,0.3)",
yellow40: "rgba(255,199,0,0.4)",
yellow50: "rgba(255,199,0,0.5)",
yellow60: "rgba(255,199,0,0.6)",
yellow70: "rgba(255,199,0,0.7)",
yellow80: "rgba(255,199,0,0.8)",
yellow90: "rgba(255,199,0,0.9)",
yellow100: "rgba(255,199,0,1)",
green05: "rgba(0,159,101,0.05)",
green10: "rgba(0,159,101,0.1)",
green20: "rgba(0,159,101,0.2)",
green30: "rgba(0,159,101,0.3)",
green40: "rgba(0,159,101,0.4)",
green50: "rgba(0,159,101,0.5)",
green60: "rgba(0,159,101,0.6)",
green70: "rgba(0,159,101,0.7)",
green80: "rgba(0,159,101,0.8)",
green90: "rgba(0,159,101,0.9)",
green100: "rgba(0,159,101,1)",
blue10: "rgba(0,142,255,0.1)",
blue20: "rgba(0,142,255,0.2)",
blue30: "rgba(0,142,255,0.3)",
blue40: "rgba(0,142,255,0.4)",
blue50: "rgba(0,142,255,0.5)",
blue60: "rgba(0,142,255,0.6)",
blue70: "rgba(0,142,255,0.7)",
blue80: "rgba(0,142,255,0.8)",
blue90: "rgba(0,142,255,0.9)",
blue100: "rgba(0,142,255,1)",
};
const theme = {
colors: {
white: base.white,
black: base.black,
gray: scales.black60,
lighterGray: scales.black20,
lightGray: scales.black30,
washedGray: scales.black10,
veryLightGray: scales.black04,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red10,
veryLightRed: scales.red05,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
midGreen: scales.green60,
washedGreen: scales.green10,
veryLightGreen: scales.green05,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: "rgba(0,0,0,0)",
scales: scales,
orange: "rgba(255, 153, 0, 1)",
midOrange: "rgba(255, 153, 0, 0.2)",
lightOrange: "rgba(255, 153, 0, 0.08)",
sentBlue: "rgba(33,157,255,1)",
recvGreen: "rgba(0,159,101,1)",
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ["none", "1px solid"],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
16, // 4
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ["550px", "750px", "960px"],
};
export const styled = baseStyled;
export default theme;

View File

@ -1,55 +0,0 @@
import { api } from './api';
import { store } from './store';
export class Subscription {
start() {
if (api.ship) {
this.initializeBtcWallet();
this.initializeSettings();
this.initializeCurrencyPoll();
} else {
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
}
}
initializeBtcWallet() {
api.bind('/all', 'PUT', api.ship, 'btc-wallet',
this.handleEvent.bind(this),
this.handleError.bind(this));
}
initializeSettings() {
let app = 'settings-store';
let path = '/bucket/btc-wallet';
fetch(`/~/scry/${app}${path}.json`).then(res => res.json())
.then(n => {
this.handleEvent({data: n});
});
api.bind(path, 'PUT', api.ship, app,
this.handleEvent.bind(this),
this.handleError.bind(this));
}
initializeCurrencyPoll() {
fetch('https://blockchain.info/ticker')
.then(res => res.json())
.then(n => {
store.handleEvent({data: {currencyRates: n}})
setTimeout(this.initializeCurrencyPoll, 1000 * 60 * 15);
});
}
handleEvent(diff) {
store.handleEvent(diff);
}
handleError(err) {
console.error(err);
this.initializeBtcWallet();
this.initializeSettings();
}
}
export let subscription = new Subscription();

View File

@ -1,16 +1,44 @@
import _ from 'lodash';
type Channel = {
poke: (
ship: string,
appl: string,
mark: string,
data: any,
postDataHandler: (json: any) => void,
errorHandler: (err: string) => void
) => void;
subscribe: (
ship: string,
appl: string,
path: string,
errorHandler: (err: string) => void,
eventHandler: (event: any) => void
) => void;
};
class UrbitApi {
setChannel(ship, channel) {
ship: string;
channel: Channel;
bindPaths: string[];
setChannel(ship: string, channel: Channel) {
this.ship = ship;
this.channel = channel;
this.bindPaths = [];
}
bind(path, method, ship = this.ship, appl = 'btc-wallet', success, fail) {
bind(
path: string,
method: string,
ship = this.ship,
appl = 'btc-wallet',
success: any,
fail: any
) {
this.bindPaths = _.uniq([...this.bindPaths, path]);
window.subscriptionId = this.channel.subscribe(
(window as any).subscriptionId = this.channel.subscribe(
ship,
appl,
path,
@ -25,22 +53,19 @@ class UrbitApi {
path,
},
});
},
(err) => {
fail(err);
}
);
}
btcWalletCommand(data) {
btcWalletCommand(data: any) {
return this.action('btc-wallet', 'btc-wallet-command', data);
}
settingsEvent(data) {
settingsEvent(data: any) {
return this.action('settings-store', 'settings-event', data);
}
action(appl, mark, data) {
action(appl: string, mark: string, data: string) {
return new Promise((resolve, reject) => {
this.channel.poke(
this.ship,
@ -58,4 +83,4 @@ class UrbitApi {
}
}
export let api = new UrbitApi();
window.api = api;
(window as any).api = api;

View File

@ -1,3 +1,5 @@
import { CurrencyRate, Denomination, Transaction } from '../types';
export function uuid() {
let str = '0v';
str += Math.ceil(Math.random() * 8) + '.';
@ -10,7 +12,7 @@ export function uuid() {
return str.slice(0, -1);
}
export function isPatTa(str) {
export function isPatTa(str: string) {
const r = /^[a-z,0-9,\-,.,_,~]+$/.exec(str);
return !!r;
}
@ -21,8 +23,8 @@ export function isPatTa(str) {
To:
(javascript Date object)
*/
export function daToDate(st) {
var dub = function (n) {
export function daToDate(st: string) {
var dub = function (n: string) {
return parseInt(n) < 10 ? '0' + parseInt(n) : n.toString();
};
var da = st.split('..');
@ -41,8 +43,8 @@ export function daToDate(st) {
~2018.7.17..23.15.09..5be5 // urbit @da
*/
export function dateToDa(d, mil) {
var fil = function (n) {
export function dateToDa(d: Date, mil: boolean) {
var fil = function (n: number) {
return n >= 10 ? n : '0' + n;
};
return (
@ -56,12 +58,12 @@ export function dateToDa(d, mil) {
);
}
export function deSig(ship) {
export function deSig(ship: string) {
return ship.replace('~', '');
}
// trim patps to match dojo, chat-cli
export function cite(ship) {
export function cite(ship: string) {
let patp = ship,
shortened = '';
if (patp.startsWith('~')) {
@ -80,7 +82,11 @@ export function cite(ship) {
return `~${patp}`;
}
export function satsToCurrency(sats, denomination, rates) {
export function satsToCurrency(
sats: number,
denomination: Denomination,
rates: CurrencyRate
) {
if (!rates) {
throw 'nonexistent currency table';
}
@ -99,27 +105,7 @@ export function satsToCurrency(sats, denomination, rates) {
return text;
}
export function currencyToSats(val, denomination, rates) {
if (!rates) {
throw 'nonexistent currency table';
}
if (!rates[denomination]) {
throw 'currency not in table';
}
let rate = rates[denomination];
let sats = (parseFloat(val) / rate.last) * 100000000;
return sats;
}
export function reduceHistory(history) {
return Object.values(history).sort((hest1, hest2) => {
if (hest1.recvd === null) return -1;
if (hest2.recvd === null) return +1;
return hest2.recvd - hest1.recvd;
});
}
export function mapDenominationToSymbol(denomination) {
export function mapDenominationToSymbol(denomination: string) {
switch (denomination) {
case 'USD':
return '$';
@ -128,7 +114,15 @@ export function mapDenominationToSymbol(denomination) {
}
}
export function copyToClipboard(textToCopy) {
export function reduceHistory(history: Transaction[]) {
return Object.values(history).sort((hest1, hest2) => {
if (hest1.recvd === null) return -1;
if (hest2.recvd === null) return +1;
return hest2.recvd - hest1.recvd;
});
}
export function copyToClipboard(textToCopy: string) {
// navigator clipboard api needs a secure context (https or localhost)
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(textToCopy);
@ -141,7 +135,7 @@ export function copyToClipboard(textToCopy) {
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
return new Promise((res, rej) => {
return new Promise<void>((res, rej) => {
document.execCommand('copy') ? res() : rej();
textArea.remove();
});

View File

@ -0,0 +1,183 @@
import baseStyled from 'styled-components';
const base = {
white: 'rgba(255,255,255,1)',
black: 'rgba(0,0,0,1)',
red: 'rgba(255,65,54,1)',
yellow: 'rgba(255,199,0,1)',
green: 'rgba(0,159,101,1)',
blue: 'rgba(0,142,255,1)',
};
const scales = {
white05: 'rgba(255,255,255,0.05)',
white10: 'rgba(255,255,255,0.1)',
white20: 'rgba(255,255,255,0.2)',
white30: 'rgba(255,255,255,0.3)',
white40: 'rgba(255,255,255,0.4)',
white50: 'rgba(255,255,255,0.5)',
white60: 'rgba(255,255,255,0.6)',
white70: 'rgba(255,255,255,0.7)',
white80: 'rgba(255,255,255,0.8)',
white90: 'rgba(255,255,255,0.9)',
white100: 'rgba(255,255,255,1)',
black05: 'rgba(0,0,0,0.05)',
black10: 'rgba(0,0,0,0.1)',
black20: 'rgba(0,0,0,0.2)',
black30: 'rgba(0,0,0,0.3)',
black40: 'rgba(0,0,0,0.4)',
black50: 'rgba(0,0,0,0.5)',
black60: 'rgba(0,0,0,0.6)',
black70: 'rgba(0,0,0,0.7)',
black80: 'rgba(0,0,0,0.8)',
black90: 'rgba(0,0,0,0.9)',
black100: 'rgba(0,0,0,1)',
red05: 'rgba(255,65,54,0.05)',
red10: 'rgba(255,65,54,0.1)',
red20: 'rgba(255,65,54,0.2)',
red30: 'rgba(255,65,54,0.3)',
red40: 'rgba(255,65,54,0.4)',
red50: 'rgba(255,65,54,0.5)',
red60: 'rgba(255,65,54,0.6)',
red70: 'rgba(255,65,54,0.7)',
red80: 'rgba(255,65,54,0.8)',
red90: 'rgba(255,65,54,0.9)',
red100: 'rgba(255,65,54,1)',
yellow05: 'rgba(255,199,0,0.05)',
yellow10: 'rgba(255,199,0,0.1)',
yellow20: 'rgba(255,199,0,0.2)',
yellow30: 'rgba(255,199,0,0.3)',
yellow40: 'rgba(255,199,0,0.4)',
yellow50: 'rgba(255,199,0,0.5)',
yellow60: 'rgba(255,199,0,0.6)',
yellow70: 'rgba(255,199,0,0.7)',
yellow80: 'rgba(255,199,0,0.8)',
yellow90: 'rgba(255,199,0,0.9)',
yellow100: 'rgba(255,199,0,1)',
green05: 'rgba(0,159,101,0.05)',
green10: 'rgba(0,159,101,0.1)',
green20: 'rgba(0,159,101,0.2)',
green30: 'rgba(0,159,101,0.3)',
green40: 'rgba(0,159,101,0.4)',
green50: 'rgba(0,159,101,0.5)',
green60: 'rgba(0,159,101,0.6)',
green70: 'rgba(0,159,101,0.7)',
green80: 'rgba(0,159,101,0.8)',
green90: 'rgba(0,159,101,0.9)',
green100: 'rgba(0,159,101,1)',
blue05: 'rgba(0,142,255,0.05)',
blue10: 'rgba(0,142,255,0.1)',
blue20: 'rgba(0,142,255,0.2)',
blue30: 'rgba(0,142,255,0.3)',
blue40: 'rgba(0,142,255,0.4)',
blue50: 'rgba(0,142,255,0.5)',
blue60: 'rgba(0,142,255,0.6)',
blue70: 'rgba(0,142,255,0.7)',
blue80: 'rgba(0,142,255,0.8)',
blue90: 'rgba(0,142,255,0.9)',
blue100: 'rgba(0,142,255,1)',
};
const util = {
cyan: '#00FFFF',
magenta: '#FF00FF',
yellow: '#FFFF00',
black: '#000000',
gray0: '#333333',
};
const theme = {
colors: {
white: util.gray0,
black: base.white,
gray: scales.white60,
lightGray: scales.white30,
washedGray: scales.white05,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red05,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
washedGreen: scales.green10,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: 'rgba(0,0,0,0)',
scales: scales,
util: util,
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ['none', '1px solid'],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ['550px', '750px', '960px'],
};
export const styled = baseStyled;
export default theme;

View File

@ -0,0 +1,184 @@
import baseStyled from 'styled-components';
const base = {
white: 'rgba(255,255,255,1)',
black: 'rgba(0,0,0,1)',
red: 'rgba(255,65,54,1)',
yellow: 'rgba(255,199,0,1)',
green: 'rgba(0,159,101,1)',
blue: 'rgba(0,142,255,1)',
none: 'rgba(0,0,0,0)',
};
const scales = {
white10: 'rgba(255,255,255,0.1)',
white20: 'rgba(255,255,255,0.2)',
white30: 'rgba(255,255,255,0.3)',
white40: 'rgba(255,255,255,0.4)',
white50: 'rgba(255,255,255,0.5)',
white60: 'rgba(255,255,255,0.6)',
white70: 'rgba(255,255,255,0.7)',
white80: 'rgba(255,255,255,0.8)',
white90: 'rgba(255,255,255,0.9)',
white100: 'rgba(255,255,255,1)',
black04: 'rgba(0,0,0,0.04)',
black10: 'rgba(0,0,0,0.1)',
black20: 'rgba(0,0,0,0.2)',
black30: 'rgba(0,0,0,0.3)',
black40: 'rgba(0,0,0,0.4)',
black50: 'rgba(0,0,0,0.5)',
black60: 'rgba(0,0,0,0.6)',
black70: 'rgba(0,0,0,0.7)',
black80: 'rgba(0,0,0,0.8)',
black90: 'rgba(0,0,0,0.9)',
black100: 'rgba(0,0,0,1)',
red05: 'rgba(255,65,54,0.05)',
red10: 'rgba(255,65,54,0.1)',
red20: 'rgba(255,65,54,0.2)',
red30: 'rgba(255,65,54,0.3)',
red40: 'rgba(255,65,54,0.4)',
red50: 'rgba(255,65,54,0.5)',
red60: 'rgba(255,65,54,0.6)',
red70: 'rgba(255,65,54,0.7)',
red80: 'rgba(255,65,54,0.8)',
red90: 'rgba(255,65,54,0.9)',
red100: 'rgba(255,65,54,1)',
yellow10: 'rgba(255,199,0,0.1)',
yellow20: 'rgba(255,199,0,0.2)',
yellow30: 'rgba(255,199,0,0.3)',
yellow40: 'rgba(255,199,0,0.4)',
yellow50: 'rgba(255,199,0,0.5)',
yellow60: 'rgba(255,199,0,0.6)',
yellow70: 'rgba(255,199,0,0.7)',
yellow80: 'rgba(255,199,0,0.8)',
yellow90: 'rgba(255,199,0,0.9)',
yellow100: 'rgba(255,199,0,1)',
green05: 'rgba(0,159,101,0.05)',
green10: 'rgba(0,159,101,0.1)',
green20: 'rgba(0,159,101,0.2)',
green30: 'rgba(0,159,101,0.3)',
green40: 'rgba(0,159,101,0.4)',
green50: 'rgba(0,159,101,0.5)',
green60: 'rgba(0,159,101,0.6)',
green70: 'rgba(0,159,101,0.7)',
green80: 'rgba(0,159,101,0.8)',
green90: 'rgba(0,159,101,0.9)',
green100: 'rgba(0,159,101,1)',
blue10: 'rgba(0,142,255,0.1)',
blue20: 'rgba(0,142,255,0.2)',
blue30: 'rgba(0,142,255,0.3)',
blue40: 'rgba(0,142,255,0.4)',
blue50: 'rgba(0,142,255,0.5)',
blue60: 'rgba(0,142,255,0.6)',
blue70: 'rgba(0,142,255,0.7)',
blue80: 'rgba(0,142,255,0.8)',
blue90: 'rgba(0,142,255,0.9)',
blue100: 'rgba(0,142,255,1)',
};
const theme = {
colors: {
white: base.white,
black: base.black,
gray: scales.black60,
lighterGray: scales.black20,
lightGray: scales.black30,
washedGray: scales.black10,
veryLightGray: scales.black04,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red10,
veryLightRed: scales.red05,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
midGreen: scales.green60,
washedGreen: scales.green10,
veryLightGreen: scales.green05,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: 'rgba(0,0,0,0)',
scales: scales,
orange: 'rgba(255, 153, 0, 1)',
midOrange: 'rgba(255, 153, 0, 0.2)',
lightOrange: 'rgba(255, 153, 0, 0.08)',
sentBlue: 'rgba(33,157,255,1)',
recvGreen: 'rgba(0,159,101,1)',
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ['none', '1px solid'],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
16, // 4
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ['550px', '750px', '960px'],
};
export const styled = baseStyled;
export default theme;

View File

@ -0,0 +1,45 @@
export type ProviderPerms = {
provider: string;
permitted: boolean;
};
export type ShipWallets = {
payee: string;
hasWallet: boolean;
};
export type Transaction = {
txid: TxidType;
recvd: number;
outputs: [{ ship: string; val: { value: number } }];
inputs: [{ ship: string }];
failure: string;
};
export type TxidType = {
dat: string;
wid: string;
};
export type ScanProgress = {
main: null | number;
change: null | number;
};
export type Network = 'bitcoin' | 'testnet';
export type Denomination = 'BTC' | 'USD';
export type UrbitWallet = {
bitcoinTestnet: { keys: { xpub: string; xprv: string } };
bitcoinMainnet: { keys: { xpub: string; xprv: string } };
};
export type CurrencyRate = {
[Denomination: string]: { last: number; symbol: string };
};
export type Provider = {
host: string;
connected: boolean;
};

View File

@ -0,0 +1 @@
declare module 'urbit-key-generation';

1
pkg/btc-wallet/src/urbit-ob.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module 'urbit-ob';

View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"lib": ["ESNext", "DOM"]
}
}