Skip to content
This repository has been archived by the owner on Apr 7, 2020. It is now read-only.

Commit

Permalink
Publishing client to sparkswap-desktop: v0.3.7
Browse files Browse the repository at this point in the history
  • Loading branch information
Sparkswap committed Jan 13, 2020
1 parent 4965494 commit 737abb3
Show file tree
Hide file tree
Showing 39 changed files with 1,542 additions and 228 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"author": "Sparkswap <dev@sparkswap.com> (https://github.com/sparkswap)",
"description": "Sparkswap Desktop: the only way to buy Bitcoin instantly",
"productName": "Sparkswap",
"version": "0.3.6",
"version": "0.3.7",
"license": "MIT",
"private": true,
"main": "./build/electron.js",
Expand Down
19 changes: 19 additions & 0 deletions src/common/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Asset } from '../global-shared/types'

export class BalanceError extends Error {
asset: Asset

constructor (message: string, asset: Asset) {
super(message)
this.asset = asset
}
}

export class QuantityError extends Error {
asset: Asset

constructor (message: string, asset: Asset) {
super(message)
this.asset = asset
}
}
53 changes: 53 additions & 0 deletions src/common/serializers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
RecurringBuy,
WireRecurringBuy,
Trade,
TradeStatus,
UnsavedRecurringBuy,
WireUnsavedRecurringBuy
} from './types'
import { Amount, UnknownObject } from '../global-shared/types'

export function deserializeTradeFromWire (wireTrade: UnknownObject): Trade {
return {
id: wireTrade.id as number,
status: wireTrade.status as TradeStatus,
hash: wireTrade.hash as string,
destinationAmount: wireTrade.destinationAmount as Amount,
sourceAmount: wireTrade.sourceAmount as Amount,
startTime: new Date(wireTrade.startTime as string),
endTime: wireTrade.endTime ? new Date(wireTrade.endTime as string) : undefined
}
}

export function serializeUnsavedRecurringBuyToWire (recurringBuy: UnsavedRecurringBuy): WireUnsavedRecurringBuy {
return {
amount: recurringBuy.amount,
frequency: recurringBuy.frequency,
referenceTime: recurringBuy.referenceTime.toISOString()
}
}

export function deserializeUnsavedRecurringBuyFromWire (recurringBuy: WireUnsavedRecurringBuy): UnsavedRecurringBuy {
return {
amount: recurringBuy.amount,
frequency: recurringBuy.frequency,
referenceTime: new Date(recurringBuy.referenceTime)
}
}

export function deserializeRecurringBuyFromWire (wireRecurringBuy: WireRecurringBuy): RecurringBuy {
const partialRecurringBuy = deserializeUnsavedRecurringBuyFromWire(wireRecurringBuy)
return {
id: wireRecurringBuy.id,
...partialRecurringBuy
}
}

export function serializeRecurringBuyToWire (recurringBuy: RecurringBuy): WireRecurringBuy {
const partialWireRecurringBuy = serializeUnsavedRecurringBuyToWire(recurringBuy)
return {
id: recurringBuy.id,
...partialWireRecurringBuy
}
}
47 changes: 47 additions & 0 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,50 @@ export interface Trade extends Quote {
failureCode?: TradeFailureReason,
status: TradeStatus
}

export enum TimeUnit {
MINUTES = 'MINUTES',
HOURS = 'HOURS',
DAYS = 'DAYS',
WEEKS = 'WEEKS',
MONTHS = 'MONTHS'
}

const timeUnitEntries = Object.entries(TimeUnit)

export function valueToTimeUnit (str: string): TimeUnit {
for (let i = 0; i < timeUnitEntries.length; i++) {
if (timeUnitEntries[i][1] === str) {
return TimeUnit[timeUnitEntries[i][0] as keyof typeof TimeUnit]
}
}
throw new Error(`${str} is not a valid value for TimeUnit`)
}

export interface Frequency {
interval: number,
unit: TimeUnit
}

export interface RecurringBuy {
id: number,
amount: Amount,
frequency: Frequency,
referenceTime: Date
}

export interface WireRecurringBuy {
id: number,
amount: Amount,
frequency: Frequency,
referenceTime: string
}

export type UnsavedRecurringBuy = Omit<RecurringBuy, 'id'>

export type WireUnsavedRecurringBuy = Omit<WireRecurringBuy, 'id'>

export interface AlertEvent {
title: string,
message: string
}
6 changes: 5 additions & 1 deletion src/common/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import serverRequest from './server-request'
import { getNextTimeoutDuration, isStartOfInterval, getCronDate } from './time'

