diff --git a/web/cashtab/extension/src/components/App.js b/web/cashtab/extension/src/components/App.js --- a/web/cashtab/extension/src/components/App.js +++ b/web/cashtab/extension/src/components/App.js @@ -10,6 +10,7 @@ SettingsIcon, AirdropIcon, ThemedSignAndVerifyMsg, + ThemedUserProfileIcon, } from 'components/Common/CustomIcons'; import '../index.css'; import styled, { ThemeProvider, createGlobalStyle } from 'styled-components'; @@ -20,6 +21,7 @@ import Send from 'components/Send/Send'; import SendToken from 'components/Send/SendToken'; import Airdrop from 'components/Airdrop/Airdrop'; +import Alias from 'components/Alias/Alias'; import Configure from 'components/Configure/Configure'; import SignVerifyMsg from 'components/SignVerifyMsg/SignVerifyMsg'; import NotFound from 'components/NotFound'; @@ -653,6 +655,13 @@ + + + Sign & Verify

+ + history.push('/alias') + } + > + {' '} +

Alias

+ +
diff --git a/web/cashtab/src/assets/user.svg b/web/cashtab/src/assets/user.svg new file mode 100644 --- /dev/null +++ b/web/cashtab/src/assets/user.svg @@ -0,0 +1 @@ + diff --git a/web/cashtab/src/components/Airdrop/__tests__/__snapshots__/Airdrop.test.js.snap b/web/cashtab/src/components/Airdrop/__tests__/__snapshots__/Airdrop.test.js.snap --- a/web/cashtab/src/components/Airdrop/__tests__/__snapshots__/Airdrop.test.js.snap +++ b/web/cashtab/src/components/Airdrop/__tests__/__snapshots__/Airdrop.test.js.snap @@ -3,13 +3,13 @@ exports[`Wallet with BCH balances 1`] = ` Array [

MigrationTestAlpha

@@ -51,7 +51,7 @@
You currently have 0 XEC @@ -61,7 +61,7 @@
,
,
XEC Airdrop Calculator
@@ -143,13 +143,13 @@ exports[`Wallet with BCH balances and tokens 1`] = ` Array [

MigrationTestAlpha

@@ -191,7 +191,7 @@
You currently have 0 XEC @@ -201,7 +201,7 @@
,
,
XEC Airdrop Calculator
@@ -283,13 +283,13 @@ exports[`Wallet with BCH balances and tokens and state field 1`] = ` Array [

MigrationTestAlpha

@@ -331,10 +331,10 @@
0.06 @@ -346,7 +346,7 @@
,
,
XEC Airdrop Calculator
@@ -428,13 +428,13 @@ exports[`Wallet without BCH balance 1`] = ` Array [

MigrationTestAlpha

@@ -476,7 +476,7 @@
You currently have 0 XEC @@ -486,7 +486,7 @@
,
,
XEC Airdrop Calculator
@@ -568,10 +568,10 @@ exports[`Without wallet defined 1`] = ` Array [
You currently have 0 XEC @@ -621,7 +621,7 @@
,
,
XEC Airdrop Calculator
diff --git a/web/cashtab/src/components/Alias/Alias.js b/web/cashtab/src/components/Alias/Alias.js new file mode 100644 --- /dev/null +++ b/web/cashtab/src/components/Alias/Alias.js @@ -0,0 +1,199 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { WalletContext } from 'utils/context'; +import PropTypes from 'prop-types'; +import WalletLabel from 'components/Common/WalletLabel.js'; +import { + ZeroBalanceHeader, + SidePaddingCtn, + WalletInfoCtn, +} from 'components/Common/Atoms'; +import { AntdFormWrapper } from 'components/Common/EnhancedInputs'; +import { Form, Input } from 'antd'; +import { SmartButton } from 'components/Common/PrimaryButton'; +import BalanceHeader from 'components/Common/BalanceHeader'; +import BalanceHeaderFiat from 'components/Common/BalanceHeaderFiat'; +import { Row, Col } from 'antd'; +import { UserOutlined } from '@ant-design/icons'; +import { getWalletState } from 'utils/cashMethods'; +import { currency } from 'components/Common/Ticker.js'; + +export const NamespaceCtn = styled.div` + width: 100%; + margin-top: 50px; + margin-bottom: 20px; + h2 { + color: ${props => props.theme.contrast}; + margin: 0 0 20px; + } + h3 { + color: ${props => props.theme.contrast}; + margin: 0 0 10px; + } +`; + +const Alias = ({ passLoadingStatus }) => { + const ContextValue = React.useContext(WalletContext); + const { + wallet, + fiatPrice, + cashtabSettings, + // chronik, + changeCashtabSettings, + } = ContextValue; + const walletState = getWalletState(wallet); + const { balances } = walletState; + const [formData, setFormData] = useState({ + aliasName: '', + }); + const [alias, setAlias] = useState([]); // stores the alias name array + // const [hasValidAlias, setHasValidAlias] = useState(false); + + const registerAlias = async () => { + passLoadingStatus(true); + + // validate alias input i.e. not empty, no weird symbols + // if invalid input, exit with error notification + + const fullAlias = formData.aliasName; + + if (isAliasAvailable(fullAlias)) { + // call sendXec() passing in: + // currency.aliasSettings.aliasPaymentAddress as destinationAddress + // fullAlias as optionalOpReturnMsg + // registration fee as sendAmount + // true for optionalAliasRegistrationFlag + // ** Utilize websockets to only trigger success notification upon 1 conf of the registration tx ** + } else { + // error notificationon alias being unavailable + } + + // set alias as pending until subsequent websocket notification on 1 conf on the registration tx + setAlias(fullAlias + ' (Pending)'); + passLoadingStatus(false); + }; + + // potentially move this to transaction.js in later diffs + const isAliasAvailable = alias => { + alias; // linting bypass + + // if isLocalAliasStateLatest() is true then retrieve incoming tx history from localStorage + // else retrieve via chronik and update localStorage + + // for each incoming tx with OP_RETURN outputs that paid at least currency.aliasSettings.aliasMinFee + // filter for currency.opReturn.appPrefixesHex.aliasRegistration + // filter for valid payment fee in output[1] + // add opReturnMsg (the alias) to an aliasArray + + // if alias is in aliasArray + // return false + return true; + }; + + const handleAliasNameInput = e => { + const { name, value } = e.target; + setFormData(p => ({ + ...p, + [name]: value, + })); + }; + + return ( + <> + + + {!balances.totalBalance ? ( + + You currently have 0 {currency.ticker} +
+ Deposit some funds to use this feature +
+ ) : ( + <> + + {fiatPrice !== null && ( + + )} + + )} +
+ + + + +

eCash Namespace Alias

+

+ + {/*TODO: alias array to be rendered appropriately in later diffs*/} +   + {alias.length > 0 + ? alias.map(alias => ( +
  • {alias}
  • + )) + : 'N/A'} +

    +
    + + +
    + + + handleAliasNameInput(e) + } + /> + + + registerAlias()} + > + Register Alias + + +
    +
    +
    + +
    +
    + + ); +}; + +/* +passLoadingStatus must receive a default prop that is a function +in order to pass the rendering unit test in Alias.test.js + +status => {console.log(status)} is an arbitrary stub function +*/ + +Alias.defaultProps = { + passLoadingStatus: status => { + console.log(status); + }, +}; + +Alias.propTypes = { + passLoadingStatus: PropTypes.func, +}; + +export default Alias; diff --git a/web/cashtab/src/components/Alias/__tests__/Alias.test.js b/web/cashtab/src/components/Alias/__tests__/Alias.test.js new file mode 100644 --- /dev/null +++ b/web/cashtab/src/components/Alias/__tests__/Alias.test.js @@ -0,0 +1,108 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { ThemeProvider } from 'styled-components'; +import { theme } from 'assets/styles/theme'; +import Alias from 'components/Alias/Alias'; +import { + walletWithBalancesAndTokens, + walletWithBalancesMock, + walletWithoutBalancesMock, + walletWithBalancesAndTokensWithCorrectState, +} from '../../Home/__mocks__/walletAndBalancesMock'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { WalletContext } from 'utils/context'; + +beforeEach(() => { + // Mock method not implemented in JSDOM + // See reference at https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); +}); + +test('Wallet without XEC balance', () => { + const component = renderer.create( + + + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + +test('Wallet with XEC balances', () => { + const component = renderer.create( + + + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + +test('Wallet with XEC balances and tokens', () => { + const component = renderer.create( + + + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + +test('Wallet with XEC balances and tokens and state field', () => { + const component = renderer.create( + + + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + +test('Without wallet defined', () => { + const withoutWalletDefinedMock = { + wallet: {}, + balances: { totalBalance: 0 }, + loading: false, + }; + const component = renderer.create( + + + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); diff --git a/web/cashtab/src/components/Alias/__tests__/__snapshots__/Alias.test.js.snap b/web/cashtab/src/components/Alias/__tests__/__snapshots__/Alias.test.js.snap new file mode 100644 --- /dev/null +++ b/web/cashtab/src/components/Alias/__tests__/__snapshots__/Alias.test.js.snap @@ -0,0 +1,1011 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP @generated + +exports[`Wallet with XEC balances 1`] = ` +Array [ +
    +
    +

    + MigrationTestAlpha +

    +
    + + edit.svg + + +
    + + +
    +
    +
    + You currently have 0 + XEC +
    + Deposit some funds to use this feature +
    +
    , +
    +
    +
    +
    +

    + eCash Namespace Alias +

    +

    + + + +   + N/A +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + . xec + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    , +] +`; + +exports[`Wallet with XEC balances and tokens 1`] = ` +Array [ +
    +
    +

    + MigrationTestAlpha +

    + + + edit.svg + + +
    + + +
    +
    +
    + You currently have 0 + XEC +
    + Deposit some funds to use this feature +
    +
    , +
    +
    +
    +
    +

    + eCash Namespace Alias +

    +

    + + + +   + N/A +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + . xec + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    , +] +`; + +exports[`Wallet with XEC balances and tokens and state field 1`] = ` +Array [ +
    +
    +

    + MigrationTestAlpha +

    + + + edit.svg + + +
    + + +
    +
    +
    + + + 0.06 + + XEC + + +
    +
    , +
    +
    +
    +
    +

    + eCash Namespace Alias +

    +

    + + + +   + N/A +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + . xec + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    , +] +`; + +exports[`Wallet without XEC balance 1`] = ` +Array [ +
    +
    +

    + MigrationTestAlpha +

    + + + edit.svg + + +
    + + +
    +
    +
    + You currently have 0 + XEC +
    + Deposit some funds to use this feature +
    +
    , +
    +
    +
    +
    +

    + eCash Namespace Alias +

    +

    + + + +   + N/A +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + . xec + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    , +] +`; + +exports[`Without wallet defined 1`] = ` +Array [ +
    +
    + + + edit.svg + + +
    + + +
    +
    +
    + You currently have 0 + XEC +
    + Deposit some funds to use this feature +
    +
    , +
    +
    +
    +
    +

    + eCash Namespace Alias +

    +

    + + + +   + N/A +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + . xec + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    , +] +`; diff --git a/web/cashtab/src/components/App.js b/web/cashtab/src/components/App.js --- a/web/cashtab/src/components/App.js +++ b/web/cashtab/src/components/App.js @@ -10,6 +10,7 @@ SettingsIcon, AirdropIcon, ThemedSignAndVerifyMsg, + ThemedUserProfileIcon, } from 'components/Common/CustomIcons'; import '../index.css'; import styled, { ThemeProvider, createGlobalStyle } from 'styled-components'; @@ -20,6 +21,7 @@ import Send from 'components/Send/Send'; import SendToken from 'components/Send/SendToken'; import Airdrop from 'components/Airdrop/Airdrop'; +import Alias from 'components/Alias/Alias'; import Configure from 'components/Configure/Configure'; import SignVerifyMsg from 'components/SignVerifyMsg/SignVerifyMsg'; import NotFound from 'components/NotFound'; @@ -586,6 +588,13 @@ + + + Sign & Verify

    + + history.push('/alias') + } + > + {' '} +

    Alias

    + +
    diff --git a/web/cashtab/src/components/Common/CustomIcons.js b/web/cashtab/src/components/Common/CustomIcons.js --- a/web/cashtab/src/components/Common/CustomIcons.js +++ b/web/cashtab/src/components/Common/CustomIcons.js @@ -32,6 +32,7 @@ import { ReactComponent as Audit } from 'assets/audit.svg'; import { ReactComponent as Mail } from 'assets/mail.svg'; export const CashLoadingIcon = ; +import { ReactComponent as User } from 'assets/user.svg'; export const CashReceivedNotificationIcon = () => ( @@ -160,6 +161,11 @@ min-width: 24px; `; +export const ThemedUserProfileIcon = styled(User)` + height: 33px; + width: 30px; +`; + export const LoadingBlock = styled.div` width: 100%; display: flex; diff --git a/web/cashtab/src/components/Common/Ticker.js b/web/cashtab/src/components/Common/Ticker.js --- a/web/cashtab/src/components/Common/Ticker.js +++ b/web/cashtab/src/components/Common/Ticker.js @@ -7,6 +7,22 @@ ticker: 'XEC', logo: mainLogo, legacyPrefix: 'bitcoincash', + aliasSettings: { + // change to multisig prior to landing in prod + aliasPaymentAddress: 'ecash:qp3c268rd5946l2f5m5es4x25f7ewu4sjvpy52pqa8', + // nominal pricing tiers for testing only - to be used as 'aliasRegistrationFeeInSats.oneChar' + aliasRegistrationFeeInSats: { + oneChar: 558, + twoChar: 557, + threeChar: 556, + fourChar: 555, + fiveChar: 554, + sixChar: 553, + sevenChar: 552, + eightChar: 551, + minFee: 550, // dust + }, + }, coingeckoId: 'ecash', defaultFee: 2.01, dustSats: 550, @@ -50,6 +66,7 @@ cashtab: '00746162', cashtabEncrypted: '65746162', airdrop: '64726f70', + aliasRegistration: '2E786563', }, encryptedMsgCharLimit: 125, unencryptedMsgCharLimit: 206, diff --git a/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap b/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap --- a/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap +++ b/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap @@ -2,10 +2,10 @@ exports[`Wallet with BCH balances and tokens 1`] = `

    ,
    ,
    @@ -386,13 +386,13 @@ onClick={[Function]} >

    TBS @@ -412,13 +412,13 @@ exports[`Wallet without BCH balance 1`] = ` Array [

    MigrationTestAlpha

    @@ -460,10 +460,10 @@
    0 @@ -474,26 +474,26 @@

    ,
    @@ -541,18 +541,18 @@ exports[`Without wallet defined 1`] = `
    ecash: qzagy47m vh6qxkvcn3acjnz73rkhkc6y7c cxkrr6zd @@ -161,7 +161,7 @@

    MigrationTestAlpha

    @@ -262,7 +262,7 @@
    You currently have 0 XEC @@ -271,7 +271,7 @@
    ecash: qzagy47m vh6qxkvcn3acjnz73rkhkc6y7c cxkrr6zd @@ -370,7 +370,7 @@

    MigrationTestAlpha

    @@ -471,10 +471,10 @@
    0.06 @@ -485,7 +485,7 @@
    ecash: qzagy47m vh6qxkvcn3acjnz73rkhkc6y7c cxkrr6zd @@ -584,7 +584,7 @@

    MigrationTestAlpha

    @@ -685,7 +685,7 @@
    You currently have 0 XEC @@ -694,7 +694,7 @@
    ecash: qzagy47m vh6qxkvcn3acjnz73rkhkc6y7c cxkrr6zd @@ -793,7 +793,7 @@
    ,
    Multiple Recipients:  

    0 XEC

    = @@ -497,16 +497,16 @@ } >

    MigrationTestAlpha

    @@ -631,7 +631,7 @@
    You currently have 0 XEC @@ -640,7 +640,7 @@
    ,
    Multiple Recipients:  

    0 XEC

    = @@ -1077,16 +1077,16 @@ } >

    MigrationTestAlpha

    @@ -1211,10 +1211,10 @@
    0.06 @@ -1225,7 +1225,7 @@
    ,
    Multiple Recipients:  

    0 XEC

    = @@ -1662,17 +1662,17 @@ } >

    MigrationTestAlpha

    @@ -1797,7 +1797,7 @@
    You currently have 0 XEC @@ -1806,7 +1806,7 @@
    ,
    Multiple Recipients:  

    0 XEC

    = @@ -2243,16 +2243,16 @@ } >
    ,
    Multiple Recipients:  

    0 XEC

    = @@ -2818,16 +2818,16 @@ } >
    6.001000000 @@ -33,7 +33,7 @@ } >
    @@ -223,7 +223,7 @@ } >
    0 @@ -65,17 +65,17 @@
    ,

    Create a Token

    You need at least @@ -96,13 +96,13 @@ exports[`Wallet with BCH balances and tokens 1`] = ` Array [

    MigrationTestAlpha

    @@ -144,10 +144,10 @@
    0 @@ -158,17 +158,17 @@
    ,

    Create a Token

    You need at least @@ -189,13 +189,13 @@ exports[`Wallet with BCH balances and tokens and state field 1`] = ` Array [

    MigrationTestAlpha

    @@ -237,10 +237,10 @@
    0.06 @@ -251,16 +251,16 @@
    ,

    Create a Token

    @@ -311,7 +311,7 @@
    @@ -349,7 +349,7 @@
    @@ -387,7 +387,7 @@
    @@ -425,7 +425,7 @@
    @@ -463,7 +463,7 @@
    @@ -569,7 +569,7 @@
    0 @@ -672,17 +672,17 @@
    ,

    Create a Token

    You need at least @@ -703,10 +703,10 @@ exports[`Without wallet defined 1`] = ` Array [

    ,

    Create a Token

    You need at least diff --git a/web/cashtab/src/utils/cashMethods.js b/web/cashtab/src/utils/cashMethods.js --- a/web/cashtab/src/utils/cashMethods.js +++ b/web/cashtab/src/utils/cashMethods.js @@ -567,6 +567,7 @@ airdropFlag, airdropTokenId, encryptedEj, + // optionalAliasRegistrationFlag, ) => { // encrypted mesage is mandatory when encryptionFlag is true // airdrop token id is mandatory when airdropFlag is true @@ -614,6 +615,13 @@ Buffer.from(currency.opReturn.appPrefixesHex.cashtab, 'hex'), // 00746162 ); + /* + if (optionalAliasRegistrationFlag) { + add currency.opReturn.appPrefixesHex.aliasRegistration to script + } + */ + // the alias to be registered is the optionalOpReturnMsg + // add the un-encrypted message to script if supplied if (optionalOpReturnMsg) { script.push(Buffer.from(optionalOpReturnMsg)); @@ -781,6 +789,8 @@ ) { // add the airdrop prefix to array resultArray[i] = currency.opReturn.appPrefixesHex.airdrop; + // TODO: if i === 1 and message === currency.opReturn.appPrefixesHex.aliasRegistration + // flag accordingly } else { // this is either an external message or a subsequent cashtab message loop to extract the message resultArray[i] = message; diff --git a/web/cashtab/src/utils/chronik.js b/web/cashtab/src/utils/chronik.js --- a/web/cashtab/src/utils/chronik.js +++ b/web/cashtab/src/utils/chronik.js @@ -551,6 +551,9 @@ } if (txType === currency.opReturn.appPrefixesHex.cashtab) { + // if this is an alias registration, render accordingly + // isAliasRegistration = true; + // this is a Cashtab message try { opReturnMessage = Buffer.from( diff --git a/web/cashtab/src/utils/transactions.js b/web/cashtab/src/utils/transactions.js --- a/web/cashtab/src/utils/transactions.js +++ b/web/cashtab/src/utils/transactions.js @@ -332,6 +332,7 @@ airdropFlag, airdropTokenId, optionalMockPubKeyResponse = false, + // optionalAliasRegistrationFlag, ) => { try { let txBuilder = new TransactionBuilder(); @@ -406,6 +407,7 @@ airdropFlag, airdropTokenId, encryptedEj, + // optionalAliasRegistrationFlag, ); txBuilder.addOutput(opReturnData, 0); } diff --git a/web/standards/op_return-prefix-guideline.md b/web/standards/op_return-prefix-guideline.md --- a/web/standards/op_return-prefix-guideline.md +++ b/web/standards/op_return-prefix-guideline.md @@ -18,6 +18,7 @@ | 00746162 | Cashtab | Bitcoin ABC | ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j | https://cashtab.com/ | n/a | | 65746162 | Cashtab Encrypted | Bitcoin ABC | ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j | https://cashtab.com/ | n/a | | 64726f70 | Airdrop | Bitcoin ABC | ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j | https://cashtab.com/ | n/a | +| 2E786563 | Namespace Alias | Bitcoin ABC | ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j | https://cashtab.com/ | n/a | ---