diff --git a/config/mocks/wallet-options-v4.json b/config/mocks/wallet-options-v4.json index cd2adb3af85..899c64add52 100644 --- a/config/mocks/wallet-options-v4.json +++ b/config/mocks/wallet-options-v4.json @@ -139,6 +139,21 @@ "txExplorerBaseUrl": "https://www.blockchain.com/eth/tx", "txListAppRoute": "/pax/transactions" }, + "STX": { + "campaign": "BLOCKSTACK", + "coinCode": "STX", + "coinTicker": "STX", + "displayName": "Stacks", + "availability": { + "send": false, + "request": false, + "lockbox": false, + "exchangeTo": false, + "exchangeFrom": false, + "syncToPit": false + }, + "hasLockboxSupport": false + }, "XLM": { "airdrop": { "link": "https://support.blockchain.com/hc/en-us/categories/360001126692-Crypto-Giveaway", diff --git a/packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.js b/packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.js index 276bdb6752c..43207679fe1 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.js @@ -83,6 +83,7 @@ export default ({ api, coreSagas }) => { const saveGoals = function * (firstLogin) { yield put(actions.goals.saveGoal('walletTour', { firstLogin })) + yield put(actions.goals.saveGoal('registerForBlockstackAirdrop')) yield put(actions.goals.saveGoal('coinifyUpgrade')) yield put(actions.goals.saveGoal('coinifyBuyViaCard')) yield put(actions.goals.saveGoal('upgradeForAirdrop')) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/goals/sagas.js b/packages/blockchain-wallet-v4-frontend/src/data/goals/sagas.js index d2d6dfce282..d3791053708 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/goals/sagas.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/goals/sagas.js @@ -424,6 +424,28 @@ export default ({ api }) => { } } + const runRegisterForBlockstackAirdropGoal = function * (goal) { + const { id } = goal + yield put(actions.goals.deleteGoal(id)) + yield call(waitForUserData) + // const { current } = (yield select( + // selectors.modules.profile.getUserTiers + // )).getOrElse({ current: 0 }) || { current: 0 } + const blockstackTag = (yield select( + selectors.modules.profile.getBlockstackTag + )).getOrElse(false) + // TODO: check current === TIERS[2] + if (!blockstackTag) { + const password = null + yield put(actions.core.data.stx.generateAddress(password)) + const { payload } = yield take(actions.core.data.stx.setAddress) + const { address } = payload + yield call(api.registerUserCampaign, 'BLOCKSTACK', { + 'x-campaign-address': address + }) + } + } + const runWalletTour = function * (goal) { const { id, data } = goal yield put(actions.goals.deleteGoal(id)) @@ -475,6 +497,7 @@ export default ({ api }) => { upgradeForAirdrop, walletTour } = initialModals + // Order matters here if (linkAccount) { return yield put( actions.modals.showModal(linkAccount.name, linkAccount.data) @@ -527,6 +550,7 @@ export default ({ api }) => { const runGoal = function * (goal) { try { + // Ordering doesn't matter here switch (goal.name) { case 'linkAccount': yield call(runLinkAccountGoal, goal) @@ -567,6 +591,8 @@ export default ({ api }) => { case 'walletTour': yield call(runWalletTour, goal) break + case 'registerForBlockstackAirdrop': + yield call(runRegisterForBlockstackAirdropGoal, goal) } } catch (error) { yield put(actions.logs.logErrorMessage(logLocation, 'runGoal', error)) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/modules/profile/selectors.js b/packages/blockchain-wallet-v4-frontend/src/data/modules/profile/selectors.js index 75fc1552158..14747885302 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/modules/profile/selectors.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/modules/profile/selectors.js @@ -44,6 +44,10 @@ export const getPowerPaxTag = compose( lift(hasPath(['tags', 'POWER_PAX'])), getUserData ) +export const getBlockstackTag = compose( + lift(path(['tags', 'BLOCKSTACK'])), + getUserData +) export const isUserCreated = compose( lift(equals(USER_ACTIVATION_STATES.CREATED)), getUserActivationState diff --git a/packages/blockchain-wallet-v4/src/redux/data/actionTypes.js b/packages/blockchain-wallet-v4/src/redux/data/actionTypes.js index 535bb766a51..f1a1a593dfb 100755 --- a/packages/blockchain-wallet-v4/src/redux/data/actionTypes.js +++ b/packages/blockchain-wallet-v4/src/redux/data/actionTypes.js @@ -4,6 +4,7 @@ import * as coinify from './coinify/actionTypes' import * as eth from './eth/actionTypes' import * as misc from './misc/actionTypes' import * as sfox from './sfox/actionTypes' +import * as stx from './stx/actionTypes' import * as xlm from './xlm/actionTypes' -export { bch, btc, coinify, eth, misc, sfox, xlm } +export { bch, btc, coinify, eth, misc, sfox, stx, xlm } diff --git a/packages/blockchain-wallet-v4/src/redux/data/actions.js b/packages/blockchain-wallet-v4/src/redux/data/actions.js index 148a354b2ad..99e33671c77 100755 --- a/packages/blockchain-wallet-v4/src/redux/data/actions.js +++ b/packages/blockchain-wallet-v4/src/redux/data/actions.js @@ -4,6 +4,7 @@ import * as coinify from './coinify/actions' import * as eth from './eth/actions' import * as misc from './misc/actions' import * as sfox from './sfox/actions' +import * as stx from './stx/actions' import * as xlm from './xlm/actions' -export { bch, btc, coinify, eth, misc, sfox, xlm } +export { bch, btc, coinify, eth, misc, sfox, stx, xlm } diff --git a/packages/blockchain-wallet-v4/src/redux/data/sagaRegister.js b/packages/blockchain-wallet-v4/src/redux/data/sagaRegister.js index 8408ef08a0e..0779762b87d 100755 --- a/packages/blockchain-wallet-v4/src/redux/data/sagaRegister.js +++ b/packages/blockchain-wallet-v4/src/redux/data/sagaRegister.js @@ -6,6 +6,7 @@ import coinify from './coinify/sagaRegister' import eth from './eth/sagaRegister' import misc from './misc/sagaRegister' import sfox from './sfox/sagaRegister' +import stx from './stx/sagaRegister' import xlm from './xlm/sagaRegister' export default ({ api, options, networks }) => @@ -16,5 +17,6 @@ export default ({ api, options, networks }) => yield fork(eth({ api })) yield fork(misc({ api })) yield fork(sfox({ api, options })) + yield fork(stx()) yield fork(xlm({ api, networks })) } diff --git a/packages/blockchain-wallet-v4/src/redux/data/sagas.js b/packages/blockchain-wallet-v4/src/redux/data/sagas.js index 5f81009ba4b..a973dfe7538 100755 --- a/packages/blockchain-wallet-v4/src/redux/data/sagas.js +++ b/packages/blockchain-wallet-v4/src/redux/data/sagas.js @@ -3,6 +3,7 @@ import btc from './btc/sagas' import coinify from './coinify/sagas' import eth from './eth/sagas' import sfox from './sfox/sagas' +import stx from './stx/sagas' import xlm from './xlm/sagas' export default ({ api, options, networks }) => ({ @@ -11,5 +12,6 @@ export default ({ api, options, networks }) => ({ coinify: coinify({ api, options }), eth: eth({ api }), sfox: sfox({ api, options }), + stx: stx(), xlm: xlm({ api, networks }) }) diff --git a/packages/blockchain-wallet-v4/src/redux/data/stx/actionTypes.js b/packages/blockchain-wallet-v4/src/redux/data/stx/actionTypes.js new file mode 100644 index 00000000000..83ec46d5896 --- /dev/null +++ b/packages/blockchain-wallet-v4/src/redux/data/stx/actionTypes.js @@ -0,0 +1,2 @@ +export const GENERATE_ADDRESS = '@CORE.DATA.GENERATE_STX_ADDRESS' +export const SET_ADDRESS = '@CORE.DATA.SET_STX_ADDRESS' diff --git a/packages/blockchain-wallet-v4/src/redux/data/stx/actions.js b/packages/blockchain-wallet-v4/src/redux/data/stx/actions.js new file mode 100644 index 00000000000..1286a7ae061 --- /dev/null +++ b/packages/blockchain-wallet-v4/src/redux/data/stx/actions.js @@ -0,0 +1,11 @@ +import * as AT from './actionTypes' + +export const generateAddress = password => ({ + type: AT.GENERATE_ADDRESS, + payload: { password } +}) + +export const setAddress = address => ({ + type: AT.SET_ADDRESS, + payload: { address } +}) diff --git a/packages/blockchain-wallet-v4/src/redux/data/stx/reducers.js b/packages/blockchain-wallet-v4/src/redux/data/stx/reducers.js new file mode 100644 index 00000000000..4ca34537f2d --- /dev/null +++ b/packages/blockchain-wallet-v4/src/redux/data/stx/reducers.js @@ -0,0 +1,15 @@ +import { assoc } from 'ramda' +import * as AT from './actionTypes' + +const INITIAL_STATE = { + address: null +} + +export default (state = INITIAL_STATE, action) => { + const { type, payload } = action + switch (type) { + case AT.SET_ADDRESS: { + return assoc('address', payload.address, state) + } + } +} diff --git a/packages/blockchain-wallet-v4/src/redux/data/stx/sagaRegister.js b/packages/blockchain-wallet-v4/src/redux/data/stx/sagaRegister.js new file mode 100644 index 00000000000..148e07e3826 --- /dev/null +++ b/packages/blockchain-wallet-v4/src/redux/data/stx/sagaRegister.js @@ -0,0 +1,11 @@ +import { takeLatest } from 'redux-saga/effects' +import * as AT from './actionTypes' +import sagas from './sagas' + +export default () => { + const dataStxSagas = sagas() + + return function * coreDataXlmSaga () { + yield takeLatest(AT.GENERATE_ADDRESS, dataStxSagas.generateAddress) + } +} diff --git a/packages/blockchain-wallet-v4/src/redux/data/stx/sagas.js b/packages/blockchain-wallet-v4/src/redux/data/stx/sagas.js new file mode 100644 index 00000000000..21c568c0fe6 --- /dev/null +++ b/packages/blockchain-wallet-v4/src/redux/data/stx/sagas.js @@ -0,0 +1,20 @@ +import { put, select } from 'redux-saga/effects' +import { getMnemonic } from '../../wallet/selectors' +import { callTask } from '../../../utils/functional' +import { deriveAddress } from '../../../utils/stx' +import * as A from './actions' + +export default () => { + const generateAddress = function * (action) { + const { payload } = action + const { password } = payload + const mnemonicT = yield select(getMnemonic, password) + const mnemonic = yield callTask(mnemonicT) + const address = deriveAddress(mnemonic) + yield put(A.setAddress(address)) + } + + return { + generateAddress + } +} diff --git a/packages/blockchain-wallet-v4/src/redux/walletOptions/selectors.js b/packages/blockchain-wallet-v4/src/redux/walletOptions/selectors.js index 6c8f6058fd7..1387db5d35b 100755 --- a/packages/blockchain-wallet-v4/src/redux/walletOptions/selectors.js +++ b/packages/blockchain-wallet-v4/src/redux/walletOptions/selectors.js @@ -56,6 +56,8 @@ export const getXlmSendTimeOutSeconds = state => getSupportedCoins(state).map(path(['XLM', 'config', 'sendTimeOutSeconds'])) export const getXlmExchangeAddresses = state => getSupportedCoins(state).map(path(['XLM', 'exchangeAddresses'])) +export const getStxCampaign = state => + getWebOptions(state).map(path(['coins', 'STX', 'campaign'])) export const getCoinAvailability = curry((state, coin) => getSupportedCoins(state).map(path([toUpper(coin), 'availability'])) ) diff --git a/packages/blockchain-wallet-v4/src/utils/stx.js b/packages/blockchain-wallet-v4/src/utils/stx.js new file mode 100644 index 00000000000..fe33e309a5a --- /dev/null +++ b/packages/blockchain-wallet-v4/src/utils/stx.js @@ -0,0 +1,15 @@ +import BIP39 from 'bip39' +import Bitcoin from 'bitcoinjs-lib' + +export const deriveAddress = mnemonic => { + const seed = BIP39.mnemonicToSeed(mnemonic) + const address = Bitcoin.HDNode.fromSeedBuffer(seed) + .deriveHardened(44) + .deriveHardened(5757) + .deriveHardened(0) + .derive(0) + .derive(0) + .getAddress() + + return address +} diff --git a/packages/blockchain-wallet-v4/src/utils/stx.spec.js b/packages/blockchain-wallet-v4/src/utils/stx.spec.js new file mode 100644 index 00000000000..78a4f8340c1 --- /dev/null +++ b/packages/blockchain-wallet-v4/src/utils/stx.spec.js @@ -0,0 +1,17 @@ +import { deriveAddress } from './stx' + +describe('deriveAddress', () => { + it('should derive correct address', () => { + expect( + deriveAddress( + 'rubber recipe vote copper obtain negative erosion strong kingdom thank tomato void' + ) + ).toBe('1EaiavwwQY2eE2vff5JJGo89dRC13xRM7f') + + expect( + deriveAddress( + 'one remember hint unlock finger reform utility acid speed cushion split client bitter myself protect actor frame forward rather better mercy clay card awesome' + ) + ).toBe('1LJepqGsKKLPxFumnzFndsWTWsaCfkSDTp') + }) +})