export {
serverRequest
serverRequest,
getNextTimeoutDuration,
isStartOfInterval,
getCronDate
}
92 changes: 92 additions & 0 deletions src/common/utils/time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Frequency, TimeUnit } from '../types'

const MILLISECONDS_PER_MINUTE = 60 * 1000
const MILLISECONDS_PER_HOUR = MILLISECONDS_PER_MINUTE * 60
const MILLISECONDS_PER_DAY = MILLISECONDS_PER_HOUR * 24
const MILLISECONDS_PER_WEEK = MILLISECONDS_PER_DAY * 7

const EPOCH_DATE = new Date(Date.UTC(1970, 0, 1))

function getMinutesSince (start: Date, since = EPOCH_DATE): number {
return Math.floor((start.getTime() - since.getTime()) / MILLISECONDS_PER_MINUTE)
}

function getHoursSince (start: Date, since = EPOCH_DATE): number {
return Math.floor((start.getTime() - since.getTime()) / MILLISECONDS_PER_HOUR)
}

function getDaysSince (start: Date, since = EPOCH_DATE): number {
return Math.floor((start.getTime() - since.getTime()) / MILLISECONDS_PER_DAY)
}

function getWeeksSince (start: Date, since = EPOCH_DATE): number {
return Math.floor((start.getTime() - since.getTime()) / MILLISECONDS_PER_WEEK)
}

function getMonthsSince (start: Date, since = EPOCH_DATE): number {
const yearsSince = start.getUTCFullYear() - since.getUTCFullYear()
return yearsSince * 12 + start.getUTCMonth() - since.getUTCMonth()
}

function getNextMinuteMs (start: Date, minutes: number, reference = EPOCH_DATE): number {
return (getMinutesSince(start, reference) + minutes) * MILLISECONDS_PER_MINUTE
}

function getNextHourMs (start: Date, hours: number, reference = EPOCH_DATE): number {
return (getHoursSince(start, reference) + hours) * MILLISECONDS_PER_HOUR
}

function getNextDayMs (start: Date, days: number, reference = EPOCH_DATE): number {
return (getDaysSince(start, reference) + days) * MILLISECONDS_PER_DAY
}

function getNextWeekMs (start: Date, weeks: number, reference = EPOCH_DATE): number {
return (getWeeksSince(start, reference) + weeks) * MILLISECONDS_PER_WEEK
}

function getNextMonthMs (start: Date, months: number, reference = EPOCH_DATE): number {
const sinceMut = new Date(reference.getTime())
return sinceMut.setUTCMonth(getMonthsSince(start, reference) + months) - reference.getTime()
}

const getNextMsFns = {
[TimeUnit.MINUTES]: getNextMinuteMs,
[TimeUnit.HOURS]: getNextHourMs,
[TimeUnit.DAYS]: getNextDayMs,
[TimeUnit.WEEKS]: getNextWeekMs,
[TimeUnit.MONTHS]: getNextMonthMs
}

export function getNextTimeoutDuration (frequency: Frequency, start = new Date(), reference = EPOCH_DATE): number {
const msFromSinceToNext = getNextMsFns[frequency.unit](start, frequency.interval, reference)
return msFromSinceToNext + reference.getTime() - start.getTime()
}

export function getCronDate (date: Date): Date {
const cronDate = new Date(date.getTime())
cronDate.setUTCSeconds(0, 0)
return cronDate
}

export function isStartOfInterval ({ unit, interval }: Frequency, since = EPOCH_DATE): boolean {
const now = getCronDate(new Date())

if (now.getUTCMinutes() !== since.getUTCMinutes()) {
return false
}

switch (unit) {
case TimeUnit.HOURS:
return getHoursSince(now, since) % interval === 0
case TimeUnit.DAYS:
return getDaysSince(now, since) % interval === 0 && now.getUTCHours() === since.getUTCHours()
case TimeUnit.WEEKS:
return getWeeksSince(now, since) % interval === 0 && now.getUTCDay() === since.getUTCDay() &&
now.getUTCHours() === since.getUTCHours()
case TimeUnit.MONTHS:
return getMonthsSince(now, since) % interval === 0 && now.getUTCDate() === since.getUTCDate() &&
now.getUTCHours() === since.getUTCHours()
default:
throw new Error(`Unrecognized time unit ${unit}`)
}
}
3 changes: 2 additions & 1 deletion src/global-shared/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export const API_ENDPOINTS: { [key: string]: string } = {
START_BERBIX: '/start-berbix',
FINISH_BERBIX: '/finish-berbix',
SUBMIT_PHOTO_ID: '/submit-photo-id',
GET_PROOF: '/proof-of-keys'
GET_PROOF: '/proof-of-keys',
HEALTHCHECK: '/healthcheck'
}
54 changes: 52 additions & 2 deletions src/global-shared/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { createHash } from 'crypto'
import { SwapHash, SwapPreimage } from './types'

export function requireEnv (name: string, defaultVal?: string): string | never {
const value = process.env[name]
if (value !== undefined) {
return value
}
if (defaultVal !== undefined) {
return defaultVal
}
throw new Error(`Error: must set ${name} environment variable`)
}

export function fail (error: Error): void {
console.error(error)
process.exit(1)
Expand All @@ -22,6 +33,45 @@ export function generateHash (preimage: SwapPreimage): SwapHash {
return sha256.update(preimageBuf).digest('base64')
}

export function sum (arr: number[]): number {
return arr.reduce((acc, num) => acc + num, 0)
export function sum (nums: Iterable<number>): number {
let total = 0
for (const num of nums) {
total += num
}
return total
}

export function getNthFromMap<K, T> (map: Map<K, T>, n: number): [K, T] {
return Array.from(map.entries())[n]
}

type StringKeyOf<T> = Extract<keyof T, string>

// like `Object.keys()`, but the type of the response is the union of the
// enum's keys, rather than just `string`. This allows it to be used again
// as an enum key without casting, e.g.:
// ```
// enum Example {
// testKey = 'TEST'
// }
// const keys = Object.keys(Example)
// console.log(Example[keys[0]]) // prints `TEST` without a typeerror
// ```
export function enumStringKeys<T extends Record<string, unknown>> (obj: T):
Array<StringKeyOf<T>> {
const propertyIsEnumerable = Object.prototype.propertyIsEnumerable
return Object.getOwnPropertyNames(obj).filter((key) => {
return propertyIsEnumerable.call(obj, key) && typeof key === 'string'
}) as Array<StringKeyOf<T>>
}

// Typeguard for strings as keys of a provided enum
export function isEnumKey<T extends Record<string, unknown>> (obj: T,
str: string): str is StringKeyOf<T> {
const keys = enumStringKeys(obj) as string[]
if (keys.includes(str)) {
return true
}

return false
}
12 changes: 11 additions & 1 deletion src/node/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import {
markProofOfKeysShown,
hasShownProofOfKeys
} from './events'
import {
addRecurringBuy,
getRecurringBuys,
removeRecurringBuy,
updater as recurringBuyUpdater
} from './recurringBuys'

export {
initialize,
Expand All @@ -24,5 +30,9 @@ export {
failTrade,
tradeUpdater,
markProofOfKeysShown,
hasShownProofOfKeys
hasShownProofOfKeys,
addRecurringBuy,
getRecurringBuys,
removeRecurringBuy,
recurringBuyUpdater
}
20 changes: 20 additions & 0 deletions src/node/data/migrations/0003-create-recurring-buys-table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
CREATE TABLE timeUnit (
unit VARCHAR(5) PRIMARY KEY NOT NULL
);

INSERT INTO timeUnit(unit) VALUES('MINUTES');
INSERT INTO timeUnit(unit) VALUES('HOURS');
INSERT INTO timeUnit(unit) VALUES('DAYS');
INSERT INTO timeUnit(unit) VALUES('WEEKS');
INSERT INTO timeUnit(unit) VALUES('MONTHS');

CREATE TABLE recurringBuys (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
amountAsset VARCHAR(5) NOT NULL REFERENCES assets(symbol),
amountUnit VARCHAR(20) NOT NULL REFERENCES units(unit),
amountValue INTEGER NOT NULL,
duration INTEGER NOT NULL,
timeUnit VARCHAR(5) NOT NULL REFERENCES timeUnit(unit),
referenceTime DATETIME NOT NULL
);

3 changes: 2 additions & 1 deletion src/node/data/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { readdirSync } from 'fs'

const migrationFilePaths = [
pathJoin(__dirname, '0001-create-trades-table.sql'),
pathJoin(__dirname, '0002-create-events-table.sql')
pathJoin(__dirname, '0002-create-events-table.sql'),
pathJoin(__dirname, '0003-create-recurring-buys-table.sql')
]

const version = migrationFilePaths.length
Expand Down
Loading

0 comments on commit 737abb3

Please sign in to comment.