mirror of
https://github.com/zhigang1992/connect.git
synced 2026-01-12 17:12:56 +08:00
feat: add debug mode for transaction signing
This commit is contained in:
@@ -2,4 +2,8 @@ module.exports = {
|
||||
root: true,
|
||||
reportUnusedDisableDirectives: true,
|
||||
extends: ['@blockstack/eslint-config'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json',
|
||||
}
|
||||
};
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -11,4 +11,5 @@ dist/
|
||||
.rts2_cache_umd/
|
||||
yarn-error.log
|
||||
.github/workflows/event.json
|
||||
.npmrc
|
||||
.npmrc
|
||||
coverage/
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
11
package.json
11
package.json
@@ -2,8 +2,8 @@
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"typecheck": "lerna run typecheck --parallel",
|
||||
"dev": "yarn lerna exec --parallel 'yarn dev' --scope test-app --scope @blockstack/app",
|
||||
"typecheck": "lerna run typecheck --parallel --no-bail --stream",
|
||||
"dev": "NODE_ENV=development yarn lerna exec --parallel 'yarn dev' --scope test-app --scope @blockstack/app",
|
||||
"bootstrap": "yarn lerna exec --parallel 'yarn'",
|
||||
"build:libs": "yarn build:ui && yarn build:keychain && yarn build:connect",
|
||||
"build:ui": "lerna run build --scope @blockstack/ui",
|
||||
@@ -39,6 +39,7 @@
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-config-react-app": "^5.2.1",
|
||||
"eslint-plugin-flowtype": "^4.7.0",
|
||||
"eslint-plugin-import": "^2.21.2 ",
|
||||
"eslint-plugin-jest": "^23.11.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
@@ -47,7 +48,7 @@
|
||||
"husky": "^4.2.3",
|
||||
"lerna": "^3.20.2",
|
||||
"prettier": "^2.0.5",
|
||||
"typescript": "^3.8.2"
|
||||
"typescript": "^3.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "^7.10.3",
|
||||
@@ -55,6 +56,8 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"@blockstack/eslint-config": "^1.0.5",
|
||||
"@blockstack/prettier-config": "^0.0.6"
|
||||
"eslint-plugin-import": "2.21.2",
|
||||
"@blockstack/prettier-config": "^0.0.6",
|
||||
"buffer": "5.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ module.exports = {
|
||||
// testResultsProcessor: null,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
testRunner: "jest-circus/runner",
|
||||
testRunner: 'jest-circus/runner',
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
@@ -27,17 +27,20 @@
|
||||
"@blockstack/connect": "^2.8.3",
|
||||
"@blockstack/keychain": "^0.7.5",
|
||||
"@blockstack/prettier-config": "^0.0.6",
|
||||
"@blockstack/stacks-transactions": "^0.4.6",
|
||||
"@blockstack/stats": "^0.7.0",
|
||||
"@blockstack/ui": "^2.9.5",
|
||||
"@blockstack/rpc-client": "^0.3.0-alpha.0",
|
||||
"@blockstack/stacks-transactions": "0.5.1",
|
||||
"@rehooks/document-title": "^1.0.1",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"blockstack": "^19.3.0",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"blockstack": "21.0.0",
|
||||
"bn.js": "^5.1.1",
|
||||
"buffer": "^5.6.0",
|
||||
"formik": "^2.1.4",
|
||||
"history": "^5.0.0-beta.4",
|
||||
"mdi-react": "^6.7.0",
|
||||
"preact": "^10.4.0",
|
||||
"prettier": "^2.0.5",
|
||||
"react": "^16.13.1",
|
||||
"react-chrome-redux": "^2.0.0-alpha.5",
|
||||
"react-dom": "^16.13.1",
|
||||
@@ -64,6 +67,7 @@
|
||||
"@babel/preset-react": "^7.9.4",
|
||||
"@babel/preset-typescript": "^7.9.0",
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"@blockstack/prettier-config": "^0.0.6",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.2.0",
|
||||
"@schemastore/web-manifest": "^0.0.4",
|
||||
"@types/chrome": "^0.0.104",
|
||||
@@ -91,16 +95,13 @@
|
||||
"playwright": "^1.1.1",
|
||||
"playwright-chromium": "^1.1.1",
|
||||
"playwright-core": "^1.1.1",
|
||||
"playwright-firefox": "^1.1.1",
|
||||
"playwright-webkit": "^1.1.1",
|
||||
"prettier": "^2.0.4",
|
||||
"prettier": "^2.0.5",
|
||||
"react-dev-utils": "^10.2.0",
|
||||
"react-refresh": "^0.7.2",
|
||||
"react-test-renderer": "^16.8.6",
|
||||
"terser-webpack-plugin": "^2.3.5",
|
||||
"ts-jest": "^25.2.0",
|
||||
"ts-loader": "^6.0.4",
|
||||
"typescript": "3.7.5",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-bundle-analyzer": "^3.6.0",
|
||||
"webpack-chrome-extension-reloader": "^1.3.0",
|
||||
@@ -120,6 +121,9 @@
|
||||
"prettier": "@blockstack/prettier-config",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/blockstack/blockstack-app.git"
|
||||
"url": "git://github.com/blockstack/ux.git"
|
||||
},
|
||||
"resolutions": {
|
||||
"buffer": "5.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import {
|
||||
selectIdentities,
|
||||
selectCurrentWallet,
|
||||
@@ -10,8 +10,10 @@ import { selectSecretKey } from '@store/onboarding/selectors';
|
||||
import { decrypt } from '@blockstack/keychain';
|
||||
import { DEFAULT_PASSWORD } from '@store/onboarding/types';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { doStoreSeed } from '@store/wallet';
|
||||
|
||||
export const useWallet = () => {
|
||||
const dispatch = useDispatch();
|
||||
const identities = useSelector(selectIdentities);
|
||||
const firstIdentity = useSelector(selectFirstIdentity);
|
||||
const wallet = useSelector(selectCurrentWallet);
|
||||
@@ -27,9 +29,20 @@ export const useWallet = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateSTXKeychain = async () => {
|
||||
if (wallet && !wallet.stacksPrivateKey) {
|
||||
const decryptedKey = await decrypt(wallet?.encryptedBackupPhrase, DEFAULT_PASSWORD);
|
||||
dispatch(doStoreSeed(decryptedKey, DEFAULT_PASSWORD));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void fetchSecretKey();
|
||||
}, [onboardingSecretKey]);
|
||||
|
||||
useEffect(() => {
|
||||
void updateSTXKeychain();
|
||||
}, []);
|
||||
|
||||
return { identities, firstIdentity, wallet, secretKey, isRestoringWallet, isSignedIn };
|
||||
};
|
||||
|
||||
@@ -45,31 +45,4 @@ const faqs = (appName: string) => {
|
||||
];
|
||||
};
|
||||
|
||||
const howDataVaultWorks = [
|
||||
{
|
||||
icon: '/assets/images/icon-cross-over-eye.svg',
|
||||
title: 'Private data storage',
|
||||
body:
|
||||
'Normally, companies store your data on their servers for them to keep. Data Vault stores your encrypted data independently from the app, so companies like Nurx (and even Data Vault) can’t have access.',
|
||||
},
|
||||
{
|
||||
icon: '/assets/images/icon-padlock.svg',
|
||||
title: 'Encryption that’s always on',
|
||||
body:
|
||||
'Encryption turns your data into indecipherable text that can be read only using the Secret Key that you control. This keeps everything you do private.',
|
||||
},
|
||||
{
|
||||
icon: '/assets/images/icon-chain-of-blocks.svg',
|
||||
title: 'Blockchain technology',
|
||||
body:
|
||||
'The Secret Key that unlocks your Data Vault is made using blockchain technology. That ensures there is only ever one, and that no one can take it from you. Your data will be private, out of the hands of companies, and only accessible to you.',
|
||||
},
|
||||
{
|
||||
icon: '/assets/images/icon-shapes.svg',
|
||||
title: 'One Vault works with 100s of apps',
|
||||
body:
|
||||
'You’ll only ever have to create one Data Vault to use 100s of other apps like Nurx privately.',
|
||||
},
|
||||
];
|
||||
|
||||
export { faqs, howDataVaultWorks };
|
||||
export { faqs };
|
||||
|
||||
57
packages/app/src/common/stacks-utils.ts
Normal file
57
packages/app/src/common/stacks-utils.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { ContractCallArgument, ContractCallArgumentType } from '@blockstack/connect';
|
||||
import {
|
||||
uintCV,
|
||||
intCV,
|
||||
falseCV,
|
||||
trueCV,
|
||||
contractPrincipalCV,
|
||||
standardPrincipalCV,
|
||||
bufferCV,
|
||||
} from '@blockstack/stacks-transactions';
|
||||
import RPCClient from '@blockstack/rpc-client';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
export const encodeContractCallArgument = ({ type, value }: ContractCallArgument) => {
|
||||
switch (type) {
|
||||
case ContractCallArgumentType.UINT:
|
||||
return uintCV(value);
|
||||
case ContractCallArgumentType.INT:
|
||||
return intCV(value);
|
||||
case ContractCallArgumentType.BOOL:
|
||||
if (value === 'false' || value === '0') return falseCV();
|
||||
else if (value === 'true' || value === '1') return trueCV();
|
||||
else throw new Error(`Unexpected Clarity bool value: ${JSON.stringify(value)}`);
|
||||
case ContractCallArgumentType.PRINCIPAL:
|
||||
if (value.includes('.')) {
|
||||
const [addr, name] = value.split('.');
|
||||
return contractPrincipalCV(addr, name);
|
||||
} else {
|
||||
return standardPrincipalCV(value);
|
||||
}
|
||||
case ContractCallArgumentType.BUFFER:
|
||||
return bufferCV(Buffer.from(value));
|
||||
default:
|
||||
throw new Error(`Unexpected Clarity type: ${type}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const getRPCClient = () => {
|
||||
const { origin } = location;
|
||||
const url = origin.includes('localhost')
|
||||
? 'http://localhost:3999'
|
||||
: 'https://sidecar.staging.blockstack.xyz';
|
||||
return new RPCClient(url);
|
||||
};
|
||||
|
||||
export const stacksValue = ({
|
||||
value,
|
||||
fixedDecimals = false,
|
||||
}: {
|
||||
value: number;
|
||||
fixedDecimals?: boolean;
|
||||
}) => {
|
||||
const microStacks = new BigNumber(value);
|
||||
const stacks = microStacks.shiftedBy(-6);
|
||||
const stxString = fixedDecimals ? stacks.toFormat(6) : stacks.decimalPlaces(6).toFormat();
|
||||
return `${stxString} STX`;
|
||||
};
|
||||
@@ -22,6 +22,10 @@ export const USERNAME_SUBMITTED = 'Submit Username';
|
||||
export const USERNAME_VALIDATION_ERROR = 'Validation Error Username';
|
||||
export const USERNAME_SUBMIT_SUCCESS = 'Submit Username Success';
|
||||
|
||||
export const TRANSACTION_SIGN_START = 'Start Transaction Sign Screen';
|
||||
export const TRANSACTION_SIGN_SUBMIT = 'Submit Transaction Sign';
|
||||
export const TRANSACTION_SIGN_ERROR = 'Fail Transaction Sign';
|
||||
|
||||
// Nice page names for Mark to see in Mixpanel
|
||||
export const pageTrackingNameMap = {
|
||||
[ScreenPaths.CHOOSE_ACCOUNT]: 'Choose Account',
|
||||
@@ -53,7 +57,7 @@ export const titleNameMap = {
|
||||
|
||||
export const doTrackScreenChange = (
|
||||
screen: ScreenPaths,
|
||||
decodedAuthRequest: DecodedAuthRequest | undefined
|
||||
decodedAuthRequest?: DecodedAuthRequest
|
||||
) => {
|
||||
if (titleNameMap[screen]) {
|
||||
document.title = titleNameMap[screen];
|
||||
|
||||
149
packages/app/src/common/transaction-utils.ts
Normal file
149
packages/app/src/common/transaction-utils.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import {
|
||||
TransactionVersion,
|
||||
StacksTransaction,
|
||||
deserializeCV,
|
||||
} from '@blockstack/stacks-transactions';
|
||||
import { Wallet } from '@blockstack/keychain';
|
||||
import { getRPCClient } from './stacks-utils';
|
||||
import {
|
||||
ContractDeployPayload,
|
||||
ContractCallPayload,
|
||||
STXTransferPayload,
|
||||
TransactionPayload,
|
||||
TransactionTypes,
|
||||
} from '@blockstack/connect';
|
||||
import { doTrack, TRANSACTION_SIGN_SUBMIT, TRANSACTION_SIGN_ERROR } from '@common/track';
|
||||
import { finalizeTxSignature } from './utils';
|
||||
|
||||
export const generateContractCallTx = ({
|
||||
txData,
|
||||
wallet,
|
||||
nonce,
|
||||
}: {
|
||||
txData: ContractCallPayload;
|
||||
wallet: Wallet;
|
||||
nonce: number;
|
||||
}) => {
|
||||
const { contractName, contractAddress, functionName, functionArgs } = txData;
|
||||
const version = TransactionVersion.Testnet;
|
||||
const args = functionArgs.map(arg => {
|
||||
return deserializeCV(Buffer.from(arg, 'hex'));
|
||||
});
|
||||
|
||||
return wallet.getSigner().signContractCall({
|
||||
contractName,
|
||||
contractAddress,
|
||||
functionName,
|
||||
functionArgs: args,
|
||||
version,
|
||||
nonce,
|
||||
postConditionMode: txData.postConditionMode,
|
||||
postConditions: txData.postConditions,
|
||||
});
|
||||
};
|
||||
|
||||
export const generateContractDeployTx = ({
|
||||
txData,
|
||||
wallet,
|
||||
nonce,
|
||||
}: {
|
||||
txData: ContractDeployPayload;
|
||||
wallet: Wallet;
|
||||
nonce: number;
|
||||
}) => {
|
||||
const { contractName, codeBody } = txData;
|
||||
const version = TransactionVersion.Testnet;
|
||||
|
||||
return wallet.getSigner().signContractDeploy({
|
||||
contractName,
|
||||
codeBody,
|
||||
version,
|
||||
nonce,
|
||||
postConditionMode: txData.postConditionMode,
|
||||
postConditions: txData.postConditions,
|
||||
});
|
||||
};
|
||||
|
||||
export const generateSTXTransferTx = ({
|
||||
txData,
|
||||
wallet,
|
||||
nonce,
|
||||
}: {
|
||||
txData: STXTransferPayload;
|
||||
wallet: Wallet;
|
||||
nonce: number;
|
||||
}) => {
|
||||
const { recipient, memo, amount } = txData;
|
||||
return wallet.getSigner().signSTXTransfer({
|
||||
recipient,
|
||||
memo,
|
||||
amount,
|
||||
nonce,
|
||||
postConditionMode: txData.postConditionMode,
|
||||
postConditions: txData.postConditions,
|
||||
});
|
||||
};
|
||||
|
||||
export const generateTransaction = async ({
|
||||
txData,
|
||||
wallet,
|
||||
nonce,
|
||||
}: {
|
||||
wallet: Wallet;
|
||||
nonce: number;
|
||||
txData: TransactionPayload;
|
||||
}) => {
|
||||
let tx: StacksTransaction | null = null;
|
||||
switch (txData.txType) {
|
||||
case TransactionTypes.ContractCall:
|
||||
tx = await generateContractCallTx({ txData, wallet, nonce });
|
||||
break;
|
||||
case TransactionTypes.ContractDeploy:
|
||||
tx = await generateContractDeployTx({ txData, wallet, nonce });
|
||||
break;
|
||||
case TransactionTypes.STXTransfer:
|
||||
tx = await generateSTXTransferTx({ txData, wallet, nonce });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!tx) {
|
||||
throw new Error(`Invalid Transaction Type: ${txData.txType}`);
|
||||
}
|
||||
return tx;
|
||||
};
|
||||
|
||||
export const finishTransaction = async ({
|
||||
tx,
|
||||
pendingTransaction,
|
||||
}: {
|
||||
tx: StacksTransaction;
|
||||
pendingTransaction: TransactionPayload;
|
||||
}) => {
|
||||
const serialized = tx.serialize();
|
||||
const txRaw = serialized.toString('hex');
|
||||
const client = getRPCClient();
|
||||
const res = await client.broadcastTX(serialized);
|
||||
|
||||
if (res.ok) {
|
||||
doTrack(TRANSACTION_SIGN_SUBMIT, {
|
||||
txType: pendingTransaction?.txType,
|
||||
appName: pendingTransaction?.appDetails?.name,
|
||||
});
|
||||
const txId: string = await res.json();
|
||||
finalizeTxSignature({ txId, txRaw });
|
||||
} else {
|
||||
const response = await res.json();
|
||||
if (response.error) {
|
||||
const error = `${response.error} - ${response.reason}`;
|
||||
doTrack(TRANSACTION_SIGN_ERROR, {
|
||||
txType: pendingTransaction?.txType,
|
||||
appName: pendingTransaction?.appDetails?.name,
|
||||
error: error,
|
||||
});
|
||||
console.error(response.error);
|
||||
console.error(response.reason);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DecodedAuthRequest } from './dev/types';
|
||||
import { wordlists } from 'bip39';
|
||||
import { FinishedTxData } from '@blockstack/connect';
|
||||
|
||||
export const getAuthRequestParam = () => {
|
||||
const { hash } = document.location;
|
||||
@@ -50,7 +51,6 @@ interface FinalizeAuthParams {
|
||||
* but using a new tab.
|
||||
*
|
||||
*/
|
||||
|
||||
export const finalizeAuthResponse = ({
|
||||
decodedAuthRequest,
|
||||
authRequest,
|
||||
@@ -68,7 +68,7 @@ export const finalizeAuthResponse = ({
|
||||
}
|
||||
}
|
||||
window.close();
|
||||
}, 150);
|
||||
}, 500);
|
||||
window.addEventListener('message', event => {
|
||||
if (authRequest && event.data.authRequest === authRequest) {
|
||||
const source = getEventSourceWindow(event);
|
||||
@@ -86,6 +86,23 @@ export const finalizeAuthResponse = ({
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const finalizeTxSignature = (data: FinishedTxData) => {
|
||||
window.addEventListener('message', event => {
|
||||
const source = getEventSourceWindow(event);
|
||||
if (source) {
|
||||
source.postMessage(
|
||||
{
|
||||
...data,
|
||||
source: 'blockstack-app',
|
||||
},
|
||||
event.origin
|
||||
);
|
||||
}
|
||||
window.close();
|
||||
});
|
||||
};
|
||||
|
||||
export const openPopup = (actionsUrl: string) => {
|
||||
// window.open(actionsUrl, 'Blockstack', 'scrollbars=no,status=no,menubar=no,width=300px,height=200px,left=0,top=0')
|
||||
const height = 584;
|
||||
@@ -93,6 +110,7 @@ export const openPopup = (actionsUrl: string) => {
|
||||
// width=440,height=584
|
||||
popupCenter(actionsUrl, 'Blockstack', width, height);
|
||||
};
|
||||
|
||||
// open a popup, centered on the screen, with logic to handle dual-monitor setups
|
||||
export const popupCenter = (url: string, title: string, w: number, h: number) => {
|
||||
const dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : window.screenX;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ScreenBody, ScreenActions, Title } from '@blockstack/connect';
|
||||
import useOnClickOutside from 'use-onclickoutside';
|
||||
|
||||
import { Image } from '@components/image';
|
||||
import { ConfigApp } from '@blockstack/keychain/wallet';
|
||||
import { ConfigApp } from '@blockstack/keychain';
|
||||
|
||||
interface PreviousAppsProps {
|
||||
apps: ConfigApp[];
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Home } from '../pages/home';
|
||||
import { Create, SaveKey } from '../pages/sign-up';
|
||||
import { SignIn, DecryptRecoveryCode } from '../pages/sign-in';
|
||||
import { Home } from '@pages/home';
|
||||
import { Create, SaveKey } from '@pages/sign-up';
|
||||
import { SignIn, DecryptRecoveryCode } from '@pages/sign-in';
|
||||
|
||||
import { Username } from '../pages/username';
|
||||
import { SecretKey } from '../pages/secret-key';
|
||||
import { Username } from '@pages/username';
|
||||
import { SecretKey } from '@pages/secret-key';
|
||||
|
||||
import { ChooseAccount } from '../pages/connect';
|
||||
import { ChooseAccount } from '@pages/connect';
|
||||
|
||||
import { Transaction } from '@pages/transaction';
|
||||
|
||||
import { doSaveAuthRequest } from '@store/onboarding/actions';
|
||||
import { useDispatch } from '@common/hooks/use-dispatch';
|
||||
@@ -112,6 +114,8 @@ export const Routes: React.FC = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{/* Transactions */}
|
||||
<Route path="/transaction" element={<Transaction />} />
|
||||
{/*Error/Misc*/}
|
||||
<Route
|
||||
path="/settings/secret-key"
|
||||
|
||||
98
packages/app/src/components/tabbed-card.tsx
Normal file
98
packages/app/src/components/tabbed-card.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Flex, Text, BoxProps } from '@blockstack/ui';
|
||||
|
||||
export interface Tab {
|
||||
title: string | React.ReactNode;
|
||||
key: string;
|
||||
content: React.ReactNode;
|
||||
hide?: boolean;
|
||||
}
|
||||
|
||||
export interface TabHeaderProps extends BoxProps {
|
||||
active: boolean;
|
||||
tab: Tab;
|
||||
}
|
||||
|
||||
const getBorderProps = (active: boolean) => {
|
||||
if (active) {
|
||||
return {
|
||||
borderBottomWidth: '1px',
|
||||
borderColor: 'blue',
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const getTextProps = (active: boolean) => {
|
||||
if (active) {
|
||||
return {
|
||||
color: 'blue',
|
||||
fontWeight: 600,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const TabHeader: React.FC<TabHeaderProps> = ({ tab, active, ...rest }) => {
|
||||
return (
|
||||
<Box mr={2} py={3} {...getBorderProps(active)} {...rest}>
|
||||
<Text fontSize={0} {...getTextProps(active)} cursor="pointer">
|
||||
{tab.title}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
interface TabBodyProps extends BoxProps {
|
||||
tab: Tab;
|
||||
}
|
||||
|
||||
const TabBody: React.FC<TabBodyProps> = ({ tab }) => {
|
||||
return (
|
||||
<Box width="100%" px={3} py={3} borderTopWidth="1px" borderColor="gray.light">
|
||||
{tab.content}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export interface TabbedCardProps extends BoxProps {
|
||||
tabs: Tab[];
|
||||
}
|
||||
|
||||
export const TabbedCard: React.FC<TabbedCardProps> = ({ tabs, ...rest }) => {
|
||||
const [activeKey, setActiveKey] = useState(tabs[0].key);
|
||||
|
||||
const activeTab = tabs.find(tab => tab.key === activeKey);
|
||||
|
||||
const Header = tabs.map(tab => {
|
||||
if (tab.hide) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<TabHeader
|
||||
tab={tab}
|
||||
active={tab.key === activeKey}
|
||||
onClick={() => {
|
||||
setActiveKey(tab.key);
|
||||
}}
|
||||
key={tab.key}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Flex borderWidth="1px" borderColor="gray.light" borderRadius="8px" {...rest} wrap="wrap">
|
||||
<Box width="100%">
|
||||
<Flex px={3} width="100%">
|
||||
{Header}
|
||||
</Flex>
|
||||
</Box>
|
||||
{activeTab && (
|
||||
<Box width="100%">
|
||||
<Flex width="100%">
|
||||
<TabBody tab={activeTab} />
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
22
packages/app/src/components/transactions/testnet-banner.tsx
Normal file
22
packages/app/src/components/transactions/testnet-banner.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Box, Flex, Text } from '@blockstack/ui';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const BannerText = styled(Text)`
|
||||
font-size: 11px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
export const TestnetBanner: React.FC = ({ ...rest }) => {
|
||||
return (
|
||||
<Flex height="24px" bg="white" align="center" justify="space-between" {...rest}>
|
||||
<Flex width="100%" align="center">
|
||||
<Box textAlign="center" width="100%" backgroundColor="rgba(249, 161, 77, 0.12)">
|
||||
<BannerText color="orange">Testnet mode</BannerText>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
49
packages/app/src/components/transactions/tx-error.tsx
Normal file
49
packages/app/src/components/transactions/tx-error.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import { Screen, ScreenBody, ScreenActions } from '@blockstack/connect';
|
||||
import { Button, Text, Box, Flex, FailedIcon } from '@blockstack/ui';
|
||||
import { TestnetBanner } from './testnet-banner';
|
||||
|
||||
interface TxErrorProps {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const TxError: React.FC<TxErrorProps> = ({ message }) => {
|
||||
return (
|
||||
<>
|
||||
<Screen>
|
||||
{/* TODO: only show testnet banner if in testnet mode */}
|
||||
<TestnetBanner />
|
||||
<ScreenBody
|
||||
mt={6}
|
||||
body={[
|
||||
<Box textAlign="center">
|
||||
<Flex>
|
||||
<Box mx="auto" width="25%">
|
||||
<FailedIcon size={64} width={64} maxWidth="100%" />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Text display="block" textStyle="display.large" mb={3}>
|
||||
Transaction Failed
|
||||
</Text>
|
||||
<Text display="block" mb={6}>
|
||||
{message}
|
||||
</Text>
|
||||
</Box>,
|
||||
]}
|
||||
/>
|
||||
<ScreenActions>
|
||||
<Button
|
||||
width="100%"
|
||||
mt={6}
|
||||
size="lg"
|
||||
onClick={() => {
|
||||
window.close();
|
||||
}}
|
||||
>
|
||||
Return to App
|
||||
</Button>
|
||||
</ScreenActions>
|
||||
</Screen>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BlockstackProvider } from '@blockstack/connect/types';
|
||||
import { BlockstackProvider } from '@blockstack/connect';
|
||||
|
||||
interface Response {
|
||||
source: 'blockstack-extension';
|
||||
|
||||
@@ -8,9 +8,7 @@ import { useSelector } from 'react-redux';
|
||||
import { AppState, store } from '@store';
|
||||
import { selectAppName, selectDecodedAuthRequest } from '@store/onboarding/selectors';
|
||||
import { Drawer } from '@components/drawer';
|
||||
import { selectCurrentWallet, selectIdentities } from '@store/wallet/selectors';
|
||||
import { ConfigApp } from '@blockstack/keychain/wallet';
|
||||
import { Wallet } from '@blockstack/keychain';
|
||||
import { ConfigApp } from '@blockstack/keychain';
|
||||
import { gaiaUrl } from '@common/constants';
|
||||
import {
|
||||
CHOOSE_ACCOUNT_CHOSEN,
|
||||
@@ -21,6 +19,8 @@ import {
|
||||
} from '@common/track';
|
||||
import { useAnalytics } from '@common/hooks/use-analytics';
|
||||
import { ScreenPaths } from '@store/onboarding/types';
|
||||
import { useWallet } from '@common/hooks/use-wallet';
|
||||
import { Navigate } from '@components/navigate';
|
||||
|
||||
interface ChooseAccountProps {
|
||||
next: (identityIndex: number) => void;
|
||||
@@ -39,15 +39,18 @@ const SettingsButton = () => {
|
||||
};
|
||||
|
||||
export const ChooseAccount: React.FC<ChooseAccountProps> = ({ next }) => {
|
||||
const { appName, identities, wallet } = useSelector((state: AppState) => ({
|
||||
const { appName } = useSelector((state: AppState) => ({
|
||||
appName: selectAppName(state),
|
||||
identities: selectIdentities(state),
|
||||
wallet: selectCurrentWallet(state) as Wallet,
|
||||
}));
|
||||
const { wallet, identities } = useWallet();
|
||||
const [reusedApps, setReusedApps] = React.useState<ConfigApp[]>([]);
|
||||
const [identityIndex, setIdentityIndex] = React.useState<number | undefined>();
|
||||
const { doTrack } = useAnalytics();
|
||||
|
||||
if (!wallet) {
|
||||
return <Navigate to={{ pathname: '/', hash: 'sign-up' }} screenPath={ScreenPaths.GENERATION} />;
|
||||
}
|
||||
|
||||
// TODO: refactor into util, create unit tests
|
||||
const didSelectAccount = ({ identityIndex }: { identityIndex: number }) => {
|
||||
const state = store.getState();
|
||||
|
||||
228
packages/app/src/pages/transaction/index.tsx
Normal file
228
packages/app/src/pages/transaction/index.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Screen,
|
||||
ScreenBody,
|
||||
ScreenActions,
|
||||
Title,
|
||||
PoweredBy,
|
||||
ScreenFooter,
|
||||
TransactionPayload,
|
||||
TransactionTypes,
|
||||
} from '@blockstack/connect';
|
||||
import { ScreenHeader } from '@components/connected-screen-header';
|
||||
import { Button, Box, Text } from '@blockstack/ui';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { decodeToken } from 'jsontokens';
|
||||
import { useWallet } from '@common/hooks/use-wallet';
|
||||
import { TransactionVersion, StacksTransaction } from '@blockstack/stacks-transactions';
|
||||
import { TestnetBanner } from '@components/transactions/testnet-banner';
|
||||
import { TxError } from '@components/transactions/tx-error';
|
||||
import { TabbedCard, Tab } from '@components/tabbed-card';
|
||||
import { getRPCClient, stacksValue } from '@common/stacks-utils';
|
||||
import { Wallet } from '@blockstack/keychain';
|
||||
import { doTrack, TRANSACTION_SIGN_START, TRANSACTION_SIGN_ERROR } from '@common/track';
|
||||
import { finishTransaction, generateTransaction } from '@common/transaction-utils';
|
||||
|
||||
interface TabContentProps {
|
||||
json: any;
|
||||
}
|
||||
|
||||
const getInputJSON = (pendingTransaction: TransactionPayload | undefined, wallet: Wallet) => {
|
||||
if (pendingTransaction && wallet) {
|
||||
const { appDetails, publicKey, ...rest } = pendingTransaction;
|
||||
return {
|
||||
...rest,
|
||||
'tx-sender': wallet.getSigner().getSTXAddress(TransactionVersion.Testnet),
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const TabContent: React.FC<TabContentProps> = ({ json }) => {
|
||||
return (
|
||||
<Box whiteSpace="pre" overflow="scroll" color="gray" maxHeight="200px">
|
||||
{JSON.stringify(json, null, 2)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Transaction: React.FC = () => {
|
||||
const location = useLocation();
|
||||
const { wallet } = useWallet();
|
||||
const [pendingTransaction, setPendingTransaction] = useState<TransactionPayload | undefined>();
|
||||
const [signedTransaction, setSignedTransaction] = useState<StacksTransaction | undefined>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [contractSrc, setContractSrc] = useState('');
|
||||
const [balance, setBalance] = useState(0);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const client = getRPCClient();
|
||||
|
||||
if (!wallet) {
|
||||
throw new Error('User must be logged in.');
|
||||
}
|
||||
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
title: 'Inputs',
|
||||
content: <TabContent json={getInputJSON(pendingTransaction, wallet)} />,
|
||||
key: 'inputs',
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<>
|
||||
View Source
|
||||
{/* Add this icon when we can link to the explorer */}
|
||||
{/* <ExternalIcon display="inline-block" width="9px" ml={1} /> */}
|
||||
</>
|
||||
),
|
||||
content: (
|
||||
<Box whiteSpace="pre" overflow="scroll" color="gray" maxHeight="200px">
|
||||
{contractSrc}
|
||||
</Box>
|
||||
),
|
||||
key: 'source',
|
||||
hide: pendingTransaction?.txType === TransactionTypes.STXTransfer,
|
||||
},
|
||||
];
|
||||
|
||||
const setupAccountInfo = async () => {
|
||||
const account = await wallet.getSigner().fetchAccount({
|
||||
version: TransactionVersion.Testnet,
|
||||
rpcClient: client,
|
||||
});
|
||||
setBalance(account.balance.toNumber());
|
||||
return account;
|
||||
};
|
||||
|
||||
const setupWithState = async (tx: TransactionPayload) => {
|
||||
if (tx.txType === TransactionTypes.ContractCall) {
|
||||
const contractSource = await client.fetchContractSource({
|
||||
contractName: tx.contractName,
|
||||
contractAddress: tx.contractAddress,
|
||||
});
|
||||
if (contractSource) {
|
||||
setContractSrc(contractSource);
|
||||
setPendingTransaction(tx);
|
||||
} else {
|
||||
doTrack(TRANSACTION_SIGN_ERROR, {
|
||||
txType: pendingTransaction?.txType,
|
||||
appName: pendingTransaction?.appDetails?.name,
|
||||
error: 'Contract not found',
|
||||
});
|
||||
setError(`Unable to find contract ${tx.contractAddress}.${tx.contractName}`);
|
||||
}
|
||||
} else if (tx.txType === TransactionTypes.ContractDeploy) {
|
||||
console.log(tx);
|
||||
setContractSrc(tx.codeBody);
|
||||
setPendingTransaction(tx);
|
||||
} else if (tx.txType === TransactionTypes.STXTransfer) {
|
||||
setPendingTransaction(tx);
|
||||
}
|
||||
doTrack(TRANSACTION_SIGN_START, {
|
||||
txType: tx.txType,
|
||||
appName: tx.appDetails?.name,
|
||||
});
|
||||
return tx;
|
||||
};
|
||||
|
||||
const decodeRequest = async () => {
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
const requestToken = urlParams.get('request');
|
||||
if (requestToken) {
|
||||
const token = decodeToken(requestToken);
|
||||
const reqState = (token.payload as unknown) as TransactionPayload;
|
||||
try {
|
||||
const [txData, account] = await Promise.all([setupWithState(reqState), setupAccountInfo()]);
|
||||
const tx = await generateTransaction({
|
||||
wallet,
|
||||
nonce: account.nonce,
|
||||
txData,
|
||||
});
|
||||
setSignedTransaction(tx);
|
||||
} catch (error) {
|
||||
const nodeURL = new URL(client.url);
|
||||
setError(`Unable to connect to a Stacks node at ${nodeURL.hostname}`);
|
||||
}
|
||||
setLoading(false);
|
||||
} else {
|
||||
setError('Unable to decode request');
|
||||
console.error('Unable to find contract call parameter');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (wallet.stacksPrivateKey) {
|
||||
decodeRequest();
|
||||
}
|
||||
}, [wallet]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
if (!pendingTransaction || !signedTransaction) {
|
||||
// shouldn't be able to get here
|
||||
setError('Unable to finish transaction');
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
await finishTransaction({ tx: signedTransaction, pendingTransaction });
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return <TxError message={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Screen isLoading={loading}>
|
||||
{/* TODO: only show testnet banner if in testnet mode */}
|
||||
<TestnetBanner />
|
||||
<ScreenHeader
|
||||
rightContent={
|
||||
<Text textStyle="caption" color="gray" fontSize={0}>
|
||||
{stacksValue({ value: balance })} available
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<ScreenBody
|
||||
mt={6}
|
||||
body={[
|
||||
<Title>Confirm Transaction</Title>,
|
||||
<Text mt={2} display="inline-block">
|
||||
with {pendingTransaction?.appDetails?.name}
|
||||
</Text>,
|
||||
<TabbedCard mt={4} mb={4} tabs={tabs} />,
|
||||
<Box width="100%" mt={5}>
|
||||
<Text fontWeight={600}>
|
||||
<Text>Fee</Text>
|
||||
<Text style={{ float: 'right' }}>
|
||||
{stacksValue({
|
||||
value: signedTransaction?.auth.spendingCondition?.fee?.toNumber() || 0,
|
||||
})}
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>,
|
||||
]}
|
||||
/>
|
||||
<ScreenActions>
|
||||
<Button
|
||||
width="100%"
|
||||
mt={5}
|
||||
size="lg"
|
||||
onClick={async () => {
|
||||
await handleButtonClick();
|
||||
}}
|
||||
>
|
||||
Confirm Transaction
|
||||
</Button>
|
||||
</ScreenActions>
|
||||
<ScreenFooter>
|
||||
<PoweredBy />
|
||||
</ScreenFooter>
|
||||
</Screen>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -27,6 +27,7 @@ import { selectIdentities, selectCurrentWallet } from '@store/wallet/selectors';
|
||||
import { finalizeAuthResponse } from '@common/utils';
|
||||
import { gaiaUrl } from '@common/constants';
|
||||
import { doTrackScreenChange } from '@common/track';
|
||||
import { TransactionVersion } from '@blockstack/stacks-transactions';
|
||||
|
||||
export const doSetOnboardingProgress = (status: boolean): OnboardingActions => {
|
||||
return {
|
||||
@@ -163,7 +164,7 @@ export function doFinishSignIn(
|
||||
const currentIdentity = identities[identityIndex];
|
||||
await currentIdentity.refresh();
|
||||
const gaiaConfig = await wallet.createGaiaConfig(gaiaUrl);
|
||||
await wallet.getOrCreateConfig(gaiaConfig);
|
||||
await wallet.getOrCreateConfig({ gaiaConfig, skipUpload: true });
|
||||
await wallet.updateConfigWithAuth({
|
||||
identityIndex,
|
||||
gaiaConfig,
|
||||
@@ -175,11 +176,15 @@ export function doFinishSignIn(
|
||||
name: appName as string,
|
||||
},
|
||||
});
|
||||
const stxAddress = wallet.stacksPrivateKey
|
||||
? wallet.getSigner().getSTXAddress(TransactionVersion.Testnet)
|
||||
: undefined;
|
||||
const authResponse = await currentIdentity.makeAuthResponse({
|
||||
gaiaUrl,
|
||||
appDomain: appURL.origin,
|
||||
transitPublicKey: decodedAuthRequest.public_keys[0],
|
||||
scopes: decodedAuthRequest.scopes,
|
||||
stxAddress,
|
||||
});
|
||||
finalizeAuthResponse({ decodedAuthRequest, authRequest, authResponse });
|
||||
dispatch(doSetOnboardingPath(undefined));
|
||||
|
||||
@@ -41,7 +41,7 @@ export function doStoreSeed(
|
||||
): ThunkAction<Promise<Wallet>, {}, {}, WalletActions> {
|
||||
return async dispatch => {
|
||||
dispatch(isRestoringWallet());
|
||||
const wallet = await Wallet.restore(password, secretKey, ChainID.Testnet);
|
||||
const wallet = await Wallet.restore(password, secretKey, ChainID.Mainnet);
|
||||
dispatch(didRestoreWallet(wallet));
|
||||
return wallet;
|
||||
};
|
||||
@@ -52,7 +52,7 @@ export function doGenerateWallet(
|
||||
): ThunkAction<Promise<Wallet>, {}, {}, WalletActions> {
|
||||
return async dispatch => {
|
||||
dispatch(isRestoringWallet());
|
||||
const wallet = await Wallet.generate(password, ChainID.Testnet);
|
||||
const wallet = await Wallet.generate(password, ChainID.Mainnet);
|
||||
dispatch(didGenerateWallet(wallet));
|
||||
return wallet;
|
||||
};
|
||||
|
||||
@@ -36,7 +36,6 @@ export const debug = async (page: Page) => {
|
||||
CONTROL_D: '\u0004',
|
||||
ENTER: '\r',
|
||||
};
|
||||
|
||||
console.log('\n\n🕵️ Code is paused, press enter to resume');
|
||||
// Run an infinite promise
|
||||
return new Promise(resolve => {
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
"@containers/*": ["containers/*"],
|
||||
"@common/*": ["common/*"],
|
||||
"@blockstack/connect": ["../../connect/src"],
|
||||
"@blockstack/connect/*": ["../../connect/src/*"],
|
||||
"@blockstack/ui": ["../../ui/src"],
|
||||
"@blockstack/keychain": ["../../keychain/src"],
|
||||
"@blockstack/keychain/*": ["../../keychain/src/*"]
|
||||
"@blockstack/rpc-client": ["../../rpc-client/src"],
|
||||
"@pages/*": ["pages/*"]
|
||||
},
|
||||
"baseUrl": "src",
|
||||
"allowSyntheticDefaultImports": true
|
||||
|
||||
@@ -185,6 +185,10 @@ module.exports = {
|
||||
contentBase: './dist',
|
||||
historyApiFallback: true,
|
||||
},
|
||||
node: {
|
||||
Buffer: true,
|
||||
BufferReader: true,
|
||||
},
|
||||
devtool: getSourceMap(),
|
||||
watch: false,
|
||||
plugins: [
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@blockstack/ui": "^2.9.5",
|
||||
"jsontokens": "^3.0.0",
|
||||
"preact": "^10.4.0",
|
||||
"prettier": "^2.0.5",
|
||||
"styled-components": "^5.1.0",
|
||||
"use-events": "^1.4.1",
|
||||
"use-onclickoutside": "^0.3.1"
|
||||
@@ -49,17 +49,16 @@
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"blockstack": "^19.3.0",
|
||||
"blockstack": "21.0.0",
|
||||
"bundlesize": "^0.18.0",
|
||||
"husky": "^4.2.1",
|
||||
"prettier": "^2.0.4",
|
||||
"prettier": "^2.0.5",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.2",
|
||||
"terser-webpack-plugin": "^2.3.5",
|
||||
"tsdx": "^0.12.3",
|
||||
"tslib": "^1.10.0",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-bundle-analyzer": "^3.6.0",
|
||||
"webpack-cli": "^3.3.10"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { UserSession, AppConfig } from 'blockstack';
|
||||
import './types';
|
||||
import { popupCenter } from './popup';
|
||||
import { popupCenter, setupListener } from './popup';
|
||||
|
||||
const defaultAuthURL = 'https://app.blockstack.org';
|
||||
export const defaultAuthURL = 'https://app.blockstack.org';
|
||||
|
||||
export interface FinishedData {
|
||||
authResponse: string;
|
||||
@@ -86,7 +86,7 @@ export const authenticate = async ({
|
||||
skipPopupFallback: !!window.BlockstackProvider,
|
||||
});
|
||||
|
||||
setupListener({
|
||||
setupAuthListener({
|
||||
popup,
|
||||
authRequest,
|
||||
onFinish: onFinish || finished,
|
||||
@@ -111,7 +111,7 @@ interface ListenerParams {
|
||||
userSession: UserSession;
|
||||
}
|
||||
|
||||
const setupListener = ({
|
||||
const setupAuthListener = ({
|
||||
popup,
|
||||
authRequest,
|
||||
onFinish,
|
||||
@@ -119,55 +119,24 @@ const setupListener = ({
|
||||
authURL,
|
||||
userSession,
|
||||
}: ListenerParams) => {
|
||||
let lastPong: number | null = null;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (popup) {
|
||||
try {
|
||||
popup.postMessage(
|
||||
{
|
||||
authRequest,
|
||||
method: 'ping',
|
||||
},
|
||||
authURL.origin
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('[Blockstack] Unable to send ping to authentication service');
|
||||
clearInterval(interval);
|
||||
setupListener<FinishedEventData>({
|
||||
popup,
|
||||
onCancel,
|
||||
onFinish: async (data: FinishedEventData) => {
|
||||
if (data.authRequest === authRequest) {
|
||||
if (onFinish) {
|
||||
const { authResponse } = data;
|
||||
await userSession.handlePendingSignIn(authResponse);
|
||||
onFinish({
|
||||
authResponse,
|
||||
userSession,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastPong && new Date().getTime() - lastPong > 200) {
|
||||
onCancel && onCancel();
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
const receiveMessage = async (event: MessageEvent) => {
|
||||
const authRequestMatch = event.data.authRequest === authRequest;
|
||||
if (!authRequestMatch) {
|
||||
return;
|
||||
}
|
||||
if (event.data.method === 'pong') {
|
||||
lastPong = new Date().getTime();
|
||||
} else {
|
||||
const data: FinishedEventData = event.data;
|
||||
if (onFinish) {
|
||||
window.focus();
|
||||
const { authResponse } = data;
|
||||
await userSession.handlePendingSignIn(authResponse);
|
||||
onFinish({
|
||||
authResponse,
|
||||
userSession,
|
||||
});
|
||||
}
|
||||
window.removeEventListener('message', receiveMessageCallback);
|
||||
clearInterval(interval);
|
||||
}
|
||||
};
|
||||
|
||||
const receiveMessageCallback = (event: MessageEvent) => {
|
||||
void receiveMessage(event);
|
||||
};
|
||||
|
||||
window.addEventListener('message', receiveMessageCallback, false);
|
||||
},
|
||||
messageParams: {
|
||||
authRequest,
|
||||
},
|
||||
authURL,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from './react';
|
||||
export * from './popup';
|
||||
export * from './types';
|
||||
export * from './ui';
|
||||
export * from './transactions';
|
||||
|
||||
@@ -73,3 +73,70 @@ export const popupCenter = ({
|
||||
}
|
||||
return window.open(url);
|
||||
};
|
||||
|
||||
interface ListenerParams<FinishedType> {
|
||||
popup: Window | null;
|
||||
messageParams: {
|
||||
[key: string]: any;
|
||||
};
|
||||
onFinish: (payload: FinishedType) => void | Promise<void>;
|
||||
onCancel?: () => void;
|
||||
authURL: URL;
|
||||
}
|
||||
|
||||
export const setupListener = <T>({
|
||||
popup,
|
||||
messageParams,
|
||||
onFinish,
|
||||
onCancel,
|
||||
authURL,
|
||||
}: ListenerParams<T>) => {
|
||||
let lastPong: number | null = null;
|
||||
|
||||
// Send a message to the authenticator popup at a consistent interval. This allows
|
||||
// the authenticator to 'respond'.
|
||||
const pingInterval = 250;
|
||||
const interval = setInterval(() => {
|
||||
if (popup) {
|
||||
try {
|
||||
console.log('about to ping');
|
||||
popup.postMessage(
|
||||
{
|
||||
method: 'ping',
|
||||
...messageParams,
|
||||
},
|
||||
authURL.origin
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('[Blockstack] Unable to send ping to authentication service');
|
||||
clearInterval(interval);
|
||||
}
|
||||
} else {
|
||||
console.warn('[Blockstack] Unable to send ping to authentication service - popup closed');
|
||||
}
|
||||
if (lastPong && new Date().getTime() - lastPong > pingInterval * 2) {
|
||||
onCancel && onCancel();
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, pingInterval);
|
||||
|
||||
const receiveMessage = async (event: MessageEvent) => {
|
||||
if (event.data.method === 'pong') {
|
||||
lastPong = new Date().getTime();
|
||||
return;
|
||||
}
|
||||
if (event.data.source === 'blockstack-app') {
|
||||
const data = event.data as T;
|
||||
await onFinish(data);
|
||||
window.focus();
|
||||
window.removeEventListener('message', receiveMessageCallback);
|
||||
clearInterval(interval);
|
||||
}
|
||||
};
|
||||
|
||||
const receiveMessageCallback = (event: MessageEvent) => {
|
||||
void receiveMessage(event);
|
||||
};
|
||||
|
||||
window.addEventListener('message', receiveMessageCallback, false);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useReducer, createContext } from 'react';
|
||||
import { AuthOptions, FinishedData } from '../../../auth';
|
||||
import { UserSession } from 'blockstack/lib';
|
||||
|
||||
enum States {
|
||||
MODAL_OPEN = 'modal/open',
|
||||
@@ -23,6 +24,7 @@ type State = {
|
||||
screen: string;
|
||||
authData?: FinishedData;
|
||||
authOptions: AuthOptions;
|
||||
userSession?: UserSession;
|
||||
};
|
||||
|
||||
const initialState: State = {
|
||||
@@ -30,6 +32,7 @@ const initialState: State = {
|
||||
isAuthenticating: false,
|
||||
screen: States.SCREENS_INTRO,
|
||||
authData: undefined,
|
||||
userSession: undefined,
|
||||
authOptions: {
|
||||
redirectTo: '',
|
||||
manifestPath: '',
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { useContext } from 'react';
|
||||
import { authenticate, AuthOptions, FinishedData } from '../../auth';
|
||||
import { openContractCall, openContractDeploy, openSTXTransfer } from '../../transactions';
|
||||
import {
|
||||
ContractCallOptions,
|
||||
ContractDeployOptions,
|
||||
STXTransferOptions,
|
||||
} from '../../transactions/types';
|
||||
import { ConnectContext, ConnectDispatchContext, States } from '../components/connect/context';
|
||||
|
||||
const useConnectDispatch = () => {
|
||||
@@ -11,7 +17,9 @@ const useConnectDispatch = () => {
|
||||
};
|
||||
|
||||
export const useConnect = () => {
|
||||
const { isOpen, isAuthenticating, authData, screen, authOptions } = useContext(ConnectContext);
|
||||
const { isOpen, isAuthenticating, authData, screen, authOptions, userSession } = useContext(
|
||||
ConnectContext
|
||||
);
|
||||
const dispatch = useConnectDispatch();
|
||||
|
||||
const doUpdateAuthOptions = (payload: Partial<AuthOptions>) => {
|
||||
@@ -64,12 +72,31 @@ export const useConnect = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const doContractCall = async (opts: ContractCallOptions) =>
|
||||
openContractCall({
|
||||
...opts,
|
||||
appDetails: authOptions.appDetails,
|
||||
});
|
||||
|
||||
const doContractDeploy = async (opts: ContractDeployOptions) =>
|
||||
openContractDeploy({
|
||||
...opts,
|
||||
appDetails: authOptions.appDetails,
|
||||
});
|
||||
|
||||
const doSTXTransfer = async (opts: STXTransferOptions) =>
|
||||
openSTXTransfer({
|
||||
...opts,
|
||||
appDetails: authOptions.appDetails,
|
||||
});
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
isAuthenticating,
|
||||
authData,
|
||||
authOptions,
|
||||
screen,
|
||||
userSession,
|
||||
doOpenAuth,
|
||||
doCloseAuth,
|
||||
doChangeScreen,
|
||||
@@ -81,5 +108,8 @@ export const useConnect = () => {
|
||||
doFinishAuth,
|
||||
doAuth,
|
||||
authenticate,
|
||||
doContractCall,
|
||||
doContractDeploy,
|
||||
doSTXTransfer,
|
||||
};
|
||||
};
|
||||
|
||||
144
packages/connect/src/transactions/index.ts
Normal file
144
packages/connect/src/transactions/index.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { UserSession, AppConfig } from 'blockstack';
|
||||
import { SECP256K1Client, TokenSigner } from 'jsontokens';
|
||||
import { defaultAuthURL } from '../auth';
|
||||
import { popupCenter, setupListener } from '../popup';
|
||||
import {
|
||||
ContractCallOptions,
|
||||
ContractCallPayload,
|
||||
ContractDeployOptions,
|
||||
ContractDeployPayload,
|
||||
FinishedTxData,
|
||||
TransactionPopup,
|
||||
TransactionOptions,
|
||||
STXTransferOptions,
|
||||
STXTransferPayload,
|
||||
TransactionPayload,
|
||||
TransactionTypes,
|
||||
} from './types';
|
||||
import { serializeCV } from '@blockstack/stacks-transactions';
|
||||
|
||||
export * from './types';
|
||||
|
||||
const getKeys = (_userSession?: UserSession) => {
|
||||
let userSession = _userSession;
|
||||
|
||||
if (!userSession) {
|
||||
const appConfig = new AppConfig(['store_write'], document.location.href);
|
||||
userSession = new UserSession({ appConfig });
|
||||
}
|
||||
|
||||
const privateKey = userSession.loadUserData().appPrivateKey;
|
||||
const publicKey = SECP256K1Client.derivePublicKey(privateKey);
|
||||
|
||||
return { privateKey, publicKey };
|
||||
};
|
||||
|
||||
const signPayload = async (payload: TransactionPayload, privateKey: string) => {
|
||||
const tokenSigner = new TokenSigner('ES256k', privateKey);
|
||||
return tokenSigner.signAsync(payload as any);
|
||||
};
|
||||
|
||||
const openTransactionPopup = async ({ token, opts }: TransactionPopup) => {
|
||||
const extensionURL = await window.BlockstackProvider?.getURL();
|
||||
const authURL = new URL(extensionURL || opts.authOrigin || defaultAuthURL);
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set('request', token);
|
||||
|
||||
const popup = popupCenter({
|
||||
url: `${authURL.origin}/#/transaction?${urlParams.toString()}`,
|
||||
h: 700,
|
||||
});
|
||||
|
||||
setupListener<FinishedTxData>({
|
||||
popup,
|
||||
authURL,
|
||||
onFinish: data => {
|
||||
if (opts.finished) {
|
||||
opts.finished(data);
|
||||
}
|
||||
},
|
||||
messageParams: {},
|
||||
});
|
||||
return popup;
|
||||
};
|
||||
|
||||
export const makeContractCallToken = async (opts: ContractCallOptions) => {
|
||||
const { contractAddress, functionName, contractName, functionArgs, appDetails } = opts;
|
||||
const { privateKey, publicKey } = getKeys(opts.userSession);
|
||||
|
||||
const args: string[] = functionArgs.map(arg => {
|
||||
if (typeof arg === 'string') {
|
||||
return arg;
|
||||
}
|
||||
return serializeCV(arg).toString('hex');
|
||||
});
|
||||
|
||||
const payload: ContractCallPayload = {
|
||||
contractAddress,
|
||||
contractName,
|
||||
functionName,
|
||||
functionArgs: args,
|
||||
txType: TransactionTypes.ContractCall,
|
||||
publicKey,
|
||||
};
|
||||
|
||||
if (appDetails) {
|
||||
payload.appDetails = appDetails;
|
||||
}
|
||||
|
||||
return signPayload(payload, privateKey);
|
||||
};
|
||||
|
||||
export const makeContractDeployToken = async (opts: ContractDeployOptions) => {
|
||||
const { contractName, codeBody, appDetails } = opts;
|
||||
const { privateKey, publicKey } = getKeys(opts.userSession);
|
||||
|
||||
const payload: ContractDeployPayload = {
|
||||
contractName,
|
||||
codeBody,
|
||||
publicKey,
|
||||
txType: TransactionTypes.ContractDeploy,
|
||||
};
|
||||
|
||||
if (appDetails) {
|
||||
payload.appDetails = appDetails;
|
||||
}
|
||||
|
||||
return signPayload(payload, privateKey);
|
||||
};
|
||||
|
||||
export const makeSTXTransferToken = async (opts: STXTransferOptions) => {
|
||||
const { amount, recipient, memo, appDetails } = opts;
|
||||
const { privateKey, publicKey } = getKeys(opts.userSession);
|
||||
|
||||
const payload: STXTransferPayload = {
|
||||
amount: amount.toString(10),
|
||||
recipient,
|
||||
memo,
|
||||
publicKey,
|
||||
txType: TransactionTypes.STXTransfer,
|
||||
};
|
||||
|
||||
if (appDetails) {
|
||||
payload.appDetails = appDetails;
|
||||
}
|
||||
|
||||
return signPayload(payload, privateKey);
|
||||
};
|
||||
|
||||
async function generateTokenAndOpenPopup<T extends TransactionOptions>(
|
||||
opts: T,
|
||||
makeTokenFn: (opts: T) => Promise<string>
|
||||
) {
|
||||
const token = await makeTokenFn(opts);
|
||||
return openTransactionPopup({ token, opts });
|
||||
}
|
||||
|
||||
export const openContractCall = async (opts: ContractCallOptions) =>
|
||||
generateTokenAndOpenPopup(opts, makeContractCallToken);
|
||||
|
||||
export const openContractDeploy = async (opts: ContractDeployOptions) =>
|
||||
generateTokenAndOpenPopup(opts, makeContractDeployToken);
|
||||
|
||||
export const openSTXTransfer = async (opts: STXTransferOptions) =>
|
||||
generateTokenAndOpenPopup(opts, makeSTXTransferToken);
|
||||
120
packages/connect/src/transactions/types.ts
Normal file
120
packages/connect/src/transactions/types.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { UserSession } from 'blockstack';
|
||||
import { AuthOptions } from '../auth';
|
||||
import {
|
||||
PostConditionMode,
|
||||
PostCondition,
|
||||
StacksNetwork,
|
||||
AnchorMode,
|
||||
ClarityValue,
|
||||
} from '@blockstack/stacks-transactions';
|
||||
import BN from 'bn.js';
|
||||
|
||||
export interface TxBase {
|
||||
appDetails?: AuthOptions['appDetails'];
|
||||
postConditionMode?: PostConditionMode;
|
||||
postConditions?: PostCondition[];
|
||||
network?: StacksNetwork;
|
||||
anchorMode?: AnchorMode;
|
||||
senderKey?: string;
|
||||
nonce?: number;
|
||||
}
|
||||
|
||||
export interface FinishedTxData {
|
||||
txId: string;
|
||||
txRaw: string;
|
||||
}
|
||||
|
||||
export enum TransactionTypes {
|
||||
ContractCall = 'contract_call',
|
||||
ContractDeploy = 'smart_contract',
|
||||
STXTransfer = 'token_transfer',
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract Call
|
||||
*/
|
||||
|
||||
export enum ContractCallArgumentType {
|
||||
BUFFER = 'buffer',
|
||||
UINT = 'uint',
|
||||
INT = 'int',
|
||||
PRINCIPAL = 'principal',
|
||||
BOOL = 'bool',
|
||||
}
|
||||
|
||||
export interface ContractCallBase extends TxBase {
|
||||
contractAddress: string;
|
||||
contractName: string;
|
||||
functionName: string;
|
||||
functionArgs: (string | ClarityValue)[];
|
||||
}
|
||||
|
||||
export interface ContractCallOptions extends ContractCallBase {
|
||||
authOrigin?: string;
|
||||
userSession?: UserSession;
|
||||
finished?: (data: FinishedTxData) => void;
|
||||
}
|
||||
|
||||
export interface ContractCallArgument {
|
||||
type: ContractCallArgumentType;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ContractCallPayload extends ContractCallBase {
|
||||
txType: TransactionTypes.ContractCall;
|
||||
publicKey: string;
|
||||
functionArgs: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract Deploy
|
||||
*/
|
||||
export interface ContractDeployBase extends TxBase {
|
||||
contractName: string;
|
||||
codeBody: string;
|
||||
}
|
||||
|
||||
export interface ContractDeployOptions extends ContractDeployBase {
|
||||
authOrigin?: string;
|
||||
userSession?: UserSession;
|
||||
finished?: (data: FinishedTxData) => void;
|
||||
}
|
||||
|
||||
export interface ContractDeployPayload extends ContractDeployOptions {
|
||||
publicKey: string;
|
||||
txType: TransactionTypes.ContractDeploy;
|
||||
}
|
||||
|
||||
/**
|
||||
* STX Transfer
|
||||
*/
|
||||
|
||||
export interface STXTransferBase extends TxBase {
|
||||
recipient: string;
|
||||
amount: BN | string;
|
||||
memo?: string;
|
||||
}
|
||||
|
||||
export interface STXTransferOptions extends STXTransferBase {
|
||||
authOrigin?: string;
|
||||
userSession?: UserSession;
|
||||
finished?: (data: FinishedTxData) => void;
|
||||
}
|
||||
|
||||
export interface STXTransferPayload extends STXTransferOptions {
|
||||
publicKey: string;
|
||||
txType: TransactionTypes.STXTransfer;
|
||||
amount: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction Popup
|
||||
*/
|
||||
|
||||
export type TransactionOptions = ContractCallOptions | ContractDeployOptions | STXTransferOptions;
|
||||
export type TransactionPayload = ContractCallPayload | ContractDeployPayload | STXTransferPayload;
|
||||
|
||||
export interface TransactionPopup {
|
||||
token: string;
|
||||
opts: TransactionOptions;
|
||||
}
|
||||
@@ -38,6 +38,7 @@
|
||||
"@babel/compat-data": "^7.10.1",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||
"@blockstack/prettier-config": "^0.0.6",
|
||||
"@types/bn.js": "^4.11.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/node": "^13.13.10",
|
||||
"@types/triplesec": "^3.0.0",
|
||||
@@ -55,10 +56,12 @@
|
||||
"typescript": "^3.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blockstack/stacks-transactions": "^0.4.6",
|
||||
"@blockstack/rpc-client": "^0.3.0-alpha.0",
|
||||
"@blockstack/stacks-transactions": "0.5.1",
|
||||
"bip39": "^3.0.2",
|
||||
"bitcoinjs-lib": "^5.1.6",
|
||||
"blockstack": "21.0.0-alpha.2",
|
||||
"blockstack": "21.0.0",
|
||||
"bn.js": "^5.1.1",
|
||||
"c32check": "^1.0.1",
|
||||
"jsontokens": "^3.0.0",
|
||||
"prettier": "^2.0.5",
|
||||
|
||||
@@ -51,13 +51,15 @@ export class Identity {
|
||||
gaiaUrl,
|
||||
transitPublicKey,
|
||||
scopes = [],
|
||||
stxAddress,
|
||||
}: {
|
||||
appDomain: string;
|
||||
gaiaUrl: string;
|
||||
transitPublicKey: string;
|
||||
scopes?: string[];
|
||||
stxAddress?: string;
|
||||
}) {
|
||||
const appPrivateKey = await this.appPrivateKey(appDomain);
|
||||
const appPrivateKey = this.appPrivateKey(appDomain);
|
||||
const hubInfo = await getHubInfo(gaiaUrl);
|
||||
const profileUrl = await this.profileUrl(hubInfo.read_url_prefix);
|
||||
const profile =
|
||||
@@ -67,7 +69,7 @@ export class Identity {
|
||||
profile.apps = {};
|
||||
}
|
||||
const challengeSigner = ECPair.fromPrivateKey(Buffer.from(appPrivateKey, 'hex'));
|
||||
const storageUrl = `${hubInfo.read_url_prefix}${await ecPairToAddress(challengeSigner)}/`;
|
||||
const storageUrl = `${hubInfo.read_url_prefix}${ecPairToAddress(challengeSigner)}/`;
|
||||
profile.apps[appDomain] = storageUrl;
|
||||
if (!profile.appsMeta) {
|
||||
profile.appsMeta = {};
|
||||
@@ -76,7 +78,7 @@ export class Identity {
|
||||
storage: storageUrl,
|
||||
publicKey: challengeSigner.publicKey.toString('hex'),
|
||||
};
|
||||
const gaiaHubConfig = await connectToGaiaHubWithConfig({
|
||||
const gaiaHubConfig = connectToGaiaHubWithConfig({
|
||||
hubInfo,
|
||||
privateKey: this.keyPair.key,
|
||||
gaiaHubUrl: gaiaUrl,
|
||||
@@ -90,7 +92,10 @@ export class Identity {
|
||||
|
||||
return makeAuthResponse(
|
||||
this.keyPair.key,
|
||||
this.profile || {},
|
||||
{
|
||||
...(this.profile || {}),
|
||||
stxAddress,
|
||||
},
|
||||
this.defaultUsername || '',
|
||||
{
|
||||
profileUrl,
|
||||
@@ -105,7 +110,7 @@ export class Identity {
|
||||
);
|
||||
}
|
||||
|
||||
async appPrivateKey(appDomain: string) {
|
||||
appPrivateKey(appDomain: string) {
|
||||
const { salt, appsNodeKey } = this.keyPair;
|
||||
const appsNode = new IdentityAddressOwnerNode(bip32.fromBase58(appsNodeKey), salt);
|
||||
return appsNode.getAppPrivateKey(appDomain);
|
||||
|
||||
@@ -3,6 +3,8 @@ export * from './utils';
|
||||
export * from './mnemonic';
|
||||
export * from './address-derivation';
|
||||
export { default as Wallet } from './wallet';
|
||||
export * from './wallet';
|
||||
export * from './wallet/signer';
|
||||
export { decrypt } from './encryption/decrypt';
|
||||
export { encrypt } from './encryption/encrypt';
|
||||
export * from './profiles';
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getAddress } from '../utils';
|
||||
const APPS_NODE_INDEX = 0;
|
||||
const SIGNING_NODE_INDEX = 1;
|
||||
const ENCRYPTION_NODE_INDEX = 2;
|
||||
const STX_NODE_INDEX = 6;
|
||||
|
||||
export default class IdentityAddressOwnerNode {
|
||||
hdNode: BIP32Interface;
|
||||
@@ -40,7 +41,7 @@ export default class IdentityAddressOwnerNode {
|
||||
return this.hdNode.deriveHardened(APPS_NODE_INDEX);
|
||||
}
|
||||
|
||||
async getAddress() {
|
||||
getAddress() {
|
||||
return getAddress(this.hdNode);
|
||||
}
|
||||
|
||||
@@ -52,20 +53,24 @@ export default class IdentityAddressOwnerNode {
|
||||
return this.hdNode.deriveHardened(SIGNING_NODE_INDEX);
|
||||
}
|
||||
|
||||
async getAppNode(appDomain: string) {
|
||||
getSTXNode() {
|
||||
return this.hdNode.deriveHardened(STX_NODE_INDEX);
|
||||
}
|
||||
|
||||
getAppNode(appDomain: string) {
|
||||
return getLegacyAppNode(this.hdNode, this.salt, appDomain);
|
||||
}
|
||||
|
||||
async getAppPrivateKey(appDomain: string) {
|
||||
const appNode = await this.getAppNode(appDomain);
|
||||
getAppPrivateKey(appDomain: string) {
|
||||
const appNode = this.getAppNode(appDomain);
|
||||
if (!appNode.privateKey) {
|
||||
throw new Error('App node does not have private key');
|
||||
}
|
||||
return appNode.privateKey.toString('hex');
|
||||
}
|
||||
|
||||
async getAppAddress(appDomain: string) {
|
||||
const appNode = await this.getAppNode(appDomain);
|
||||
getAppAddress(appDomain: string) {
|
||||
const appNode = this.getAppNode(appDomain);
|
||||
return publicKeyToAddress(appNode.publicKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ import {
|
||||
makeProfileZoneFile,
|
||||
} from 'blockstack';
|
||||
import { IdentityKeyPair } from './utils';
|
||||
import { uploadToGaiaHub } from './utils/gaia';
|
||||
import Identity from './identity';
|
||||
import { uploadToGaiaHub, GaiaHubConfig } from 'blockstack/lib/storage/hub';
|
||||
import { GaiaHubConfig } from 'blockstack/lib/storage/hub';
|
||||
|
||||
const PERSON_TYPE = 'Person';
|
||||
const CONTEXT = 'http://schema.org';
|
||||
@@ -58,11 +59,11 @@ export const registrars = {
|
||||
},
|
||||
};
|
||||
|
||||
export async function signProfileForUpload(profile: any, keypair: IdentityKeyPair) {
|
||||
export function signProfileForUpload(profile: Profile, keypair: IdentityKeyPair) {
|
||||
const privateKey = keypair.key;
|
||||
const publicKey = keypair.keyID;
|
||||
|
||||
const token = await signProfileToken(profile, privateKey, { publicKey });
|
||||
const token = signProfileToken(profile, privateKey, { publicKey });
|
||||
const tokenRecord = wrapProfileToken(token);
|
||||
const tokenRecords = [tokenRecord];
|
||||
return JSON.stringify(tokenRecords, null, 2);
|
||||
@@ -77,12 +78,12 @@ export async function uploadProfile(
|
||||
const identityHubConfig =
|
||||
gaiaHubConfig || (await connectToGaiaHub(gaiaHubUrl, identity.keyPair.key));
|
||||
|
||||
return uploadToGaiaHub(
|
||||
const uploadResponse = await uploadToGaiaHub(
|
||||
DEFAULT_PROFILE_FILE_NAME,
|
||||
signedProfileTokenData,
|
||||
identityHubConfig,
|
||||
'application/json'
|
||||
identityHubConfig
|
||||
);
|
||||
return uploadResponse;
|
||||
}
|
||||
|
||||
interface SendToRegistrarParams {
|
||||
@@ -146,9 +147,8 @@ export const registerSubdomain = async ({
|
||||
username,
|
||||
subdomain,
|
||||
}: RegisterParams) => {
|
||||
// const profile = identity.profile || DEFAULT_PROFILE
|
||||
const profile = DEFAULT_PROFILE;
|
||||
const signedProfileTokenData = await signProfileForUpload(profile, identity.keyPair);
|
||||
const profile = identity.profile || DEFAULT_PROFILE;
|
||||
const signedProfileTokenData = signProfileForUpload(profile, identity.keyPair);
|
||||
const profileUrl = await uploadProfile(gaiaHubUrl, identity, signedProfileTokenData);
|
||||
const fullUsername = `${username}.${subdomain}`;
|
||||
const zoneFile = makeProfileZoneFile(fullUsername, profileUrl);
|
||||
@@ -158,7 +158,6 @@ export const registerSubdomain = async ({
|
||||
zoneFile,
|
||||
identity,
|
||||
});
|
||||
|
||||
identity.defaultUsername = fullUsername;
|
||||
identity.usernames.push(fullUsername);
|
||||
return identity;
|
||||
@@ -175,7 +174,7 @@ export const signAndUploadProfile = async ({
|
||||
identity: Identity;
|
||||
gaiaHubConfig?: GaiaHubConfig;
|
||||
}) => {
|
||||
const signedProfileTokenData = await signProfileForUpload(profile, identity.keyPair);
|
||||
const signedProfileTokenData = signProfileForUpload(profile, identity.keyPair);
|
||||
await uploadProfile(gaiaHubUrl, identity, signedProfileTokenData, gaiaHubConfig);
|
||||
};
|
||||
|
||||
|
||||
@@ -49,18 +49,19 @@ interface ConnectToGaiaOptions {
|
||||
gaiaHubUrl: string;
|
||||
}
|
||||
|
||||
export const connectToGaiaHubWithConfig = async ({
|
||||
export const connectToGaiaHubWithConfig = ({
|
||||
hubInfo,
|
||||
privateKey,
|
||||
gaiaHubUrl,
|
||||
}: ConnectToGaiaOptions): Promise<GaiaHubConfig> => {
|
||||
}: ConnectToGaiaOptions): GaiaHubConfig => {
|
||||
const readURL = hubInfo.read_url_prefix;
|
||||
const token = makeGaiaAuthToken({ hubInfo, privateKey, gaiaHubUrl });
|
||||
const address = await ecPairToAddress(
|
||||
const address = ecPairToAddress(
|
||||
hexStringToECPair(privateKey + (privateKey.length === 64 ? '01' : ''))
|
||||
);
|
||||
return {
|
||||
url_prefix: readURL,
|
||||
max_file_upload_size_megabytes: 100,
|
||||
address,
|
||||
token,
|
||||
server: gaiaHubUrl,
|
||||
@@ -75,15 +76,16 @@ interface ReadOnlyGaiaConfigOptions {
|
||||
/**
|
||||
* When you already know the Gaia read URL, make a Gaia config that doesn't have to fetch `/hub_info`
|
||||
*/
|
||||
export const makeReadOnlyGaiaConfig = async ({
|
||||
export const makeReadOnlyGaiaConfig = ({
|
||||
readURL,
|
||||
privateKey,
|
||||
}: ReadOnlyGaiaConfigOptions): Promise<GaiaHubConfig> => {
|
||||
const address = await ecPairToAddress(
|
||||
}: ReadOnlyGaiaConfigOptions): GaiaHubConfig => {
|
||||
const address = ecPairToAddress(
|
||||
hexStringToECPair(privateKey + (privateKey.length === 64 ? '01' : ''))
|
||||
);
|
||||
return {
|
||||
url_prefix: readURL,
|
||||
max_file_upload_size_megabytes: 100,
|
||||
address,
|
||||
token: 'not_used',
|
||||
server: 'not_used',
|
||||
@@ -113,3 +115,24 @@ const makeGaiaAuthToken = ({ hubInfo, privateKey, gaiaHubUrl }: ConnectToGaiaOpt
|
||||
const token = new TokenSigner('ES256K', privateKey).sign(payload);
|
||||
return `v1:${token}`;
|
||||
};
|
||||
|
||||
export const uploadToGaiaHub = async (
|
||||
filename: string,
|
||||
contents: Blob | Buffer | ArrayBufferView | string,
|
||||
hubConfig: GaiaHubConfig
|
||||
): Promise<string> => {
|
||||
const contentType = 'application/json';
|
||||
|
||||
const response = await fetch(`${hubConfig.server}/store/${hubConfig.address}/${filename}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
Authorization: `bearer ${hubConfig.token}`,
|
||||
},
|
||||
body: contents,
|
||||
referrer: 'no-referrer',
|
||||
referrerPolicy: 'no-referrer',
|
||||
});
|
||||
const { publicURL } = await response.json();
|
||||
return publicURL;
|
||||
};
|
||||
|
||||
@@ -62,7 +62,7 @@ export async function getIdentityOwnerAddressNode(
|
||||
return new IdentityAddressOwnerNode(identityPrivateKeychain.deriveHardened(identityIndex), salt);
|
||||
}
|
||||
|
||||
export async function getAddress(node: BIP32Interface) {
|
||||
export function getAddress(node: BIP32Interface) {
|
||||
return publicKeyToAddress(node.publicKey);
|
||||
}
|
||||
|
||||
@@ -71,21 +71,24 @@ export interface IdentityKeyPair {
|
||||
keyID: string;
|
||||
address: string;
|
||||
appsNodeKey: string;
|
||||
stxNodeKey: string;
|
||||
salt: string;
|
||||
}
|
||||
|
||||
export async function deriveIdentityKeyPair(
|
||||
export function deriveIdentityKeyPair(
|
||||
identityOwnerAddressNode: IdentityAddressOwnerNode
|
||||
): Promise<IdentityKeyPair> {
|
||||
const address = await identityOwnerAddressNode.getAddress();
|
||||
): IdentityKeyPair {
|
||||
const address = identityOwnerAddressNode.getAddress();
|
||||
const identityKey = identityOwnerAddressNode.getIdentityKey();
|
||||
const identityKeyID = identityOwnerAddressNode.getIdentityKeyID();
|
||||
const appsNode = identityOwnerAddressNode.getAppsNode();
|
||||
const stxNode = identityOwnerAddressNode.getSTXNode();
|
||||
const keyPair = {
|
||||
key: identityKey,
|
||||
keyID: identityKeyID,
|
||||
address,
|
||||
appsNodeKey: appsNode.toBase58(),
|
||||
stxNodeKey: stxNode.toBase58(),
|
||||
salt: identityOwnerAddressNode.getSalt(),
|
||||
};
|
||||
return keyPair;
|
||||
@@ -104,7 +107,7 @@ export async function getBlockchainIdentities(
|
||||
const bitcoinPublicKeychainNode = bitcoinPrivateKeychainNode.neutered();
|
||||
const bitcoinPublicKeychain = bitcoinPublicKeychainNode.toBase58();
|
||||
|
||||
const firstBitcoinAddress = await getAddress(getBitcoinAddressNode(bitcoinPublicKeychainNode));
|
||||
const firstBitcoinAddress = getAddress(getBitcoinAddressNode(bitcoinPublicKeychainNode));
|
||||
|
||||
const identityAddresses: string[] = [];
|
||||
const identityKeypairs = [];
|
||||
@@ -135,7 +138,7 @@ export const makeIdentity = async (rootNode: BIP32Interface, index: number) => {
|
||||
identityPrivateKeychainNode,
|
||||
index
|
||||
);
|
||||
const identityKeyPair = await deriveIdentityKeyPair(identityOwnerAddressNode);
|
||||
const identityKeyPair = deriveIdentityKeyPair(identityOwnerAddressNode);
|
||||
const identity = new Identity({
|
||||
keyPair: identityKeyPair,
|
||||
address: identityKeyPair.address,
|
||||
|
||||
@@ -17,8 +17,6 @@ import {
|
||||
getPublicKeyFromPrivate,
|
||||
decryptContent,
|
||||
} from 'blockstack';
|
||||
import { GaiaHubConfig, uploadToGaiaHub } from 'blockstack/lib/storage/hub';
|
||||
import { makeReadOnlyGaiaConfig, DEFAULT_GAIA_HUB } from '../utils/gaia';
|
||||
import {
|
||||
AllowedKeyEntropyBits,
|
||||
generateEncryptedMnemonicRootKeychain,
|
||||
@@ -26,6 +24,9 @@ import {
|
||||
encryptMnemonicFormatted,
|
||||
} from '../mnemonic';
|
||||
import { deriveStxAddressChain } from '../address-derivation';
|
||||
import { GaiaHubConfig } from 'blockstack/lib/storage/hub';
|
||||
import { makeReadOnlyGaiaConfig, DEFAULT_GAIA_HUB, uploadToGaiaHub } from '../utils/gaia';
|
||||
import { WalletSigner } from './signer';
|
||||
|
||||
const CONFIG_INDEX = 45;
|
||||
|
||||
@@ -60,7 +61,7 @@ export interface ConstructorOptions {
|
||||
encryptedBackupPhrase: string;
|
||||
identities: Identity[];
|
||||
configPrivateKey: string;
|
||||
stxAddressKeychain: BIP32Interface;
|
||||
stacksPrivateKey: string;
|
||||
walletConfig?: WalletConfig;
|
||||
}
|
||||
|
||||
@@ -74,7 +75,7 @@ export class Wallet {
|
||||
identityPublicKeychain: string;
|
||||
identities: Identity[];
|
||||
configPrivateKey: string;
|
||||
stxAddressKeychain: BIP32Interface;
|
||||
stacksPrivateKey: string;
|
||||
walletConfig?: WalletConfig;
|
||||
|
||||
constructor({
|
||||
@@ -87,7 +88,7 @@ export class Wallet {
|
||||
identityAddresses,
|
||||
identities,
|
||||
configPrivateKey,
|
||||
stxAddressKeychain,
|
||||
stacksPrivateKey,
|
||||
walletConfig,
|
||||
}: ConstructorOptions) {
|
||||
this.chain = chain;
|
||||
@@ -99,7 +100,7 @@ export class Wallet {
|
||||
this.identityAddresses = identityAddresses;
|
||||
this.identities = identities.map(identity => new Identity(identity));
|
||||
this.configPrivateKey = configPrivateKey;
|
||||
this.stxAddressKeychain = stxAddressKeychain;
|
||||
this.stacksPrivateKey = stacksPrivateKey;
|
||||
this.walletConfig = walletConfig;
|
||||
}
|
||||
|
||||
@@ -161,7 +162,7 @@ export class Wallet {
|
||||
...walletAttrs,
|
||||
chain,
|
||||
configPrivateKey,
|
||||
stxAddressKeychain,
|
||||
stacksPrivateKey: stxAddressKeychain.toBase58(),
|
||||
encryptedBackupPhrase,
|
||||
});
|
||||
}
|
||||
@@ -183,7 +184,7 @@ export class Wallet {
|
||||
rootNode: bip32.BIP32Interface;
|
||||
gaiaReadURL: string;
|
||||
}) {
|
||||
const gaiaConfig = await makeReadOnlyGaiaConfig({
|
||||
const gaiaConfig = makeReadOnlyGaiaConfig({
|
||||
readURL: gaiaReadURL,
|
||||
privateKey: this.configPrivateKey,
|
||||
});
|
||||
@@ -243,7 +244,13 @@ export class Wallet {
|
||||
}
|
||||
}
|
||||
|
||||
async getOrCreateConfig(gaiaConfig: GaiaHubConfig): Promise<WalletConfig> {
|
||||
async getOrCreateConfig({
|
||||
gaiaConfig,
|
||||
skipUpload,
|
||||
}: {
|
||||
gaiaConfig: GaiaHubConfig;
|
||||
skipUpload?: boolean;
|
||||
}): Promise<WalletConfig> {
|
||||
if (this.walletConfig) {
|
||||
return this.walletConfig;
|
||||
}
|
||||
@@ -259,14 +266,16 @@ export class Wallet {
|
||||
})),
|
||||
};
|
||||
this.walletConfig = newConfig;
|
||||
await this.updateConfig(gaiaConfig);
|
||||
if (!skipUpload) {
|
||||
await this.updateConfig(gaiaConfig);
|
||||
}
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
async updateConfig(gaiaConfig: GaiaHubConfig): Promise<void> {
|
||||
const publicKey = getPublicKeyFromPrivate(this.configPrivateKey);
|
||||
const encrypted = await encryptContent(JSON.stringify(this.walletConfig), { publicKey });
|
||||
await uploadToGaiaHub('wallet-config.json', encrypted, gaiaConfig, 'application/json');
|
||||
await uploadToGaiaHub('wallet-config.json', encrypted, gaiaConfig);
|
||||
}
|
||||
|
||||
async updateConfigWithAuth({
|
||||
@@ -312,6 +321,10 @@ export class Wallet {
|
||||
|
||||
await this.updateConfig(gaiaConfig);
|
||||
}
|
||||
|
||||
getSigner() {
|
||||
return new WalletSigner({ privateKey: this.stacksPrivateKey });
|
||||
}
|
||||
}
|
||||
|
||||
export default Wallet;
|
||||
|
||||
147
packages/keychain/src/wallet/signer.ts
Normal file
147
packages/keychain/src/wallet/signer.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
makeContractCall,
|
||||
makeContractDeploy,
|
||||
TransactionVersion,
|
||||
ClarityValue,
|
||||
StacksTestnet,
|
||||
makeSTXTokenTransfer,
|
||||
PostConditionMode,
|
||||
getAddressFromPrivateKey,
|
||||
PostCondition,
|
||||
StacksNetwork,
|
||||
} from '@blockstack/stacks-transactions';
|
||||
import RPCClient from '@blockstack/rpc-client';
|
||||
import { bip32 } from 'bitcoinjs-lib';
|
||||
import { assertIsTruthy } from '../utils';
|
||||
import BN from 'bn.js';
|
||||
|
||||
interface ContractCallOptions {
|
||||
contractName: string;
|
||||
contractAddress: string;
|
||||
functionName: string;
|
||||
functionArgs: ClarityValue[];
|
||||
version: TransactionVersion;
|
||||
nonce: number;
|
||||
postConditions?: PostCondition[];
|
||||
postConditionMode?: PostConditionMode;
|
||||
network?: StacksNetwork;
|
||||
}
|
||||
|
||||
interface ContractDeployOptions {
|
||||
contractName: string;
|
||||
codeBody: string;
|
||||
version: TransactionVersion;
|
||||
nonce: number;
|
||||
postConditions?: PostCondition[];
|
||||
postConditionMode?: PostConditionMode;
|
||||
network?: StacksNetwork;
|
||||
}
|
||||
|
||||
interface STXTransferOptions {
|
||||
recipient: string;
|
||||
amount: string;
|
||||
memo?: string;
|
||||
nonce: number;
|
||||
postConditions?: PostCondition[];
|
||||
postConditionMode?: PostConditionMode;
|
||||
network?: StacksNetwork;
|
||||
}
|
||||
|
||||
export class WalletSigner {
|
||||
privateKey: string;
|
||||
|
||||
constructor({ privateKey }: { privateKey: string }) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
getSTXAddress(version: TransactionVersion) {
|
||||
return getAddressFromPrivateKey(this.getSTXPrivateKey(), version);
|
||||
}
|
||||
|
||||
getSTXPrivateKey() {
|
||||
const node = bip32.fromBase58(this.privateKey);
|
||||
assertIsTruthy<Buffer>(node.privateKey);
|
||||
return node.privateKey;
|
||||
}
|
||||
|
||||
getNetwork() {
|
||||
const network = new StacksTestnet();
|
||||
network.coreApiUrl = 'https://sidecar.staging.blockstack.xyz';
|
||||
return network;
|
||||
}
|
||||
|
||||
async fetchAccount({
|
||||
version,
|
||||
rpcClient,
|
||||
}: {
|
||||
version: TransactionVersion;
|
||||
rpcClient: RPCClient;
|
||||
}) {
|
||||
const address = this.getSTXAddress(version);
|
||||
const account = await rpcClient.fetchAccount(address);
|
||||
return account;
|
||||
}
|
||||
|
||||
async signContractCall({
|
||||
contractName,
|
||||
contractAddress,
|
||||
functionName,
|
||||
functionArgs,
|
||||
nonce,
|
||||
postConditionMode,
|
||||
postConditions,
|
||||
}: ContractCallOptions) {
|
||||
const tx = await makeContractCall({
|
||||
contractAddress,
|
||||
contractName,
|
||||
functionName,
|
||||
functionArgs,
|
||||
senderKey: this.getSTXPrivateKey().toString('hex'),
|
||||
nonce: new BN(nonce),
|
||||
network: this.getNetwork(),
|
||||
postConditionMode,
|
||||
postConditions,
|
||||
});
|
||||
return tx;
|
||||
}
|
||||
|
||||
async signContractDeploy({
|
||||
contractName,
|
||||
codeBody,
|
||||
nonce,
|
||||
postConditionMode,
|
||||
postConditions,
|
||||
}: ContractDeployOptions) {
|
||||
const tx = await makeContractDeploy({
|
||||
contractName,
|
||||
codeBody: codeBody,
|
||||
senderKey: this.getSTXPrivateKey().toString('hex'),
|
||||
network: this.getNetwork(),
|
||||
nonce: new BN(nonce),
|
||||
postConditionMode,
|
||||
postConditions,
|
||||
});
|
||||
return tx;
|
||||
}
|
||||
|
||||
async signSTXTransfer({
|
||||
recipient,
|
||||
amount,
|
||||
memo,
|
||||
nonce,
|
||||
postConditionMode,
|
||||
postConditions,
|
||||
}: STXTransferOptions) {
|
||||
const tx = await makeSTXTokenTransfer({
|
||||
recipient,
|
||||
amount: new BN(amount),
|
||||
memo,
|
||||
senderKey: this.getSTXPrivateKey().toString('hex'),
|
||||
network: this.getNetwork(),
|
||||
nonce: new BN(nonce),
|
||||
postConditionMode,
|
||||
postConditions,
|
||||
});
|
||||
return tx;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import { decodeToken } from 'jsontokens';
|
||||
import { getIdentity, profileResponse } from './helpers';
|
||||
import { ecPairToAddress } from 'blockstack';
|
||||
import { ECPair } from 'bitcoinjs-lib';
|
||||
import { getAddress } from '../src';
|
||||
import { TransactionVersion } from '@blockstack/stacks-transactions';
|
||||
|
||||
interface Decoded {
|
||||
[key: string]: any;
|
||||
@@ -60,9 +62,7 @@ test('adds to apps in profile if publish_data scope', async () => {
|
||||
expect(apps[appDomain]).not.toBeFalsy();
|
||||
const appPrivateKey = await decryptPrivateKey(transitPrivateKey, payload.private_key);
|
||||
const challengeSigner = ECPair.fromPrivateKey(Buffer.from(appPrivateKey as string, 'hex'));
|
||||
const expectedDomain = `https://gaia.blockstack.org/hub/${await ecPairToAddress(
|
||||
challengeSigner
|
||||
)}/`;
|
||||
const expectedDomain = `https://gaia.blockstack.org/hub/${ecPairToAddress(challengeSigner)}/`;
|
||||
expect(apps[appDomain]).toEqual(expectedDomain);
|
||||
expect(appsMeta[appDomain]).not.toBeFalsy();
|
||||
expect(appsMeta[appDomain].storage).toEqual(expectedDomain);
|
||||
@@ -72,7 +72,7 @@ test('adds to apps in profile if publish_data scope', async () => {
|
||||
test('generates an app private key', async () => {
|
||||
const expectedKey = '6f8b6a170f8b2ee57df5ead49b0f4c8acde05f9e1c4c6ef8223d6a42fabfa314';
|
||||
const identity = await getIdentity();
|
||||
const appPrivateKey = await identity.appPrivateKey('https://banter.pub');
|
||||
const appPrivateKey = identity.appPrivateKey('https://banter.pub');
|
||||
expect(appPrivateKey).toEqual(expectedKey);
|
||||
});
|
||||
|
||||
@@ -80,7 +80,7 @@ test('generates an app private key for a different seed', async () => {
|
||||
const identity = await getIdentity(
|
||||
'monster toilet shoe giggle welcome coyote enact glass copy era shed foam'
|
||||
);
|
||||
const appPrivateKey = await identity.appPrivateKey('https://banter.pub');
|
||||
const appPrivateKey = identity.appPrivateKey('https://banter.pub');
|
||||
expect(appPrivateKey).toEqual('a7bf3ecf0dd68a23a6621c39780d6cae3776240251a7988fed9ecfda2699ffe8');
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { makeProfileZoneFile } from 'blockstack';
|
||||
describe('signProfileForUpload', () => {
|
||||
it('should create a signed JSON string', async () => {
|
||||
const identity = await getIdentity();
|
||||
const signedJSON = await signProfileForUpload(DEFAULT_PROFILE, identity.keyPair);
|
||||
const signedJSON = signProfileForUpload(DEFAULT_PROFILE, identity.keyPair);
|
||||
const profile = JSON.parse(signedJSON);
|
||||
expect(profile.length).toEqual(1);
|
||||
const [data] = profile;
|
||||
|
||||
18
packages/keychain/tests/wallet-signer.test.ts
Normal file
18
packages/keychain/tests/wallet-signer.test.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import './setup';
|
||||
import { getWallet } from './helpers';
|
||||
import { TransactionVersion } from '@blockstack/stacks-transactions';
|
||||
|
||||
const getSigner = async () => {
|
||||
const wallet = await getWallet();
|
||||
return wallet.getSigner();
|
||||
};
|
||||
|
||||
test('can get a STX address', async () => {
|
||||
const signer = await getSigner();
|
||||
expect(signer.getSTXAddress(TransactionVersion.Mainnet)).toEqual(
|
||||
'SP1GZ804XH4240T4JT2GQ34GG0DMT6B3BQ5NV18PD'
|
||||
);
|
||||
expect(signer.getSTXAddress(TransactionVersion.Testnet)).toEqual(
|
||||
'ST1GZ804XH4240T4JT2GQ34GG0DMT6B3BQ5YQX2WX'
|
||||
);
|
||||
});
|
||||
@@ -33,6 +33,8 @@ describe('Restoring a wallet', () => {
|
||||
'xprvA1y4zBndD83n6PWgVH6ivkTpNQ2WU1UGPg9hWa2q8sCANa7YrYMZFHWMhrbpsarx' +
|
||||
'XMuQRa4jtaT2YXugwsKrjFgn765tUHu9XjyiDFEjB7f',
|
||||
salt: 'c15619adafe7e75a195a1a2b5788ca42e585a3fd181ae2ff009c6089de54ed9e',
|
||||
stxNodeKey:
|
||||
'xprvA1y4zBndD83nNNFWE1UiWpmc9hpPuk8xjPNwb2j341txeJmCHe8VWT7VKS6FcgnCtbuBP2kzyW34ESdJtJ81AQxCbr9cmQsUHHZ8dtyTxCy',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -157,7 +159,7 @@ test('creates a config', async () => {
|
||||
.once(JSON.stringify({ publicUrl: 'asdf' }));
|
||||
const wallet = await Wallet.generate('password', ChainID.Testnet);
|
||||
const hubConfig = await wallet.createGaiaConfig('https://gaia.blockstack.org');
|
||||
const config = await wallet.getOrCreateConfig(hubConfig);
|
||||
const config = await wallet.getOrCreateConfig({ gaiaConfig: hubConfig });
|
||||
expect(Object.keys(config.identities[0].apps).length).toEqual(0);
|
||||
const { body } = fetchMock.mock.calls[2][1];
|
||||
const decrypted = (await decryptContent(body, { privateKey: wallet.configPrivateKey })) as string;
|
||||
@@ -179,7 +181,7 @@ test('updates wallet config', async () => {
|
||||
|
||||
const wallet = await Wallet.generate('password', ChainID.Testnet);
|
||||
const gaiaConfig = await wallet.createGaiaConfig('https://gaia.blockstack.org');
|
||||
await wallet.getOrCreateConfig(gaiaConfig);
|
||||
await wallet.getOrCreateConfig({ gaiaConfig });
|
||||
const app: ConfigApp = {
|
||||
origin: 'http://localhost:5000',
|
||||
scopes: ['read_write'],
|
||||
@@ -216,7 +218,7 @@ test('updates config for reusing id warning', async () => {
|
||||
|
||||
const wallet = await Wallet.generate('password', ChainID.Testnet);
|
||||
const gaiaConfig = await wallet.createGaiaConfig('https://gaia.blockstack.org');
|
||||
await wallet.getOrCreateConfig(gaiaConfig);
|
||||
await wallet.getOrCreateConfig({ gaiaConfig });
|
||||
expect(wallet.walletConfig?.hideWarningForReusingIdentity).toBeFalsy();
|
||||
await wallet.updateConfigForReuseWarning({ gaiaConfig });
|
||||
expect(wallet.walletConfig?.hideWarningForReusingIdentity).toBeTruthy();
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"outDir": "/lib",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": "./src",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"paths": {
|
||||
"@blockstack/rpc-client": ["../../rpc-client/src"]
|
||||
},
|
||||
"lib": [
|
||||
"es2017",
|
||||
"dom"
|
||||
|
||||
8
packages/rpc-client/.gitignore
vendored
Normal file
8
packages/rpc-client/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
node_modules
|
||||
.rts2_cache_cjs
|
||||
.rts2_cache_esm
|
||||
.rts2_cache_umd
|
||||
.rts2_cache_system
|
||||
dist
|
||||
21
packages/rpc-client/LICENSE
Normal file
21
packages/rpc-client/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Hank Stoever
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
27
packages/rpc-client/README.md
Normal file
27
packages/rpc-client/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# TSDX Bootstrap
|
||||
|
||||
This project was bootstrapped with [TSDX](https://github.com/jaredpalmer/tsdx).
|
||||
|
||||
## Local Development
|
||||
|
||||
Below is a list of commands you will probably find useful.
|
||||
|
||||
### `npm start` or `yarn start`
|
||||
|
||||
Runs the project in development/watch mode. Your project will be rebuilt upon changes. TSDX has a special logger for you convenience. Error messages are pretty printed and formatted for compatibility VS Code's Problems tab.
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/4060187/52168303-574d3a00-26f6-11e9-9f3b-71dbec9ebfcb.gif" width="600" />
|
||||
|
||||
Your library will be rebuilt if you make edits.
|
||||
|
||||
### `npm run build` or `yarn build`
|
||||
|
||||
Bundles the package to the `dist` folder.
|
||||
The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module).
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/4060187/52168322-a98e5b00-26f6-11e9-8cf6-222d716b75ef.gif" width="600" />
|
||||
|
||||
### `npm test` or `yarn test`
|
||||
|
||||
Runs the test watcher (Jest) in an interactive mode.
|
||||
By default, runs tests related to files changed since the last commit.
|
||||
41
packages/rpc-client/package.json
Normal file
41
packages/rpc-client/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@blockstack/rpc-client",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"license": "MIT",
|
||||
"author": "Hank Stoever",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/rpc-client.esm.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "tsdx watch",
|
||||
"build": "tsdx build",
|
||||
"test": "tsdx test",
|
||||
"lint": "tsdx lint",
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blockstack/stacks-blockchain-sidecar-types": "^0.0.19",
|
||||
"@types/bn.js": "^4.11.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"bn.js": "^5.1.1",
|
||||
"husky": "^4.2.3",
|
||||
"tsdx": "^0.13.1",
|
||||
"tslib": "^1.11.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blockstack/stacks-transactions": "0.5.1",
|
||||
"cross-fetch": "^3.0.4"
|
||||
}
|
||||
}
|
||||
143
packages/rpc-client/src/index.ts
Normal file
143
packages/rpc-client/src/index.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import BN from 'bn.js';
|
||||
import { serializeCV, ClarityValue } from '@blockstack/stacks-transactions';
|
||||
import { TransactionResults } from '@blockstack/stacks-blockchain-sidecar-types';
|
||||
|
||||
export interface Account {
|
||||
balance: BN;
|
||||
nonce: number;
|
||||
}
|
||||
|
||||
export const toBN = (hex: string) => {
|
||||
return new BN(hex.slice(2), 16);
|
||||
};
|
||||
|
||||
interface FetchContractInterface {
|
||||
contractAddress: string;
|
||||
contractName: string;
|
||||
}
|
||||
|
||||
interface BufferArg {
|
||||
buffer: {
|
||||
length: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContractInterfaceFunctionArg {
|
||||
name: string;
|
||||
type: string | BufferArg;
|
||||
}
|
||||
|
||||
export interface ContractInterfaceFunction {
|
||||
name: string;
|
||||
access: 'public' | 'private' | 'read_only';
|
||||
args: ContractInterfaceFunctionArg[];
|
||||
}
|
||||
|
||||
export interface ContractInterface {
|
||||
functions: ContractInterfaceFunction[];
|
||||
}
|
||||
interface CallReadOnly extends FetchContractInterface {
|
||||
args: ClarityValue[];
|
||||
functionName: string;
|
||||
}
|
||||
|
||||
export class RPCClient {
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* @param url The base URL for the RPC server
|
||||
*/
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
async fetchAccount(principal: string): Promise<Account> {
|
||||
const url = `${this.url}/v2/accounts/${principal}`;
|
||||
const response = await fetch(url, {
|
||||
credentials: 'omit',
|
||||
});
|
||||
const data = await response.json();
|
||||
return {
|
||||
balance: toBN(data.balance),
|
||||
nonce: data.nonce,
|
||||
};
|
||||
}
|
||||
|
||||
async broadcastTX(hex: Buffer) {
|
||||
const url = `${this.url}/v2/transactions`;
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'omit',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
},
|
||||
body: hex,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
async fetchContractInterface({
|
||||
contractAddress,
|
||||
contractName,
|
||||
}: FetchContractInterface) {
|
||||
const url = `${this.url}/v2/contracts/interface/${contractAddress}/${contractName}`;
|
||||
const response = await fetch(url);
|
||||
const contractInterface: ContractInterface = await response.json();
|
||||
return contractInterface;
|
||||
}
|
||||
|
||||
async callReadOnly({
|
||||
contractName,
|
||||
contractAddress,
|
||||
functionName,
|
||||
args,
|
||||
}: CallReadOnly) {
|
||||
const url = `${this.url}/v2/contracts/call-read/${contractAddress}/${contractName}/${functionName}`;
|
||||
const argsStrings = args.map((arg) => {
|
||||
return `0x${serializeCV(arg).toString('hex')}`;
|
||||
});
|
||||
const body = {
|
||||
sender: 'SP31DA6FTSJX2WGTZ69SFY11BH51NZMB0ZW97B5P0',
|
||||
arguments: argsStrings,
|
||||
};
|
||||
console.log(body);
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Unable to call read-only function.`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async fetchContractSource({
|
||||
contractName,
|
||||
contractAddress,
|
||||
}: {
|
||||
contractName: string;
|
||||
contractAddress: string;
|
||||
}) {
|
||||
const url = `${this.url}/v2/contracts/source/${contractAddress}/${contractName}`;
|
||||
const res = await fetch(url);
|
||||
if (res.ok) {
|
||||
const { source }: { source: string } = await res.json();
|
||||
return source;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async fetchAddressTransactions({ address }: { address: string }) {
|
||||
const url = `${this.url}/sidecar/v1/address/${address}/transactions`;
|
||||
const res = await fetch(url);
|
||||
const data: TransactionResults = await res.json();
|
||||
return data.results;
|
||||
}
|
||||
}
|
||||
|
||||
export default RPCClient;
|
||||
11
packages/rpc-client/test/index.test.ts
Normal file
11
packages/rpc-client/test/index.test.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { fetchAccount } from '../src';
|
||||
|
||||
describe('fetchAccount', () => {
|
||||
it('works', async () => {
|
||||
const account = await fetchAccount(
|
||||
'ST2VHM28V9E5QCRD6C73215KAPSBKQGPWTEE5CMQT'
|
||||
);
|
||||
expect(account.balance.toNumber()).toEqual(10000);
|
||||
expect(account.nonce).toEqual(0);
|
||||
}, 10000);
|
||||
});
|
||||
30
packages/rpc-client/tsconfig.json
Normal file
30
packages/rpc-client/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"include": ["src", "types", "test"],
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"lib": ["dom", "esnext"],
|
||||
"importHelpers": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"rootDir": "./",
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"*": ["src/*", "node_modules/*"]
|
||||
},
|
||||
"jsx": "react",
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
21
packages/test-app/common/context.ts
Normal file
21
packages/test-app/common/context.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { createContext } from 'react';
|
||||
import { UserData } from 'blockstack/lib/auth/authApp';
|
||||
import { UserSession, AppConfig } from 'blockstack';
|
||||
|
||||
export interface AppState {
|
||||
userData: UserData | null;
|
||||
}
|
||||
|
||||
export const defaultState = (): AppState => {
|
||||
const appConfig = new AppConfig(['store_write'], document.location.href);
|
||||
const userSession = new UserSession({ appConfig });
|
||||
|
||||
if (userSession.isUserSignedIn()) {
|
||||
return {
|
||||
userData: userSession.loadUserData(),
|
||||
};
|
||||
}
|
||||
return { userData: null };
|
||||
};
|
||||
|
||||
export const AppContext = createContext<AppState>(defaultState());
|
||||
59
packages/test-app/common/contracts.ts
Normal file
59
packages/test-app/common/contracts.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
export const SampleContracts: readonly {
|
||||
readonly contractName: string;
|
||||
readonly contractSource: string;
|
||||
}[] = [
|
||||
{
|
||||
contractName: 'hello-world-contract',
|
||||
contractSource: `(define-constant sender 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR)
|
||||
(define-constant recipient 'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G)
|
||||
|
||||
(define-fungible-token novel-token-19)
|
||||
(begin (ft-mint? novel-token-19 u12 sender))
|
||||
(begin (ft-transfer? novel-token-19 u2 sender recipient))
|
||||
|
||||
(define-non-fungible-token hello-nft uint)
|
||||
(begin (nft-mint? hello-nft u1 sender))
|
||||
(begin (nft-mint? hello-nft u2 sender))
|
||||
(begin (nft-transfer? hello-nft u1 sender recipient))
|
||||
|
||||
(define-public (test-emit-event)
|
||||
(begin
|
||||
(print "Event! Hello world")
|
||||
(ok u1)))
|
||||
(begin (test-emit-event))
|
||||
|
||||
(define-public (test-event-types)
|
||||
(begin
|
||||
(unwrap-panic (ft-mint? novel-token-19 u3 recipient))
|
||||
(unwrap-panic (nft-mint? hello-nft u2 recipient))
|
||||
(unwrap-panic (stx-transfer? u60 tx-sender 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR))
|
||||
(unwrap-panic (stx-burn? u20 tx-sender))
|
||||
(ok u1)))
|
||||
|
||||
(define-map store ((key (buff 32))) ((value (buff 32))))
|
||||
(define-public (get-value (key (buff 32)))
|
||||
(begin
|
||||
(match (map-get? store ((key key)))
|
||||
entry (ok (get value entry))
|
||||
(err 0))))
|
||||
(define-public (set-value (key (buff 32)) (value (buff 32)))
|
||||
(begin
|
||||
(map-set store ((key key)) ((value value)))
|
||||
(ok u1)))`,
|
||||
},
|
||||
{
|
||||
contractName: 'kv-store',
|
||||
contractSource: `
|
||||
(define-map store ((key (buff 32))) ((value (buff 32))))
|
||||
|
||||
(define-public (get-value (key (buff 32)))
|
||||
(match (map-get? store {key: key})
|
||||
entry (ok (get value entry))
|
||||
(err 0)))
|
||||
|
||||
(define-public (set-value (key (buff 32)) (value (buff 32)))
|
||||
(begin
|
||||
(map-set store {key: key} {value: value})
|
||||
(ok u1)))`,
|
||||
},
|
||||
];
|
||||
83
packages/test-app/common/use-faucet.ts
Normal file
83
packages/test-app/common/use-faucet.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useContext, useState, useEffect } from 'react';
|
||||
import { AppContext } from '@common/context';
|
||||
import { getRPCClient } from './utils';
|
||||
import { useSTXAddress } from './use-stx-address';
|
||||
|
||||
export const useFaucet = () => {
|
||||
const stxAddress = useSTXAddress();
|
||||
const state = useContext(AppContext);
|
||||
const [balance, setBalance] = useState<number | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
const [txId, setTxId] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const client = getRPCClient();
|
||||
|
||||
interface FaucetResponse {
|
||||
txId?: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
const submit = async (stxAddress: string) => {
|
||||
setWaiting(true);
|
||||
const waitForBalance = async (currentBalance: number, attempts: number) => {
|
||||
const { balance } = await client.fetchAccount(stxAddress);
|
||||
if (attempts > 18) {
|
||||
setError(
|
||||
"It looks like your transaction still isn't confirmed after a few minutes. Something may have gone wrong."
|
||||
);
|
||||
setWaiting(false);
|
||||
}
|
||||
if (balance.toNumber() > currentBalance) {
|
||||
setWaiting(false);
|
||||
setBalance(balance.toNumber());
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
waitForBalance(currentBalance, attempts + 1);
|
||||
}, 10000);
|
||||
};
|
||||
try {
|
||||
const url = `${client.url}/sidecar/v1/debug/faucet?address=${stxAddress}`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
});
|
||||
const data: FaucetResponse = await res.json();
|
||||
console.log(data);
|
||||
if (data.txId) {
|
||||
setTxId(data.txId);
|
||||
const { balance } = await client.fetchAccount(stxAddress);
|
||||
await waitForBalance(balance.toNumber(), 0);
|
||||
} else {
|
||||
setError('Something went wrong when requesting the faucet.');
|
||||
}
|
||||
} catch (e) {
|
||||
setError('Something went wrong when requesting the faucet.');
|
||||
setLoading(false);
|
||||
console.error(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const getBalance = async () => {
|
||||
if (stxAddress) {
|
||||
setLoading(true);
|
||||
const { balance } = await client.fetchAccount(stxAddress);
|
||||
setBalance(balance.toNumber());
|
||||
if (balance.toNumber() === 0) {
|
||||
void submit(stxAddress);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
void getBalance();
|
||||
}, [state.userData]);
|
||||
|
||||
return {
|
||||
balance,
|
||||
loading,
|
||||
waiting,
|
||||
error,
|
||||
txId,
|
||||
};
|
||||
};
|
||||
7
packages/test-app/common/use-stx-address.ts
Normal file
7
packages/test-app/common/use-stx-address.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
import { AppContext } from '@common/context';
|
||||
|
||||
export const useSTXAddress = (): string | undefined => {
|
||||
const { userData } = useContext(AppContext);
|
||||
return userData?.profile?.stxAddress;
|
||||
};
|
||||
33
packages/test-app/common/utils.ts
Normal file
33
packages/test-app/common/utils.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { RPCClient } from '@blockstack/rpc-client';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export const getAuthOrigin = () => {
|
||||
let authOrigin = process.env.AUTH_ORIGIN || 'http://localhost:8080';
|
||||
// In order to have deploy previews use the same version of the authenticator,
|
||||
// we detect if this is a 'deploy preview' and change the origin to point to the
|
||||
// same PR's deploy preview in the authenticator.
|
||||
const { origin } = location;
|
||||
if (origin.includes('deploy-preview')) {
|
||||
// Our netlify sites are called "authenticator-demo" for this app, and
|
||||
// "stacks-authenticator" for the authenticator.
|
||||
authOrigin = document.location.origin.replace('authenticator-demo', 'stacks-authenticator');
|
||||
} else if (origin.includes('authenticator-demo')) {
|
||||
// TODO: revert this when 301 is merged
|
||||
authOrigin = 'https://deploy-preview-301--stacks-authenticator.netlify.app';
|
||||
// authOrigin = 'https://app.blockstack.org';
|
||||
}
|
||||
return authOrigin;
|
||||
};
|
||||
|
||||
export const getRPCClient = () => {
|
||||
const { origin } = location;
|
||||
const url = origin.includes('localhost')
|
||||
? 'http://localhost:3999'
|
||||
: 'https://sidecar.staging.blockstack.xyz';
|
||||
return new RPCClient(url);
|
||||
};
|
||||
|
||||
export const toRelativeTime = (ts: number): string => dayjs().to(ts);
|
||||
@@ -1,95 +1,39 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ThemeProvider, Box, theme, Text, Flex, CSSReset, Button, Stack } from '@blockstack/ui';
|
||||
import { Connect, AuthOptions, useConnect } from '@blockstack/connect';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ThemeProvider, theme, Flex, CSSReset } from '@blockstack/ui';
|
||||
import { Connect, AuthOptions } from '@blockstack/connect';
|
||||
import { getAuthOrigin } from '@common/utils';
|
||||
import { UserSession, AppConfig } from 'blockstack';
|
||||
import { defaultState, AppContext, AppState } from '@common/context';
|
||||
import { Header } from '@components/header';
|
||||
import { Home } from '@components/home';
|
||||
|
||||
const icon = '/assets/messenger-app-icon.png';
|
||||
let authOrigin = process.env.AUTH_ORIGIN || 'http://localhost:8080';
|
||||
// In order to have deploy previews use the same version of the authenticator,
|
||||
// we detect if this is a 'deploy preview' and change the origin to point to the
|
||||
// same PR's deploy preview in the authenticator.
|
||||
const { origin } = location;
|
||||
if (origin.includes('deploy-preview')) {
|
||||
// Our netlify sites are called "authenticator-demo" for this app, and
|
||||
// "stacks-authenticator" for the authenticator.
|
||||
authOrigin = document.location.origin.replace('authenticator-demo', 'stacks-authenticator');
|
||||
} else if (origin.includes('authenticator-demo')) {
|
||||
authOrigin = 'https://app.blockstack.org';
|
||||
}
|
||||
|
||||
const Card: React.FC = props => (
|
||||
<Flex
|
||||
border="1px solid"
|
||||
borderRadius="6px"
|
||||
borderColor="inherit"
|
||||
p={6}
|
||||
direction="column"
|
||||
boxShadow="mid"
|
||||
minWidth="420px"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
const AppContent: React.FC = () => {
|
||||
const { doOpenAuth, doAuth } = useConnect();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Box textAlign="center" pb={6}>
|
||||
<Text as="h1">Blockstack Connect</Text>
|
||||
</Box>
|
||||
<Flex justify="center">
|
||||
<Stack isInline>
|
||||
<Button onClick={() => doOpenAuth(false)} data-test="sign-up">
|
||||
Sign Up
|
||||
</Button>
|
||||
<Button onClick={() => doOpenAuth(true)} data-test="sign-in">
|
||||
Sign In
|
||||
</Button>
|
||||
<Button onClick={() => doAuth()} data-test="skip-connect">
|
||||
Skip Connect
|
||||
</Button>
|
||||
</Stack>
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
interface AppState {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const SignedIn = (props: { username: string; handleSignOut: () => void }) => {
|
||||
return (
|
||||
<Card>
|
||||
<Box textAlign="center">
|
||||
<Text as="h1">Welcome back!</Text>
|
||||
</Box>
|
||||
<Box textAlign="center" pt={4}>
|
||||
<Text as="h2">{props.username}</Text>
|
||||
</Box>
|
||||
<Flex mt={6} align="center" justify="center">
|
||||
<Button mx="auto" onClick={props.handleSignOut}>
|
||||
Sign out
|
||||
</Button>
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const [state, setState] = React.useState<AppState>({});
|
||||
const [state, setState] = React.useState<AppState>(defaultState());
|
||||
const [authResponse, setAuthResponse] = React.useState('');
|
||||
const [appPrivateKey, setAppPrivateKey] = React.useState('');
|
||||
|
||||
const appConfig = new AppConfig();
|
||||
const appConfig = new AppConfig(['store_write'], document.location.href);
|
||||
const userSession = new UserSession({ appConfig });
|
||||
|
||||
const signOut = () => {
|
||||
userSession.signUserOut();
|
||||
setState({ userData: null });
|
||||
};
|
||||
|
||||
const authOrigin = getAuthOrigin();
|
||||
|
||||
useEffect(() => {
|
||||
if (userSession.isUserSignedIn()) {
|
||||
const userData = userSession.loadUserData();
|
||||
setState({ userData });
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleRedirectAuth = async () => {
|
||||
if (userSession.isSignInPending()) {
|
||||
const userData = await userSession.handlePendingSignIn();
|
||||
setState(() => userData);
|
||||
setState({ userData });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -100,11 +44,13 @@ export const App: React.FC = () => {
|
||||
const authOptions: AuthOptions = {
|
||||
manifestPath: '/static/manifest.json',
|
||||
redirectTo: '/',
|
||||
userSession,
|
||||
finished: ({ userSession, authResponse }) => {
|
||||
const userData = userSession.loadUserData();
|
||||
setState(() => userData);
|
||||
// setState(() => userData);
|
||||
setAppPrivateKey(userSession.loadUserData().appPrivateKey);
|
||||
setAuthResponse(authResponse);
|
||||
setState({ userData });
|
||||
console.log(userData);
|
||||
},
|
||||
onCancel: () => {
|
||||
@@ -117,31 +63,20 @@ export const App: React.FC = () => {
|
||||
},
|
||||
};
|
||||
|
||||
const handleSignOut = () => {
|
||||
setState({});
|
||||
};
|
||||
const isSignedIn = (state && state.identityAddress) || undefined;
|
||||
return (
|
||||
<Connect authOptions={authOptions}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CSSReset />
|
||||
<Flex
|
||||
direction="column"
|
||||
height="100vh"
|
||||
width="100vw"
|
||||
align="center"
|
||||
justify="center"
|
||||
bg="whitesmoke"
|
||||
>
|
||||
{authResponse && <input type="hidden" id="auth-response" value={authResponse} />}
|
||||
{appPrivateKey && <input type="hidden" id="app-private-key" value={appPrivateKey} />}
|
||||
<AppContext.Provider value={state}>
|
||||
<CSSReset />
|
||||
<Flex direction="column" minHeight="100vh" bg="white">
|
||||
{authResponse && <input type="hidden" id="auth-response" value={authResponse} />}
|
||||
{appPrivateKey && <input type="hidden" id="app-private-key" value={appPrivateKey} />}
|
||||
|
||||
{!isSignedIn ? (
|
||||
<AppContent />
|
||||
) : (
|
||||
<SignedIn handleSignOut={handleSignOut} username={state.username} />
|
||||
)}
|
||||
</Flex>
|
||||
<Header signOut={signOut} />
|
||||
|
||||
<Home />
|
||||
</Flex>
|
||||
</AppContext.Provider>
|
||||
</ThemeProvider>
|
||||
</Connect>
|
||||
);
|
||||
|
||||
22
packages/test-app/components/auth.tsx
Normal file
22
packages/test-app/components/auth.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Button, Text, Box, space, ButtonGroup } from '@blockstack/ui';
|
||||
import { useConnect } from '@blockstack/connect';
|
||||
|
||||
export const Auth: React.FC = () => {
|
||||
const { doOpenAuth } = useConnect();
|
||||
return (
|
||||
<Box>
|
||||
<Text display="block" textStyle="body.large">
|
||||
Sign in with your Blockstack account to try the demo
|
||||
</Text>
|
||||
<ButtonGroup spacing={space('base')} mt={space('base-loose')}>
|
||||
<Button size="lg" onClick={() => doOpenAuth(true)} data-test="sign-in">
|
||||
Sign in
|
||||
</Button>
|
||||
<Button size="lg" mode="tertiary" onClick={() => doOpenAuth()} data-test="sign-up">
|
||||
Sign up
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
80
packages/test-app/components/counter-actions.tsx
Normal file
80
packages/test-app/components/counter-actions.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { Button, ButtonGroup, Box, Text } from '@blockstack/ui';
|
||||
import { AppContext } from '@common/context';
|
||||
import { getAuthOrigin, getRPCClient } from '@common/utils';
|
||||
import { useConnect } from '@blockstack/connect';
|
||||
import { deserializeCV, IntCV } from '@blockstack/stacks-transactions';
|
||||
import { ExplorerLink } from '@components/explorer-link';
|
||||
|
||||
export const CounterActions: React.FC = () => {
|
||||
const { userData } = React.useContext(AppContext);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [txId, setTxId] = React.useState('');
|
||||
const [counter, setCounter] = React.useState<number | null>(null);
|
||||
const [error, setError] = React.useState('');
|
||||
const { doContractCall } = useConnect();
|
||||
|
||||
const callMethod = async (method: string) => {
|
||||
setError('');
|
||||
setLoading(true);
|
||||
const authOrigin = getAuthOrigin();
|
||||
await doContractCall({
|
||||
authOrigin,
|
||||
contractAddress: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
|
||||
functionName: method,
|
||||
functionArgs: [],
|
||||
contractName: 'counter',
|
||||
finished: data => {
|
||||
setTxId(data.txId);
|
||||
console.log('finished!', data);
|
||||
setLoading(false);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getCounter = async () => {
|
||||
const client = getRPCClient();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const data = await client.callReadOnly({
|
||||
contractName: 'counter',
|
||||
contractAddress: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
|
||||
args: [],
|
||||
functionName: 'get-counter',
|
||||
});
|
||||
const cv = deserializeCV(Buffer.from(data.result.slice(2), 'hex')) as IntCV;
|
||||
console.log(cv.value);
|
||||
setCounter(cv.value.toNumber());
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setError('Unable to get current counter value.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{!userData && <Text display="block">Log in to change the state of this smart contract.</Text>}
|
||||
<ButtonGroup spacing={4} my={5}>
|
||||
<Button mt={3} onClick={() => callMethod('increment')}>
|
||||
Increase by 1
|
||||
</Button>
|
||||
<Button mt={3} onClick={() => callMethod('decrement')}>
|
||||
Decrease by 1
|
||||
</Button>
|
||||
<Button mt={3} onClick={getCounter}>
|
||||
Get current value
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
{error && (
|
||||
<Text display="block" color="red">
|
||||
{error}
|
||||
</Text>
|
||||
)}
|
||||
{txId && !loading && <ExplorerLink txId={txId} />}
|
||||
{counter !== null && !loading && (
|
||||
<Text display="block">Current counter value: {counter}</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
56
packages/test-app/components/counter.tsx
Normal file
56
packages/test-app/components/counter.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { space, Box, Text, Flex } from '@blockstack/ui';
|
||||
import { ExplorerLink } from './explorer-link';
|
||||
import { CounterActions } from './counter-actions';
|
||||
import { getRPCClient } from '@common/utils';
|
||||
import { ContractCallTransaction } from '@blockstack/stacks-blockchain-sidecar-types';
|
||||
import { TxCard } from '@components/tx-card';
|
||||
|
||||
export const Counter = () => {
|
||||
const [transactions, setTransactions] = useState<ContractCallTransaction[]>([]);
|
||||
const client = getRPCClient();
|
||||
|
||||
useEffect(() => {
|
||||
const getTransactions = async () => {
|
||||
const transactions = await client.fetchAddressTransactions({
|
||||
address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.counter',
|
||||
});
|
||||
const filtered = transactions.filter(t => {
|
||||
return t.tx_type === 'contract_call';
|
||||
});
|
||||
setTransactions(filtered as ContractCallTransaction[]);
|
||||
};
|
||||
void getTransactions();
|
||||
}, []);
|
||||
return (
|
||||
<Box py={6}>
|
||||
<Text as="h2" textStyle="display.small">
|
||||
Counter smart contract
|
||||
</Text>
|
||||
<Text textStyle="body.large" display="block" my={space('loose')}>
|
||||
Try a smart contract that keeps a single "counter" state variable. The public methods
|
||||
"increment" and "decrement" change the value of the counter.
|
||||
</Text>
|
||||
<ExplorerLink
|
||||
txId="STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.counter"
|
||||
text="View contract in explorer"
|
||||
skipConfirmCheck
|
||||
/>
|
||||
|
||||
{transactions.length > 0 && (
|
||||
<>
|
||||
<Text display="block" my={space('base-loose')} textStyle="body.large.medium">
|
||||
Latest changes
|
||||
</Text>
|
||||
<Flex flexWrap="wrap" justifyContent="left">
|
||||
{transactions.slice(0, 3).map(t => (
|
||||
<TxCard tx={t} label={t.contract_call.function_name === 'increment' ? '+1' : '-1'} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
<CounterActions />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
37
packages/test-app/components/deploy.tsx
Normal file
37
packages/test-app/components/deploy.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { getAuthOrigin } from '@common/utils';
|
||||
import { useConnect } from '@blockstack/connect';
|
||||
import { SampleContracts } from '@common/contracts';
|
||||
import { Box, Button, Text } from '@blockstack/ui';
|
||||
import { ExplorerLink } from './explorer-link';
|
||||
|
||||
export const Deploy = () => {
|
||||
const [tx, setTx] = React.useState('');
|
||||
const authOrigin = getAuthOrigin();
|
||||
const { doContractDeploy, userSession } = useConnect();
|
||||
const handleSubmit = () =>
|
||||
doContractDeploy({
|
||||
authOrigin,
|
||||
codeBody: SampleContracts[0].contractSource,
|
||||
contractName: SampleContracts[0].contractName,
|
||||
userSession,
|
||||
finished: data => {
|
||||
setTx(data.txId);
|
||||
console.log('finished!', data);
|
||||
},
|
||||
});
|
||||
return (
|
||||
<Box mb={6} maxWidth="600px" mt={6}>
|
||||
<Text as="h2" fontSize={5} mt={6}>
|
||||
Contract Deploy
|
||||
</Text>
|
||||
<Text display="block" my={4} textStyle="caption.medium">
|
||||
Deploy a Clarity smart contract. To keep things simple, we'll provide a contract for you.
|
||||
</Text>
|
||||
{tx && <ExplorerLink txId={tx} />}
|
||||
<Box mt={4}>
|
||||
<Button onClick={handleSubmit}>Deploy</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
24
packages/test-app/components/explorer-link.tsx
Normal file
24
packages/test-app/components/explorer-link.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { Link } from '@blockstack/connect';
|
||||
import { Box } from '@blockstack/ui';
|
||||
|
||||
interface LinkProps {
|
||||
txId: string;
|
||||
text?: string;
|
||||
skipConfirmCheck?: boolean;
|
||||
}
|
||||
|
||||
export const ExplorerLink: React.FC<LinkProps> = ({ txId, text }) => {
|
||||
let id = txId.replace('"', '');
|
||||
if (!id.startsWith('0x') && !id.includes('.')) {
|
||||
id = `0x${id}`;
|
||||
}
|
||||
const url = `https://testnet-explorer.blockstack.org/txid/${id}`;
|
||||
return (
|
||||
<Box>
|
||||
<Link onClick={() => window.open(url, '_blank')} color="blue" display="inline-block" my={3}>
|
||||
{text || 'View transaction in explorer'}
|
||||
</Link>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
116
packages/test-app/components/faucet.tsx
Normal file
116
packages/test-app/components/faucet.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Flex, Box, Button, Input, Text } from '@blockstack/ui';
|
||||
import { getRPCClient } from '@common/utils';
|
||||
import { ExplorerLink } from './explorer-link';
|
||||
|
||||
interface FaucetResponse {
|
||||
txId?: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export const Faucet = ({ address: _address = '' }: { address: string }) => {
|
||||
const [address, setAddress] = useState(_address);
|
||||
const [tx, setTX] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
const client = getRPCClient();
|
||||
|
||||
const handleInput = (evt: React.FormEvent<HTMLInputElement>) => {
|
||||
setAddress(evt.currentTarget.value || '');
|
||||
};
|
||||
|
||||
const getServerURL = () => {
|
||||
const { origin } = location;
|
||||
if (origin.includes('localhost')) {
|
||||
return 'http://localhost:3999';
|
||||
}
|
||||
return 'https://sidecar.staging.blockstack.xyz';
|
||||
};
|
||||
|
||||
const waitForBalance = async (currentBalance: number, attempts: number) => {
|
||||
const { balance } = await client.fetchAccount(address);
|
||||
if (attempts > 18) {
|
||||
setError(
|
||||
"It looks like your transaction still isn't confirmed after a few minutes. Something may have gone wrong."
|
||||
);
|
||||
setLoading(false);
|
||||
}
|
||||
if (balance.toNumber() > currentBalance) {
|
||||
setLoading(false);
|
||||
setSuccess(true);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
waitForBalance(currentBalance, attempts + 1);
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setTX('');
|
||||
|
||||
try {
|
||||
const url = `${getServerURL()}/sidecar/v1/debug/faucet?address=${address}`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
});
|
||||
const data: FaucetResponse = await res.json();
|
||||
console.log(data);
|
||||
if (data.txId) {
|
||||
setTX(data.txId);
|
||||
const { balance } = await client.fetchAccount(address);
|
||||
await waitForBalance(balance.toNumber(), 0);
|
||||
} else {
|
||||
setError('Something went wrong when requesting the faucet.');
|
||||
}
|
||||
} catch (e) {
|
||||
setError('Something went wrong when requesting the faucet.');
|
||||
setLoading(false);
|
||||
setTX('');
|
||||
console.error(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mb={6} maxWidth="600px" mt={6}>
|
||||
<Text as="h2" fontSize={5} mt={6}>
|
||||
Faucet
|
||||
</Text>
|
||||
<Text display="block" my={4} textStyle="caption.medium">
|
||||
Receive some free testnet STX for testing out the network. STX are required to execute smart
|
||||
contract functions.
|
||||
</Text>
|
||||
{tx && <ExplorerLink txId={tx} />}
|
||||
{error && (
|
||||
<Text display="inline-block" my={1} fontSize={1} color="red">
|
||||
{error}
|
||||
</Text>
|
||||
)}
|
||||
<Flex wrap="wrap">
|
||||
<Box width="100%">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Address"
|
||||
textStyle="body.small"
|
||||
value={address}
|
||||
onChange={handleInput}
|
||||
name="address"
|
||||
/>
|
||||
</Box>
|
||||
<Box width="100%" mt={3}>
|
||||
<Button
|
||||
isLoading={loading}
|
||||
loadingText=" Waiting for TX to Confirm"
|
||||
isDisabled={success}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
{success ? 'Faucet TX Confirmed' : 'Receive Testnet STX'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
38
packages/test-app/components/header.tsx
Normal file
38
packages/test-app/components/header.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Flex, Box, Text } from '@blockstack/ui';
|
||||
import { BlockstackIcon } from '@blockstack/ui';
|
||||
import { AppContext } from '@common/context';
|
||||
import { Link } from '@blockstack/connect';
|
||||
|
||||
interface HeaderProps {
|
||||
signOut: () => void;
|
||||
}
|
||||
|
||||
export const Header: React.FC<HeaderProps> = ({ signOut }) => {
|
||||
const state = useContext(AppContext);
|
||||
return (
|
||||
<Flex as="nav" justifyContent="space-between" alignItems="center" height="64px" px={6}>
|
||||
<Box verticalAlign="center">
|
||||
<BlockstackIcon maxHeight="26px" display="inline-block" ml="-10px" />
|
||||
<Text display="inline-block" ml={1}>
|
||||
Blockstack
|
||||
</Text>
|
||||
</Box>
|
||||
{state.userData ? (
|
||||
<Box>
|
||||
<Link
|
||||
display="inline-block"
|
||||
ml={2}
|
||||
textStyle="caption.medium"
|
||||
color="blue"
|
||||
onClick={() => {
|
||||
signOut();
|
||||
}}
|
||||
>
|
||||
Sign out
|
||||
</Link>
|
||||
</Box>
|
||||
) : null}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
81
packages/test-app/components/home.tsx
Normal file
81
packages/test-app/components/home.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { AppContext } from '@common/context';
|
||||
import { Box, Spinner, Text, Flex, space, BoxProps } from '@blockstack/ui';
|
||||
import { Auth } from './auth';
|
||||
import { Tab } from './tab';
|
||||
import { Status } from './status';
|
||||
import { Counter } from './counter';
|
||||
import { useFaucet } from '@common/use-faucet';
|
||||
import { ExplorerLink } from './explorer-link';
|
||||
|
||||
type Tabs = 'status' | 'counter';
|
||||
|
||||
const Container: React.FC<BoxProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<Box width="100%" px={6} {...props}>
|
||||
<Box maxWidth="900px" mx="auto">
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Home: React.FC = () => {
|
||||
const state = useContext(AppContext);
|
||||
const [tab, setTab] = useState<Tabs>('status');
|
||||
const faucet = useFaucet();
|
||||
|
||||
const Page: React.FC = () => {
|
||||
if (faucet.loading) {
|
||||
return (
|
||||
<Container px={6} width="100%">
|
||||
<Spinner mr={4} />
|
||||
<Text textStyle="body.large">Setting up...</Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
if (faucet.balance === 0) {
|
||||
return (
|
||||
<Container px={6} width="100%">
|
||||
<Spinner mr={4} />
|
||||
<Text textStyle="body.large" display="block">
|
||||
Please wait while we get you some Stacks tokens to test with
|
||||
</Text>
|
||||
<ExplorerLink txId={faucet.txId} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container borderColor="#F0F0F5" borderWidth={0} borderBottomWidth="1px">
|
||||
<Flex>
|
||||
<Tab active={tab === 'status'}>
|
||||
<Text onClick={() => setTab('status')}>Status smart contract</Text>
|
||||
</Tab>
|
||||
<Tab active={tab === 'counter'}>
|
||||
<Text onClick={() => setTab('counter')}>Counter smart contract</Text>
|
||||
</Tab>
|
||||
</Flex>
|
||||
</Container>
|
||||
<Container>{tab === 'status' ? <Status /> : <Counter />}</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Flex flexWrap="wrap">
|
||||
<Container mt={space('base-loose')}>
|
||||
<Text as="h1" textStyle="display.large" fontSize={7} mb={space('loose')} display="block">
|
||||
Testnet Demo
|
||||
</Text>
|
||||
</Container>
|
||||
{state.userData ? (
|
||||
<Page />
|
||||
) : (
|
||||
<Container>
|
||||
<Auth />
|
||||
</Container>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
203
packages/test-app/components/status.tsx
Normal file
203
packages/test-app/components/status.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { space, Box, Text, Button, Input, Flex } from '@blockstack/ui';
|
||||
import { ExplorerLink } from './explorer-link';
|
||||
import { useConnect } from '@blockstack/connect';
|
||||
import {
|
||||
PostConditionMode,
|
||||
standardPrincipalCV,
|
||||
BufferCV,
|
||||
deserializeCV,
|
||||
ClarityType,
|
||||
bufferCV,
|
||||
} from '@blockstack/stacks-transactions';
|
||||
import { getAuthOrigin, getRPCClient } from '@common/utils';
|
||||
import { ContractCallTransaction } from '@blockstack/stacks-blockchain-sidecar-types';
|
||||
import { TxCard } from '@components/tx-card';
|
||||
import { useSTXAddress } from '@common/use-stx-address';
|
||||
|
||||
export const Status = () => {
|
||||
const stxAddress = useSTXAddress();
|
||||
const [status, setStatus] = useState('');
|
||||
const [readStatus, setReadStatus] = useState('');
|
||||
const [address, setAddress] = useState('');
|
||||
const [txId, setTxId] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [transactions, setTransactions] = useState<ContractCallTransaction[]>([]);
|
||||
const { doContractCall } = useConnect();
|
||||
|
||||
const client = getRPCClient();
|
||||
|
||||
useEffect(() => {
|
||||
const getTransactions = async () => {
|
||||
const transactions = await client.fetchAddressTransactions({
|
||||
address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.status',
|
||||
});
|
||||
const filtered = transactions.filter(t => {
|
||||
return t.tx_type === 'contract_call';
|
||||
});
|
||||
setTransactions(filtered as ContractCallTransaction[]);
|
||||
};
|
||||
void getTransactions();
|
||||
}, []);
|
||||
|
||||
const getAddressCV = () => {
|
||||
try {
|
||||
return standardPrincipalCV(address);
|
||||
} catch (error) {
|
||||
setError('Invalid address.');
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmitRead = async () => {
|
||||
const addressCV = getAddressCV();
|
||||
if (!addressCV) {
|
||||
return;
|
||||
}
|
||||
const args = [addressCV];
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await client.callReadOnly({
|
||||
contractName: 'status',
|
||||
contractAddress: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
|
||||
args,
|
||||
functionName: 'get-status',
|
||||
});
|
||||
console.log(data);
|
||||
const cv = deserializeCV(Buffer.from(data.result.slice(2), 'hex')) as BufferCV;
|
||||
console.log(cv);
|
||||
if (cv.type === ClarityType.Buffer) {
|
||||
const ua = Array.from(cv.buffer);
|
||||
const str = String.fromCharCode.apply(null, ua);
|
||||
setReadStatus(str);
|
||||
console.log(str);
|
||||
}
|
||||
} catch (error) {
|
||||
setError('An error occurred while fetching the status contract.');
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const onSubmitWrite = async () => {
|
||||
const authOrigin = getAuthOrigin();
|
||||
const statusArg = bufferCV(Buffer.from(status));
|
||||
await doContractCall({
|
||||
authOrigin,
|
||||
contractAddress: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
|
||||
functionName: 'write-status!',
|
||||
functionArgs: [statusArg],
|
||||
contractName: 'status',
|
||||
finished: data => {
|
||||
setTxId(data.txId);
|
||||
console.log('finished!', data);
|
||||
},
|
||||
postConditionMode: PostConditionMode.Deny,
|
||||
});
|
||||
};
|
||||
|
||||
const handleStatus = (evt: React.FormEvent<HTMLInputElement>) => {
|
||||
setStatus(evt.currentTarget.value || '');
|
||||
};
|
||||
|
||||
const handleAddress = (evt: React.FormEvent<HTMLInputElement>) => {
|
||||
setAddress(evt.currentTarget.value || '');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box py={6}>
|
||||
<Text as="h2" textStyle="display.small">
|
||||
Status smart contract
|
||||
</Text>
|
||||
<Text textStyle="body.large" display="block" my={space('loose')}>
|
||||
Try a smart contract where anyone can write their public status, like a decentralized
|
||||
Twitter. You can read someone else's status by entering their address.
|
||||
</Text>
|
||||
<ExplorerLink
|
||||
txId="STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.status"
|
||||
text="View contract in explorer"
|
||||
skipConfirmCheck
|
||||
/>
|
||||
|
||||
{transactions.length > 0 && (
|
||||
<>
|
||||
<Text display="block" my={space('base-loose')} textStyle="body.large.medium">
|
||||
Latest statuses
|
||||
</Text>
|
||||
<Flex flexWrap="wrap" justifyContent="left">
|
||||
{transactions.slice(0, 3).map(t => (
|
||||
<TxCard tx={t} label={JSON.parse(t.tx_result?.repr || '')} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Text display="block" my={space('base-loose')} textStyle="body.large.medium">
|
||||
Write a status
|
||||
</Text>
|
||||
|
||||
<Box width="100%" mt={3}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Enter your status"
|
||||
textStyle="body.small"
|
||||
value={status}
|
||||
onChange={handleStatus}
|
||||
name="status"
|
||||
maxWidth="300px"
|
||||
onKeyUp={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'enter') {
|
||||
onSubmitWrite();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{txId && <ExplorerLink txId={txId} />}
|
||||
<Button my={space('base-loose')} onClick={onSubmitWrite}>
|
||||
Write status
|
||||
</Button>
|
||||
|
||||
<Text display="block" my={space('base-loose')} textStyle="body.large.medium">
|
||||
Read a status
|
||||
</Text>
|
||||
|
||||
{stxAddress && (
|
||||
<Text display="block" my={space('base-loose')} textStyle="body.small">
|
||||
If you want to read your own status, your address is {stxAddress}.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Box width="100%" mt={3}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Enter an STX address"
|
||||
textStyle="body.small"
|
||||
value={address}
|
||||
onChange={handleAddress}
|
||||
name="status"
|
||||
maxWidth="300px"
|
||||
onKeyUp={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'enter') {
|
||||
onSubmitRead();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{readStatus && (
|
||||
<Text display="block" fontWeight={600} my={3} width="100%">
|
||||
{readStatus}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Text display="block" color="red" width="100%" fontSize={1} mt={2}>
|
||||
{error}
|
||||
</Text>
|
||||
)}
|
||||
<Button my={space('base-loose')} onClick={onSubmitRead} isLoading={loading}>
|
||||
Read status
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
63
packages/test-app/components/stx-transfer.tsx
Normal file
63
packages/test-app/components/stx-transfer.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Flex, Box, Button, Input, Text } from '@blockstack/ui';
|
||||
import { useConnect } from '@blockstack/connect';
|
||||
import { ExplorerLink } from '@components/explorer-link';
|
||||
import { getAuthOrigin } from '@common/utils';
|
||||
|
||||
export const STXTransfer = () => {
|
||||
const [address, setAddress] = useState('');
|
||||
const [tx, setTX] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const { doSTXTransfer } = useConnect();
|
||||
|
||||
const handleInput = (evt: React.FormEvent<HTMLInputElement>) => {
|
||||
setAddress(evt.currentTarget.value || '');
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
setError('');
|
||||
setTX('');
|
||||
|
||||
doSTXTransfer({
|
||||
recipient: address,
|
||||
amount: '100',
|
||||
memo: 'Testing STX Transfer',
|
||||
authOrigin: getAuthOrigin(),
|
||||
finished: data => {
|
||||
setTX(data.txId);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mb={6} maxWidth="600px" mt={6}>
|
||||
<Text as="h2" fontSize={5} mt={6}>
|
||||
STX Transfer
|
||||
</Text>
|
||||
<Text display="block" my={4} textStyle="caption.medium">
|
||||
Send a small amount of STX to a different user.
|
||||
</Text>
|
||||
{tx && <ExplorerLink txId={tx} />}
|
||||
{error && (
|
||||
<Text display="inline-block" my={1} fontSize={1} color="red">
|
||||
{error}
|
||||
</Text>
|
||||
)}
|
||||
<Flex wrap="wrap">
|
||||
<Box width="100%">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Address"
|
||||
textStyle="body.small"
|
||||
value={address}
|
||||
onChange={handleInput}
|
||||
name="address"
|
||||
/>
|
||||
</Box>
|
||||
<Box width="100%" mt={3}>
|
||||
<Button onClick={onSubmit}>Submit</Button>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
29
packages/test-app/components/tab.tsx
Normal file
29
packages/test-app/components/tab.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Box, BoxProps } from '@blockstack/ui';
|
||||
|
||||
export const InactiveTab: React.FC = ({ children }) => {
|
||||
return (
|
||||
<Box color="ink.400" mr={6} py={3} borderColor="white" borderBottomWidth="2px" cursor="pointer">
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const ActiveTab: React.FC = ({ children }) => {
|
||||
return (
|
||||
<Box color="ink.900" mr={6} py={3} borderColor="blue" borderBottomWidth="2px" cursor="pointer">
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
interface TabProps extends BoxProps {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export const Tab: React.FC<TabProps> = ({ active, ...rest }) => {
|
||||
if (active) {
|
||||
return <ActiveTab {...rest} />;
|
||||
}
|
||||
return <InactiveTab {...rest} />;
|
||||
};
|
||||
44
packages/test-app/components/tx-card.tsx
Normal file
44
packages/test-app/components/tx-card.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { Box, Flex, Text } from '@blockstack/ui';
|
||||
import { ContractCallTransaction } from '@blockstack/stacks-blockchain-sidecar-types';
|
||||
import { toRelativeTime } from '@common/utils';
|
||||
|
||||
interface TxCardProps {
|
||||
tx: ContractCallTransaction;
|
||||
label: string;
|
||||
}
|
||||
export const TxCard: React.FC<TxCardProps> = ({ tx, label }) => {
|
||||
const addr = tx.sender_address;
|
||||
const shortAddr = `${addr.slice(0, 5)}...${addr.slice(addr.length - 6, addr.length - 1)}`;
|
||||
return (
|
||||
<Box
|
||||
flex="0 1 280px"
|
||||
mr="10px"
|
||||
mt={3}
|
||||
borderColor="#F0F0F5"
|
||||
borderWidth="1px"
|
||||
borderRadius="12px"
|
||||
p={6}
|
||||
_hover={{
|
||||
borderColor: 'ink.400',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
const url = `https://testnet-explorer.blockstack.org/txid/${tx.tx_id}`;
|
||||
window.open(url, '_blank');
|
||||
}}
|
||||
>
|
||||
<Flex>
|
||||
<Box>
|
||||
<Text color="ink.600">{shortAddr}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} textAlign="right">
|
||||
<Text color="ink.600">{toRelativeTime(tx.burn_block_time * 1000)}</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Text display="block" textStyle="body.large" mt={3}>
|
||||
{label}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
16
packages/test-app/contracts/counter.clar
Normal file
16
packages/test-app/contracts/counter.clar
Normal file
@@ -0,0 +1,16 @@
|
||||
(define-data-var counter int 0)
|
||||
|
||||
(define-public (increment)
|
||||
(begin
|
||||
(var-set counter (+ (var-get counter) 1))
|
||||
(print "+1")
|
||||
(ok (var-get counter))))
|
||||
|
||||
(define-public (decrement)
|
||||
(begin
|
||||
(var-set counter (- (var-get counter) 1))
|
||||
(print "-1")
|
||||
(ok (var-get counter))))
|
||||
|
||||
(define-read-only (get-counter)
|
||||
(var-get counter))
|
||||
31
packages/test-app/contracts/status.clar
Normal file
31
packages/test-app/contracts/status.clar
Normal file
@@ -0,0 +1,31 @@
|
||||
(define-map statuses
|
||||
(
|
||||
(author principal)
|
||||
)
|
||||
(
|
||||
(status (buff 512))
|
||||
)
|
||||
)
|
||||
|
||||
(define-read-only (get-status (author principal))
|
||||
(begin
|
||||
(print author)
|
||||
(default-to ""
|
||||
(get status (map-get? statuses {author: author}))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(define-public (write-status!
|
||||
(status (buff 512))
|
||||
)
|
||||
(begin
|
||||
(print tx-sender)
|
||||
(print status)
|
||||
(map-set statuses
|
||||
((author tx-sender))
|
||||
((status status))
|
||||
)
|
||||
(ok status)
|
||||
)
|
||||
)
|
||||
51
packages/test-app/contracts/stream.clar
Normal file
51
packages/test-app/contracts/stream.clar
Normal file
@@ -0,0 +1,51 @@
|
||||
;; hello world
|
||||
|
||||
(define-public (say-hi)
|
||||
(ok "hello world")
|
||||
)
|
||||
|
||||
(define-public (echo-number (val int))
|
||||
(ok val)
|
||||
)
|
||||
|
||||
;; streams
|
||||
|
||||
(define-map streams
|
||||
((id uint))
|
||||
(
|
||||
(start-block uint)
|
||||
(recipient principal)
|
||||
(sender principal)
|
||||
)
|
||||
)
|
||||
|
||||
(define-data-var next-stream-id uint u1)
|
||||
|
||||
(define-public (make-stream
|
||||
(recipient principal)
|
||||
)
|
||||
(begin
|
||||
(map-set streams
|
||||
((id (var-get next-stream-id)))
|
||||
(
|
||||
(start-block block-height)
|
||||
(recipient recipient)
|
||||
(sender tx-sender)
|
||||
)
|
||||
)
|
||||
(var-set next-stream-id (+ (var-get next-stream-id) u1))
|
||||
(ok 'true)
|
||||
)
|
||||
)
|
||||
|
||||
(define-public (get-stream
|
||||
(stream-id uint)
|
||||
)
|
||||
(
|
||||
ok
|
||||
(unwrap-panic
|
||||
(map-get? streams (tuple (id stream-id)))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
185
packages/test-app/jest.config.js
Normal file
185
packages/test-app/jest.config.js
Normal file
@@ -0,0 +1,185 @@
|
||||
// For a detailed explanation regarding each configuration property, visit:
|
||||
// https://jestjs.io/docs/en/configuration.html
|
||||
|
||||
module.exports = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// Respect "browser" field in package.json when resolving modules
|
||||
// browser: false,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "C:\\Users\\Boobalay\\AppData\\Local\\Temp\\jest",
|
||||
|
||||
// Automatically clear mock calls and instances between every test
|
||||
// clearMocks: false,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
// collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
collectCoverageFrom: ['src/**/*.{ts,tsx}'],
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
// coverageDirectory: null,
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\"
|
||||
// ],
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: null,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: null,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files usin a array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: null,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: null,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
// https://huafu.github.io/ts-jest/user/config/diagnostics
|
||||
diagnostics: false,
|
||||
},
|
||||
},
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node', 'clar'],
|
||||
|
||||
// A map from regular expressions to module names that allow to stub out resources with a single module
|
||||
moduleNameMapper: {
|
||||
'^@blockstack/keychain': '<rootDir>/../keychain/src',
|
||||
},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: null,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: null,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: null,
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: null,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
roots: ['<rootDir>/tests', '<rootDir>/src', '<rootDir>/contracts'],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
// testEnvironment: 'jsdom',
|
||||
|
||||
preset: 'ts-jest',
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
// testMatch: [
|
||||
// "**/tests/**/*.[jt]s?(x)",
|
||||
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
testRegex: '/tests/.*.test.tsx?$',
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: null,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
testRunner: "jest-circus/runner",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
transform: { '^.+\\.tsx?$': 'ts-jest' },
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: null,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
};
|
||||
@@ -27,12 +27,14 @@
|
||||
"@blockstack/keychain": "^0.7.5",
|
||||
"@blockstack/prettier-config": "^0.0.6",
|
||||
"@blockstack/ui": "^2.9.5",
|
||||
"@blockstack/clarity": "0.1.16",
|
||||
"@blockstack/rpc-client": "^0.3.0-alpha.0",
|
||||
"@rehooks/document-title": "^1.0.1",
|
||||
"blockstack": "^21.0.0",
|
||||
"blockstack": "21.0.0",
|
||||
"dayjs": "^1.8.29",
|
||||
"formik": "^2.1.4",
|
||||
"mdi-react": "^7.0.0",
|
||||
"preact": "^10.4.0",
|
||||
"prettier": "^2.0.5",
|
||||
"react": "^16.13.1",
|
||||
"react-chrome-redux": "^2.0.0-alpha.5",
|
||||
"react-dom": "^16.13.1",
|
||||
@@ -57,6 +59,9 @@
|
||||
"@babel/preset-react": "^7.9.4",
|
||||
"@babel/preset-typescript": "^7.9.0",
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"@blockstack/clarity-native-bin": "0.1.13-alpha.6",
|
||||
"@blockstack/prettier-config": "^0.0.6",
|
||||
"@blockstack/stacks-blockchain-sidecar-types": "^0.0.19",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.2.0",
|
||||
"@schemastore/web-manifest": "^0.0.4",
|
||||
"@types/chrome": "^0.0.104",
|
||||
@@ -77,19 +82,19 @@
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"cross-env": "^7.0.0",
|
||||
"eslint": "^6.7.2",
|
||||
"dotenv": "^8.2.0",
|
||||
"eslint": "^7.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "^4.1.3",
|
||||
"html-webpack-plugin": "^4.2.0",
|
||||
"jest": "^25.3.0",
|
||||
"jest-puppeteer": "^4.3.0",
|
||||
"prettier": "^2.0.4",
|
||||
"prettier": "^2.0.5",
|
||||
"puppeteer": "^3.0.0",
|
||||
"react-dev-utils": "^10.2.1",
|
||||
"react-test-renderer": "^16.13.1",
|
||||
"terser-webpack-plugin": "^2.3.5",
|
||||
"ts-jest": "^25.3.1",
|
||||
"ts-loader": "^7.0.0",
|
||||
"typescript": "3.8.3",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-bundle-analyzer": "^3.7.0",
|
||||
"webpack-chrome-extension-reloader": "^1.3.0",
|
||||
|
||||
86
packages/test-app/tests/contracts.test.ts
Normal file
86
packages/test-app/tests/contracts.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Client, Provider, ProviderRegistry, Result, ResultInterface } from '@blockstack/clarity';
|
||||
|
||||
function unwrapOkResult(input: ResultInterface<string, unknown>): string {
|
||||
const { result } = input;
|
||||
if (!result) {
|
||||
throw new Error('Unable to parse result');
|
||||
}
|
||||
const match = /^\(ok\s0x(\w+)\)$/.exec(result);
|
||||
// const res = match[1];
|
||||
if (!match) {
|
||||
throw new Error('Unable to parse result');
|
||||
}
|
||||
return Buffer.from(match[1], 'hex').toString();
|
||||
}
|
||||
|
||||
describe('hello world contract test suite', () => {
|
||||
let helloWorldClient: Client;
|
||||
let provider: Provider;
|
||||
|
||||
const addresses = [
|
||||
'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7',
|
||||
'S02J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKPVKG2CE',
|
||||
'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR',
|
||||
];
|
||||
// const alice = addresses[0];
|
||||
const [alice, bob] = addresses;
|
||||
|
||||
beforeEach(async () => {
|
||||
provider = await ProviderRegistry.createProvider();
|
||||
helloWorldClient = new Client(
|
||||
'SP3GWX3NE58KXHESRYE4DYQ1S31PQJTCRXB3PE9SB.stream',
|
||||
'stream',
|
||||
provider
|
||||
);
|
||||
});
|
||||
|
||||
it('should have a valid syntax', async () => {
|
||||
await expect(helloWorldClient.checkContract()).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
describe('deploying an instance of the contract', () => {
|
||||
beforeEach(async () => {
|
||||
await helloWorldClient.deployContract();
|
||||
});
|
||||
it('should hello world', async () => {
|
||||
const query = helloWorldClient.createQuery({
|
||||
method: {
|
||||
name: 'say-hi',
|
||||
args: [],
|
||||
},
|
||||
});
|
||||
const receipt = await helloWorldClient.submitQuery(query);
|
||||
const result = unwrapOkResult(receipt);
|
||||
expect(result).toEqual('hello world');
|
||||
});
|
||||
|
||||
it('should create a stream', async () => {
|
||||
const tx = helloWorldClient.createTransaction({
|
||||
method: {
|
||||
name: 'make-stream',
|
||||
args: [`'${bob}`],
|
||||
},
|
||||
});
|
||||
await tx.sign(alice);
|
||||
const receipt = await helloWorldClient.submitTransaction(tx);
|
||||
const result = Result.unwrap(receipt);
|
||||
expect(result).toBeTruthy();
|
||||
const query = helloWorldClient.createQuery({
|
||||
method: {
|
||||
name: 'get-stream',
|
||||
args: ['u1'],
|
||||
},
|
||||
});
|
||||
const queryReceipt = await helloWorldClient.submitQuery(query);
|
||||
const queryResult = Result.unwrap(queryReceipt);
|
||||
// console.log(queryResult);
|
||||
expect(queryResult).toEqual(
|
||||
`(ok (tuple (recipient '${bob}) (sender '${alice}) (start-block u2)))`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await provider.close();
|
||||
});
|
||||
});
|
||||
@@ -46,14 +46,16 @@
|
||||
"@store/*": ["store/*"],
|
||||
"@store": ["store/index"],
|
||||
"@dev/*": ["dev/*"],
|
||||
"@components/*": ["components/*"],
|
||||
"@components/*": ["../components/*"],
|
||||
"@containers/*": ["containers/*"],
|
||||
"@common/*": ["common/*"],
|
||||
"@common/*": ["../common/*"],
|
||||
"@blockstack/connect": ["../../connect/src"],
|
||||
"@blockstack/connect/*": ["../../connect/src/*"],
|
||||
"@blockstack/ui": ["../../ui/src"],
|
||||
"@blockstack/keychain": ["../../keychain/src"],
|
||||
"@blockstack/keychain/*": ["../../keychain/src/*"],
|
||||
"@blockstack/keychain/*": ["../../keychain/src/*"],
|
||||
"@cards/*": ["../components/cards/*"],
|
||||
"@blockstack/rpc-client": ["../../rpc-client/src"]
|
||||
},
|
||||
"baseUrl": "src",
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
@@ -75,6 +77,8 @@
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*",
|
||||
"./tests/**/*"
|
||||
"./tests/**/*",
|
||||
"./components/**/*",
|
||||
"./common/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -189,11 +189,6 @@ module.exports = {
|
||||
to: path.join(distRootPath, 'assets'),
|
||||
test: /\.(jpg|jpeg|png|gif|svg)?$/,
|
||||
},
|
||||
{
|
||||
from: path.join(sourceRootPath, 'manifest.json'),
|
||||
to: path.join(distRootPath, 'manifest.json'),
|
||||
toType: 'file',
|
||||
},
|
||||
]),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.AUTH_ORIGIN': JSON.stringify(process.env.AUTH_ORIGIN),
|
||||
|
||||
@@ -69,8 +69,7 @@
|
||||
"ts-node": "^8.8.2",
|
||||
"tsdx": "^0.13.2",
|
||||
"tslint": "^6.1.1",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "^3.7.5"
|
||||
"tslint-config-prettier": "^1.18.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
14
packages/ui/src/icons/external-icon.tsx
Normal file
14
packages/ui/src/icons/external-icon.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import { Svg } from '../svg';
|
||||
import { BoxProps } from '../box';
|
||||
|
||||
export const ExternalIcon = (props: BoxProps) => (
|
||||
<Svg width="8" height="9" viewBox="0 0 8 9" fill="none" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1.30849 0H7.04391H7.99975H7.99981V6.6919H7.04391V1.66447L0.707141 8.00185L0 7.29477L6.33818 0.955986H1.30849V0Z"
|
||||
fill="#677282"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
23
packages/ui/src/icons/failed-icon.tsx
Normal file
23
packages/ui/src/icons/failed-icon.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import { Svg } from '../svg';
|
||||
import { BoxProps } from '../box';
|
||||
|
||||
export const FailedIcon: React.FC<BoxProps> = ({ size = 64, ...props }) => (
|
||||
<Svg width={size} height={size} viewBox="0 0 64 64" fill="none" {...props}>
|
||||
<circle cx="32" cy="32" r="30" stroke="#D4001A" strokeWidth="4" strokeLinecap="round" />
|
||||
<path
|
||||
d="M23 41L40.9995 23"
|
||||
stroke="#D4001A"
|
||||
strokeWidth="4"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M41 41L23.0005 23"
|
||||
stroke="#D4001A"
|
||||
strokeWidth="4"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
@@ -15,3 +15,5 @@ export * from './plus-circle-icon';
|
||||
export * from './private-icon';
|
||||
export * from './union-line-icon';
|
||||
export * from './close-icon';
|
||||
export * from './external-icon';
|
||||
export * from './failed-icon';
|
||||
|
||||
@@ -63,7 +63,9 @@ export function Tooltip(props: TooltipProps) {
|
||||
|
||||
const baseTooltipProps = getTooltipProps(rest);
|
||||
|
||||
const tooltipProps = hasAriaLabel ? omit(baseTooltipProps, ['role', 'id']) : baseTooltipProps;
|
||||
const { style, ...tooltipProps } = hasAriaLabel
|
||||
? omit(baseTooltipProps, ['role', 'id'])
|
||||
: baseTooltipProps;
|
||||
|
||||
const hiddenProps = pick(baseTooltipProps, ['role', 'id']);
|
||||
|
||||
@@ -82,7 +84,7 @@ export function Tooltip(props: TooltipProps) {
|
||||
maxWidth="320px"
|
||||
{...tooltipProps}
|
||||
style={{
|
||||
...tooltipProps.style,
|
||||
...style,
|
||||
useSelect: 'none',
|
||||
}}
|
||||
>
|
||||
|
||||
598
yarn.lock
598
yarn.lock
@@ -1598,6 +1598,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@blockstack/clarity-native-bin@0.1.13-alpha.6":
|
||||
version "0.1.13-alpha.6"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/clarity-native-bin/-/clarity-native-bin-0.1.13-alpha.6.tgz#f2d8c905d4b2b6aac561aa4cf16159ad7747b214"
|
||||
integrity sha512-iQeSn5/UmiRGz631Hqf+oA8x77++eIqzjHDgZT40GNsuFEduYwUnxQbsRqq7q0yJpkemcqd9bUNLv33qQRGoXA==
|
||||
dependencies:
|
||||
fs-extra "^8.0.1"
|
||||
node-fetch "^2.6.0"
|
||||
semver "^6.1.1"
|
||||
tar "^4.4.8"
|
||||
|
||||
"@blockstack/clarity@0.1.16":
|
||||
version "0.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/clarity/-/clarity-0.1.16.tgz#75483365bc28d25eabb6b95df4ebafa5f67954fb"
|
||||
integrity sha512-3XuPsQCz+wfSQHLVGhdfgiCVsZh2mkSZpDiV+TguoYOWtzAOZJQSoL0CdAkri0rDe5r+AM4s57lpukqgraRTaA==
|
||||
|
||||
"@blockstack/eslint-config@^1.0.3", "@blockstack/eslint-config@^1.0.5":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/eslint-config/-/eslint-config-1.0.5.tgz#a745661123c5e6c2682b5eb7400a4bc72390959d"
|
||||
@@ -1632,10 +1647,15 @@
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/prettier-config/-/prettier-config-0.0.6.tgz#8a41cd378ba061b79770987f2a6ad0c92b64bd72"
|
||||
integrity sha512-ke0MdyblmoUqSJBEutsG8/6G7KAjCB+uOcgZHPJvJr4R+i5yRhT4GSe5nV/wREINuK0jj2GvaA6qlx4PQTKQUA==
|
||||
|
||||
"@blockstack/stacks-transactions@^0.4.6":
|
||||
version "0.4.6"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/stacks-transactions/-/stacks-transactions-0.4.6.tgz#b774250fbaadbadf42313a82fbd628b1728cd6ed"
|
||||
integrity sha512-3Hb+v0ZmG5bVZHasfM9KzlwK+2e5r6oKsKk0eRgavLb5bBMQy/cw0YYoUWmt+ipNqDP5ssZgoOA1KKRhoSWvXg==
|
||||
"@blockstack/stacks-blockchain-sidecar-types@^0.0.19":
|
||||
version "0.0.19"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/stacks-blockchain-sidecar-types/-/stacks-blockchain-sidecar-types-0.0.19.tgz#569ed8e94a00c62b1bbfec806d2e06cade9ef464"
|
||||
integrity sha512-xuXw+TOzxqC/Bcj2Ob7Svp5ukDaKVuoV5OiNDaC3mF4rkt+HvKxj1loGmPrsIT0mbsjHTf0pxfHuEZvvugZdQw==
|
||||
|
||||
"@blockstack/stacks-transactions@0.5.1":
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/stacks-transactions/-/stacks-transactions-0.5.1.tgz#b58057d52567406ce26669ba0abcaf583cf2d21c"
|
||||
integrity sha512-szzDyRBHnPDE7hoqm6TJbKvUPE5aHRapS4gqiDetOwZzq/aDqIrFVHSgU1GM7m1d2q2jlN1knW4IiRJUeCxw0Q==
|
||||
dependencies:
|
||||
"@types/bn.js" "^4.11.6"
|
||||
"@types/elliptic" "^6.4.12"
|
||||
@@ -2255,6 +2275,16 @@
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^13.0.0"
|
||||
|
||||
"@jest/types@^25.2.6":
|
||||
version "25.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.2.6.tgz#c12f44af9bed444438091e4b59e7ed05f8659cb6"
|
||||
integrity sha512-myJTTV37bxK7+3NgKc4Y/DlQ5q92/NOwZsZ+Uch7OXdElxOg61QYc72fPYNAjlvbnJ2YvbXLamIsa9tj48BmyQ==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^3.0.0"
|
||||
|
||||
"@jest/types@^25.5.0":
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
|
||||
@@ -3208,12 +3238,12 @@
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@reach/auto-id@^0.10.0":
|
||||
version "0.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.10.4.tgz#ba9c0c96c3fd037f2d3c70e9e2e6a784a83abfab"
|
||||
integrity sha512-hJIjqOBIYdIdrjefWYfuBAntrUSP2sRp5jj3rJNSXW/Txhv6NUhfk5z5Xwsi6HST9rBS15btM8YaQBvJYicZsQ==
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.10.1.tgz#5c8f7573d6271e0e55df04e459a1d1d1cf11a216"
|
||||
integrity sha512-xGFW2v+L39M/mafdW7v+NhhsjT1LBnQJCGj64dm37T4IGNgAexlfMkRRwsqHOvuVvV38mR114YOy0xrlkqduRQ==
|
||||
dependencies:
|
||||
"@reach/utils" "0.10.4"
|
||||
tslib "^2.0.0"
|
||||
"@reach/utils" "^0.10.1"
|
||||
tslib "^1.11.1"
|
||||
|
||||
"@reach/observe-rect@1.1.0":
|
||||
version "1.1.0"
|
||||
@@ -3284,6 +3314,15 @@
|
||||
tslib "^2.0.0"
|
||||
warning "^4.0.3"
|
||||
|
||||
"@reach/utils@^0.10.1":
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.10.1.tgz#ee0283f81e161db4126a943b5c908a69f6a66d7e"
|
||||
integrity sha512-YzwZWVK+rSiUATNVtK7H2/ZkT/GhNKmkRjnj3hnVhSYLGxY9uQdfc+npetOqkh4hTAOXiErDa64ybVClR3h0TA==
|
||||
dependencies:
|
||||
"@types/warning" "^3.0.0"
|
||||
tslib "^1.11.1"
|
||||
warning "^4.0.3"
|
||||
|
||||
"@reach/visually-hidden@0.10.4":
|
||||
version "0.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.10.4.tgz#ab390db0adf759393af4d856f84375468b1df676"
|
||||
@@ -3330,11 +3369,11 @@
|
||||
resolve "^1.11.1"
|
||||
|
||||
"@rollup/plugin-node-resolve@^7.1.0":
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca"
|
||||
integrity sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.1.tgz#8c6e59c4b28baf9d223028d0e450e06a485bb2b7"
|
||||
integrity sha512-14ddhD7TnemeHE97a4rLOhobfYvUVcaYuqTnL8Ti7Jxi9V9Jr5LY7Gko4HZ5k4h4vqQM0gBQt6tsp9xXW94WPA==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^3.0.8"
|
||||
"@rollup/pluginutils" "^3.0.6"
|
||||
"@types/resolve" "0.0.8"
|
||||
builtin-modules "^3.1.0"
|
||||
is-module "^1.0.0"
|
||||
@@ -3348,7 +3387,14 @@
|
||||
"@rollup/pluginutils" "^3.0.8"
|
||||
magic-string "^0.25.5"
|
||||
|
||||
"@rollup/pluginutils@^3.0.0", "@rollup/pluginutils@^3.0.8":
|
||||
"@rollup/pluginutils@^3.0.0", "@rollup/pluginutils@^3.0.6":
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.0.8.tgz#4e94d128d94b90699e517ef045422960d18c8fde"
|
||||
integrity sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw==
|
||||
dependencies:
|
||||
estree-walker "^1.0.1"
|
||||
|
||||
"@rollup/pluginutils@^3.0.8":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
|
||||
integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
|
||||
@@ -3518,7 +3564,7 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/bn.js@*", "@types/bn.js@^4.11.5", "@types/bn.js@^4.11.6":
|
||||
"@types/bn.js@*", "@types/bn.js@^4.11.6":
|
||||
version "4.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
|
||||
integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
|
||||
@@ -3670,9 +3716,9 @@
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/jest-environment-puppeteer@^4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest-environment-puppeteer/-/jest-environment-puppeteer-4.3.1.tgz#597d16fa0594a5daf1b3c08262bfbb011e146c2d"
|
||||
integrity sha512-e7G12WRw525gsiz7NW3tKY+YWyNR08r3QvyC31rzMrnn7CkyGbsiskBjnR3koY/6jwIASgxO5knm4xJQ1KtbQQ==
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest-environment-puppeteer/-/jest-environment-puppeteer-4.3.2.tgz#630ad931d433b8197d29e0c6cb42a9faa91f591e"
|
||||
integrity sha512-QVR49cGaQMOrWRN7CXlvtPMuVAxa3Z+W3APxhWoSQLG/lvz1y03ECPvS7Y9eK+hgfndK+39400rO6IifDJV9YA==
|
||||
dependencies:
|
||||
"@jest/environment" "^24"
|
||||
"@jest/fake-timers" "^24"
|
||||
@@ -3714,9 +3760,9 @@
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/lodash@^4.14.149":
|
||||
version "4.14.156"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.156.tgz#cbe30909c89a1feeb7c60803e785344ea0ec82d1"
|
||||
integrity sha512-l2AgHXcKUwx2DsvP19wtRPqZ4NkONjmorOdq4sMcxIjqdIuuV/ULo2ftuv4NUpevwfW7Ju/UKLqo0ZXuEt/8lQ==
|
||||
version "4.14.149"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
|
||||
integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==
|
||||
|
||||
"@types/mdast@^3.0.0":
|
||||
version "3.0.3"
|
||||
@@ -4006,19 +4052,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
|
||||
integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=
|
||||
|
||||
"@types/webpack-sources@*":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-1.4.0.tgz#e58f1f05f87d39a5c64cf85705bdbdbb94d4d57e"
|
||||
integrity sha512-c88dKrpSle9BtTqR6ifdaxu1Lvjsl3C5OsfvuUbUwdXymshv1TkufUAXBajCCUM/f/TmnkZC/Esb03MinzSiXQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/source-list-map" "*"
|
||||
source-map "^0.7.3"
|
||||
|
||||
"@types/webpack-sources@^0.1.5":
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.8.tgz#078d75410435993ec8a0a2855e88706f3f751f81"
|
||||
integrity sha512-JHB2/xZlXOjzjBB6fMOpH1eQAfsrpqVVIbneE0Rok16WXwFaznaI5vfg75U5WgGJm7V9W1c4xeRQDjX/zwvghA==
|
||||
"@types/webpack-sources@*", "@types/webpack-sources@^0.1.5":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.6.tgz#3d21dfc2ec0ad0c77758e79362426a9ba7d7cbcb"
|
||||
integrity sha512-FtAWR7wR5ocJ9+nP137DV81tveD/ZgB1sadnJ/axUGM3BUVfRPx8oQNMtv3JNfTeHx3VP7cXiyfR/jmtEsVHsQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/source-list-map" "*"
|
||||
@@ -5276,6 +5313,11 @@ big.js@^5.2.2:
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
|
||||
|
||||
bignumber.js@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075"
|
||||
integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==
|
||||
|
||||
binary-extensions@^1.0.0:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
|
||||
@@ -5333,10 +5375,10 @@ bitcoin-ops@^1.3.0, bitcoin-ops@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz#e45de620398e22fd4ca6023de43974ff42240278"
|
||||
integrity sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==
|
||||
|
||||
bitcoinjs-lib@^5.1.2, bitcoinjs-lib@^5.1.6:
|
||||
version "5.1.10"
|
||||
resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-5.1.10.tgz#91ec7cde308007008b22ed9117c5f26970cbad54"
|
||||
integrity sha512-CesUqtBtnYc+SOMsYN9jWQWhdohW1MpklUkF7Ukn4HiAyN6yxykG+cIJogfRt6x5xcgH87K1Q+Mnoe/B+du1Iw==
|
||||
bitcoinjs-lib@^5.1.6:
|
||||
version "5.1.7"
|
||||
resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-5.1.7.tgz#dfa023d6ad887eaef8249513d708c9ecd2673a08"
|
||||
integrity sha512-sNlTQuvhaoIjOdIdyENsX74Dlikv7l6AzO0/uZQscuvfBID6aMANoCz1rooCTH5upTV5rKCj4z3BXBmXJxq23g==
|
||||
dependencies:
|
||||
bech32 "^1.1.2"
|
||||
bip174 "^1.0.1"
|
||||
@@ -5363,60 +5405,10 @@ bl@^4.0.1:
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
blockstack@21.0.0-alpha.2:
|
||||
version "21.0.0-alpha.2"
|
||||
resolved "https://registry.yarnpkg.com/blockstack/-/blockstack-21.0.0-alpha.2.tgz#1f223387df24b5770d5e7ec4031df52b8014ca11"
|
||||
integrity sha512-I7FQTYU78H/Eok/is0G79dSaQ7tD0DuKGKRCihHDdAKHYGeiTIZyHQ8fyK3NL8yEoRzRoIlCPEefCQgh/no6hg==
|
||||
dependencies:
|
||||
"@types/cheerio" "^0.22.13"
|
||||
"@types/elliptic" "^6.4.10"
|
||||
"@types/node" "^12.7.12"
|
||||
"@types/randombytes" "^2.0.0"
|
||||
ajv "^4.11.5"
|
||||
bip39 "^3.0.2"
|
||||
bitcoinjs-lib "^5.1.6"
|
||||
bn.js "^4.11.8"
|
||||
cross-fetch "^3.0.4"
|
||||
elliptic "^6.5.1"
|
||||
form-data "^2.5.1"
|
||||
jsontokens "3.0.0-alpha.0"
|
||||
query-string "^6.8.3"
|
||||
randombytes "^2.1.0"
|
||||
request "^2.88.0"
|
||||
ripemd160-min "0.0.5"
|
||||
schema-inspector "^1.6.8"
|
||||
tslib "^1.10.0"
|
||||
uuid "^3.3.3"
|
||||
zone-file "^1.0.0"
|
||||
|
||||
blockstack@^19.3.0:
|
||||
version "19.3.0"
|
||||
resolved "https://registry.yarnpkg.com/blockstack/-/blockstack-19.3.0.tgz#19b3deefcb5555b65f55302326646db42b13e4a8"
|
||||
integrity sha512-P/HRS5n+buTeIssxs1v479EpDZOFGpfiivRrv9UjbHj/FdSJLxC1onVD8Hiyfm0mB8y7Ah9qT2lGqKX9P6r7+g==
|
||||
dependencies:
|
||||
"@types/bn.js" "^4.11.5"
|
||||
"@types/elliptic" "^6.4.9"
|
||||
ajv "^4.11.5"
|
||||
bip39 "^3.0.2"
|
||||
bitcoinjs-lib "^5.1.2"
|
||||
bn.js "^4.11.8"
|
||||
cheerio "^0.22.0"
|
||||
cross-fetch "^2.2.2"
|
||||
elliptic "^6.4.1"
|
||||
form-data "^2.3.3"
|
||||
jsontokens "^2.0.2"
|
||||
query-string "^6.3.0"
|
||||
request "^2.88.0"
|
||||
ripemd160 "^2.0.2"
|
||||
schema-inspector "^1.6.8"
|
||||
triplesec "^3.0.26"
|
||||
uuid "^3.3.2"
|
||||
zone-file "^1.0.0"
|
||||
|
||||
blockstack@^21.0.0:
|
||||
version "21.1.0"
|
||||
resolved "https://registry.yarnpkg.com/blockstack/-/blockstack-21.1.0.tgz#4c0b2678647f7c697efe98f50e24482a1dc0cf09"
|
||||
integrity sha512-K3n161dRhDqBPzSe1gbg0+O7Xd5u00p6Ort2O+DAPMqs3aIy6XI/NhJgs7pestetL/iaGhaJ9EdDUHP4nU4zVQ==
|
||||
blockstack@21.0.0:
|
||||
version "21.0.0"
|
||||
resolved "https://registry.yarnpkg.com/blockstack/-/blockstack-21.0.0.tgz#28dcf8ecf4878c0273697dee33d4585d5876bdc9"
|
||||
integrity sha512-KH/eg3vlMZc93T4+rIjGwnSROqye/WOr46cDCrcnmkKdeUd2D2i/g5L6S9zTzIWQGQ7HWWLS1FZ9Xk8GcJdBKg==
|
||||
dependencies:
|
||||
"@types/bn.js" "^4.11.6"
|
||||
"@types/cheerio" "^0.22.13"
|
||||
@@ -5457,9 +5449,9 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.8, bn.js@^4.4.0:
|
||||
integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
|
||||
|
||||
bn.js@^5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0"
|
||||
integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.1.tgz#48efc4031a9c4041b9c99c6941d903463ab62eb5"
|
||||
integrity sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA==
|
||||
|
||||
body-parser@1.19.0:
|
||||
version "1.19.0"
|
||||
@@ -5634,7 +5626,7 @@ browserslist@4.10.0:
|
||||
node-releases "^1.1.52"
|
||||
pkg-up "^3.1.0"
|
||||
|
||||
browserslist@4.12.0, browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.12.0, browserslist@^4.8.5:
|
||||
browserslist@4.12.0, browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.12.0:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d"
|
||||
integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==
|
||||
@@ -5653,15 +5645,14 @@ browserslist@4.7.0:
|
||||
electron-to-chromium "^1.3.247"
|
||||
node-releases "^1.1.29"
|
||||
|
||||
browserslist@^4.9.1:
|
||||
version "4.12.2"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.2.tgz#76653d7e4c57caa8a1a28513e2f4e197dc11a711"
|
||||
integrity sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw==
|
||||
browserslist@^4.8.5, browserslist@^4.9.1:
|
||||
version "4.9.1"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.9.1.tgz#01ffb9ca31a1aef7678128fc6a2253316aa7287c"
|
||||
integrity sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001088"
|
||||
electron-to-chromium "^1.3.483"
|
||||
escalade "^3.0.1"
|
||||
node-releases "^1.1.58"
|
||||
caniuse-lite "^1.0.30001030"
|
||||
electron-to-chromium "^1.3.363"
|
||||
node-releases "^1.1.50"
|
||||
|
||||
bs-logger@0.x:
|
||||
version "0.2.6"
|
||||
@@ -5737,16 +5728,7 @@ buffer-xor@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
|
||||
integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
|
||||
|
||||
buffer@^4.3.0:
|
||||
version "4.9.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
|
||||
integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
buffer@^5.2.1, buffer@^5.5.0:
|
||||
buffer@5.6.0, buffer@^4.3.0, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
|
||||
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
|
||||
@@ -5994,15 +5976,20 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001043:
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30001035:
|
||||
version "1.0.30001087"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001087.tgz#4a0bdc5998a114fcf8b7954e7ba6c2c29831c54a"
|
||||
integrity sha512-KAQRGtt+eGCQBSp2iZTQibdCf9oe6cNTi5lmpsW38NnxP4WMYzfU6HCRmh4kJyh6LrTM9/uyElK4xcO93kafpg==
|
||||
|
||||
caniuse-lite@^1.0.30001088:
|
||||
version "1.0.30001090"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001090.tgz#ff7766332f60e80fea4903f30d360622e5551850"
|
||||
integrity sha512-QzPRKDCyp7RhjczTPZaqK3CjPA5Ht2UnXhZhCI4f7QiB5JK6KEuZBxIzyWnB3wO4hgAj4GMRxAhuiacfw0Psjg==
|
||||
caniuse-lite@^1.0.30001030:
|
||||
version "1.0.30001093"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001093.tgz#833e80f64b1a0455cbceed2a4a3baf19e4abd312"
|
||||
integrity sha512-0+ODNoOjtWD5eS9aaIpf4K0gQqZfILNY4WSNuYzeT1sXni+lMrrVjc0odEobJt6wrODofDZUX8XYi/5y7+xl8g==
|
||||
|
||||
caniuse-lite@^1.0.30001043:
|
||||
version "1.0.30001084"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001084.tgz#00e471931eaefbeef54f46aa2203914d3c165669"
|
||||
integrity sha512-ftdc5oGmhEbLUuMZ/Qp3mOpzfZLCxPYKcvGv6v2dJJ+8EdqcvZRbAGOiLmkM/PV1QGta/uwBs8/nCl6sokDW6w==
|
||||
|
||||
capture-exit@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -6108,28 +6095,6 @@ check-types@^8.0.3:
|
||||
resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
|
||||
integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==
|
||||
|
||||
cheerio@^0.22.0:
|
||||
version "0.22.0"
|
||||
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e"
|
||||
integrity sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=
|
||||
dependencies:
|
||||
css-select "~1.2.0"
|
||||
dom-serializer "~0.1.0"
|
||||
entities "~1.1.1"
|
||||
htmlparser2 "^3.9.1"
|
||||
lodash.assignin "^4.0.9"
|
||||
lodash.bind "^4.1.4"
|
||||
lodash.defaults "^4.0.1"
|
||||
lodash.filter "^4.4.0"
|
||||
lodash.flatten "^4.2.0"
|
||||
lodash.foreach "^4.3.0"
|
||||
lodash.map "^4.4.0"
|
||||
lodash.merge "^4.4.0"
|
||||
lodash.pick "^4.2.1"
|
||||
lodash.reduce "^4.4.0"
|
||||
lodash.reject "^4.4.0"
|
||||
lodash.some "^4.4.0"
|
||||
|
||||
chokidar-cli@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar-cli/-/chokidar-cli-1.2.3.tgz#28fe28da1c3a12b444f52ddbe8a472358a32279f"
|
||||
@@ -7014,7 +6979,7 @@ css-select-base-adapter@^0.1.1:
|
||||
resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
|
||||
integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
|
||||
|
||||
css-select@^1.1.0, css-select@~1.2.0:
|
||||
css-select@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
|
||||
integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=
|
||||
@@ -7266,6 +7231,11 @@ dateformat@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
|
||||
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
|
||||
|
||||
dayjs@^1.8.29:
|
||||
version "1.8.29"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.29.tgz#5d23e341de6bfbd206c01136d2fb0f01877820f5"
|
||||
integrity sha512-Vm6teig8ZWK7rH/lxzVGxZJCljPdmUr6q/3f4fr5F0VWNGVkZEjZOQJsAN8hUHUqn+NK4XHNEpJZS1MwLyDcLw==
|
||||
|
||||
de-indent@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||
@@ -7657,20 +7627,12 @@ dom-serializer@0, dom-serializer@^0.2.1:
|
||||
domelementtype "^2.0.1"
|
||||
entities "^2.0.0"
|
||||
|
||||
dom-serializer@~0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
|
||||
integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
|
||||
dependencies:
|
||||
domelementtype "^1.3.0"
|
||||
entities "^1.1.1"
|
||||
|
||||
domain-browser@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
|
||||
|
||||
domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
|
||||
domelementtype@1, domelementtype@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
||||
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
|
||||
@@ -7755,6 +7717,11 @@ dot-prop@^5.2.0:
|
||||
dependencies:
|
||||
is-obj "^2.0.0"
|
||||
|
||||
dotenv@^8.2.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
|
||||
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
|
||||
|
||||
duplexer@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
|
||||
@@ -7795,15 +7762,20 @@ ejs@^2.6.1:
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"
|
||||
integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==
|
||||
|
||||
electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.413:
|
||||
electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.378:
|
||||
version "1.3.481"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.481.tgz#0d59e72a0aaeb876b43fb1d6e84bf0dfc99617e8"
|
||||
integrity sha512-q2PeCP2PQXSYadDo9uNY+uHXjdB9PcsUpCVoGlY8TZOPHGlXdevlqW9PkKeqCxn2QBkGB8b6AcMO++gh8X82bA==
|
||||
|
||||
electron-to-chromium@^1.3.483:
|
||||
version "1.3.483"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.483.tgz#9269e7cfc1c8e72709824da171cbe47ca5e3ca9e"
|
||||
integrity sha512-+05RF8S9rk8S0G8eBCqBRBaRq7+UN3lDs2DAvnG8SBSgQO3hjy0+qt4CmRk5eiuGbTcaicgXfPmBi31a+BD3lg==
|
||||
electron-to-chromium@^1.3.363:
|
||||
version "1.3.484"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.484.tgz#75f5a1eee5fe3168758b7c2cf375ae73c1ccf5e6"
|
||||
integrity sha512-esh5mmjAGl6HhAaYgHlDZme+jCIc+XIrLrBTwxviE+pM64UBmdLUIHLlrPzJGbit7hQI1TR/oGDQWCvQZ5yrFA==
|
||||
|
||||
electron-to-chromium@^1.3.413:
|
||||
version "1.3.474"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.474.tgz#161af012e11f96795eade84bf03b8ddc039621b9"
|
||||
integrity sha512-fPkSgT9IBKmVJz02XioNsIpg0WYmkPrvU1lUJblMMJALxyE7/32NGvbJQKKxpNokozPvqfqkuUqVClYsvetcLw==
|
||||
|
||||
elliptic@^6.0.0, elliptic@^6.4.0, elliptic@^6.4.1, elliptic@^6.5.1, elliptic@^6.5.2:
|
||||
version "6.5.3"
|
||||
@@ -7888,7 +7860,7 @@ enquirer@^2.3.0, enquirer@^2.3.4, enquirer@^2.3.5:
|
||||
dependencies:
|
||||
ansi-colors "^3.2.1"
|
||||
|
||||
entities@^1.1.1, entities@~1.1.1:
|
||||
entities@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
||||
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
|
||||
@@ -8003,11 +7975,6 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3:
|
||||
d "^1.0.1"
|
||||
ext "^1.1.2"
|
||||
|
||||
escalade@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.1.tgz#52568a77443f6927cd0ab9c73129137533c965ed"
|
||||
integrity sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA==
|
||||
|
||||
escape-html@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
@@ -8090,7 +8057,7 @@ eslint-plugin-flowtype@^4.7.0:
|
||||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
|
||||
eslint-plugin-import@>=2.20.2, eslint-plugin-import@^2.18.2:
|
||||
eslint-plugin-import@2.21.2, eslint-plugin-import@>=2.20.2, eslint-plugin-import@^2.18.2, "eslint-plugin-import@^2.21.2 ":
|
||||
version "2.21.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.21.2.tgz#8fef77475cc5510801bedc95f84b932f7f334a7c"
|
||||
integrity sha512-FEmxeGI6yaz+SnEB6YgNHlQK1Bs2DKLM+YF+vuTk5H8J9CLbJLtlPvRFgZZ2+sXiKAlN5dpdlrWOjK8ZoZJpQA==
|
||||
@@ -8207,7 +8174,7 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
|
||||
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
|
||||
|
||||
eslint@^6.1.0, eslint@^6.3.0, eslint@^6.7.2:
|
||||
eslint@^6.1.0, eslint@^6.3.0:
|
||||
version "6.8.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
|
||||
integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==
|
||||
@@ -8250,7 +8217,7 @@ eslint@^6.1.0, eslint@^6.3.0, eslint@^6.7.2:
|
||||
text-table "^0.2.0"
|
||||
v8-compile-cache "^2.0.3"
|
||||
|
||||
eslint@^7.0.0, eslint@^7.1.0:
|
||||
eslint@^7.0.0:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.3.1.tgz#76392bd7e44468d046149ba128d1566c59acbe19"
|
||||
integrity sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA==
|
||||
@@ -8292,6 +8259,48 @@ eslint@^7.0.0, eslint@^7.1.0:
|
||||
text-table "^0.2.0"
|
||||
v8-compile-cache "^2.0.3"
|
||||
|
||||
eslint@^7.1.0:
|
||||
version "7.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.4.0.tgz#4e35a2697e6c1972f9d6ef2b690ad319f80f206f"
|
||||
integrity sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
ajv "^6.10.0"
|
||||
chalk "^4.0.0"
|
||||
cross-spawn "^7.0.2"
|
||||
debug "^4.0.1"
|
||||
doctrine "^3.0.0"
|
||||
enquirer "^2.3.5"
|
||||
eslint-scope "^5.1.0"
|
||||
eslint-utils "^2.0.0"
|
||||
eslint-visitor-keys "^1.2.0"
|
||||
espree "^7.1.0"
|
||||
esquery "^1.2.0"
|
||||
esutils "^2.0.2"
|
||||
file-entry-cache "^5.0.1"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
glob-parent "^5.0.0"
|
||||
globals "^12.1.0"
|
||||
ignore "^4.0.6"
|
||||
import-fresh "^3.0.0"
|
||||
imurmurhash "^0.1.4"
|
||||
is-glob "^4.0.0"
|
||||
js-yaml "^3.13.1"
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.4.1"
|
||||
lodash "^4.17.14"
|
||||
minimatch "^3.0.4"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.9.1"
|
||||
progress "^2.0.0"
|
||||
regexpp "^3.1.0"
|
||||
semver "^7.2.1"
|
||||
strip-ansi "^6.0.0"
|
||||
strip-json-comments "^3.1.0"
|
||||
table "^5.2.3"
|
||||
text-table "^0.2.0"
|
||||
v8-compile-cache "^2.0.3"
|
||||
|
||||
espree@^6.1.2:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
|
||||
@@ -9018,7 +9027,7 @@ fork-ts-checker-webpack-plugin@^4.0.4, fork-ts-checker-webpack-plugin@^4.1.3:
|
||||
tapable "^1.0.0"
|
||||
worker-rpc "^0.1.0"
|
||||
|
||||
form-data@^2.3.3, form-data@^2.5.1:
|
||||
form-data@^2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
|
||||
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
|
||||
@@ -9899,7 +9908,7 @@ htmlparser2@4.1.0:
|
||||
domutils "^2.0.0"
|
||||
entities "^2.0.0"
|
||||
|
||||
htmlparser2@^3.3.0, htmlparser2@^3.9.1:
|
||||
htmlparser2@^3.3.0:
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
|
||||
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
|
||||
@@ -11058,7 +11067,17 @@ jest-diff@^24.3.0, jest-diff@^24.9.0:
|
||||
jest-get-type "^24.9.0"
|
||||
pretty-format "^24.9.0"
|
||||
|
||||
jest-diff@^25.2.1, jest-diff@^25.5.0:
|
||||
jest-diff@^25.2.1:
|
||||
version "25.2.6"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.2.6.tgz#a6d70a9ab74507715ea1092ac513d1ab81c1b5e7"
|
||||
integrity sha512-KuadXImtRghTFga+/adnNrv9s61HudRMR7gVSbP35UKZdn4IK2/0N0PpGZIqtmllK9aUyye54I3nu28OYSnqOg==
|
||||
dependencies:
|
||||
chalk "^3.0.0"
|
||||
diff-sequences "^25.2.6"
|
||||
jest-get-type "^25.2.6"
|
||||
pretty-format "^25.2.6"
|
||||
|
||||
jest-diff@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
|
||||
integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==
|
||||
@@ -11904,30 +11923,6 @@ jsontokens@3.0.0, jsontokens@^3.0.0:
|
||||
elliptic "^6.4.1"
|
||||
sha.js "^2.4.11"
|
||||
|
||||
jsontokens@3.0.0-alpha.0:
|
||||
version "3.0.0-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/jsontokens/-/jsontokens-3.0.0-alpha.0.tgz#16d04a2019a6dbe2392e4eeb489ac47ec3d855d7"
|
||||
integrity sha512-+2JdFr2d3XBfamUnETQdv76u3AwBl8jmLp2Hgb/uK5NTys0FpoR5KbIIqv3QrXtjn0uIIPJz9JmhqDAnXd2Nhg==
|
||||
dependencies:
|
||||
"@types/elliptic" "^6.4.9"
|
||||
asn1.js "^5.0.1"
|
||||
base64url "^3.0.1"
|
||||
ecdsa-sig-formatter "^1.0.11"
|
||||
elliptic "^6.4.1"
|
||||
key-encoder "^2.0.3"
|
||||
|
||||
jsontokens@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jsontokens/-/jsontokens-2.0.2.tgz#fff3a940bc7b4960d395c0c583ac8e2b394e07d1"
|
||||
integrity sha512-E5W1CIbS7KcVvOJ2CguITvb77GbsjOzzmkFxnuCJqtSLvebgxRXcR1OhFXDK+2Hz+ng7MYJhMylilKnLwlwdYQ==
|
||||
dependencies:
|
||||
"@types/elliptic" "^6.4.9"
|
||||
asn1.js "^5.0.1"
|
||||
base64url "^3.0.1"
|
||||
ecdsa-sig-formatter "^1.0.11"
|
||||
elliptic "^6.4.1"
|
||||
key-encoder "^2.0.2"
|
||||
|
||||
jsprim@^1.2.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||
@@ -11946,16 +11941,6 @@ jsx-ast-utils@^2.2.3, jsx-ast-utils@^2.4.1:
|
||||
array-includes "^3.1.1"
|
||||
object.assign "^4.1.0"
|
||||
|
||||
key-encoder@^2.0.2, key-encoder@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/key-encoder/-/key-encoder-2.0.3.tgz#77073bb48ff1fe2173bb2088b83b91152c8fa4ba"
|
||||
integrity sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg==
|
||||
dependencies:
|
||||
"@types/elliptic" "^6.4.9"
|
||||
asn1.js "^5.0.1"
|
||||
bn.js "^4.11.8"
|
||||
elliptic "^6.4.1"
|
||||
|
||||
killable@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
|
||||
@@ -12188,16 +12173,11 @@ lodash._reinterpolate@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
|
||||
|
||||
lodash.assignin@^4.0.9, lodash.assignin@^4.2.0:
|
||||
lodash.assignin@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2"
|
||||
integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI=
|
||||
|
||||
lodash.bind@^4.1.4:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35"
|
||||
integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
@@ -12208,26 +12188,6 @@ lodash.debounce@^4.0.8:
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||
|
||||
lodash.defaults@^4.0.1:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
|
||||
|
||||
lodash.filter@^4.4.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
|
||||
integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=
|
||||
|
||||
lodash.flatten@^4.2.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||
|
||||
lodash.foreach@^4.3.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
|
||||
integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
@@ -12238,46 +12198,21 @@ lodash.ismatch@^4.4.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
|
||||
integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=
|
||||
|
||||
lodash.map@^4.4.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
|
||||
integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=
|
||||
|
||||
lodash.memoize@4.x, lodash.memoize@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
||||
|
||||
lodash.merge@^4.4.0, lodash.merge@^4.6.2:
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash.pick@^4.2.1:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
|
||||
integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=
|
||||
|
||||
lodash.reduce@^4.4.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b"
|
||||
integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=
|
||||
|
||||
lodash.reject@^4.4.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415"
|
||||
integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=
|
||||
|
||||
lodash.set@^4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
|
||||
integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=
|
||||
|
||||
lodash.some@^4.4.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
|
||||
integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=
|
||||
|
||||
lodash.sortby@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
@@ -13209,7 +13144,7 @@ node-fetch@2.1.2:
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
|
||||
integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
|
||||
|
||||
node-fetch@2.6.0, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.5.0:
|
||||
node-fetch@2.6.0, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
||||
@@ -13305,7 +13240,7 @@ node-notifier@^6.0.0:
|
||||
shellwords "^0.1.1"
|
||||
which "^1.3.1"
|
||||
|
||||
node-releases@^1.1.29, node-releases@^1.1.52, node-releases@^1.1.53, node-releases@^1.1.58:
|
||||
node-releases@^1.1.29, node-releases@^1.1.50, node-releases@^1.1.52, node-releases@^1.1.53:
|
||||
version "1.1.58"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935"
|
||||
integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==
|
||||
@@ -14246,38 +14181,6 @@ playwright-core@^1.1.1:
|
||||
rimraf "^3.0.2"
|
||||
ws "^6.1.0"
|
||||
|
||||
playwright-firefox@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright-firefox/-/playwright-firefox-1.1.1.tgz#9486e5ab695a2e52face4684114735bb098b934b"
|
||||
integrity sha512-YaRVNxGGfmuAgKD5XDK9vPD82UYYHv5qM1Qn5XwK8zXWhBdDcs+JgaNvW7NnOMefgFAhGy637HHLA1WZ+ajOIA==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
extract-zip "^2.0.0"
|
||||
https-proxy-agent "^3.0.0"
|
||||
jpeg-js "^0.3.7"
|
||||
mime "^2.4.4"
|
||||
pngjs "^5.0.0"
|
||||
progress "^2.0.3"
|
||||
proxy-from-env "^1.1.0"
|
||||
rimraf "^3.0.2"
|
||||
ws "^6.1.0"
|
||||
|
||||
playwright-webkit@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright-webkit/-/playwright-webkit-1.1.1.tgz#e3c55b171f32a268b6795772ed414b1bf62e2dbc"
|
||||
integrity sha512-fv1itK0rz6uqpO22M9CdRaqFyB6L0Y4S3OkWgSCuLLrmMIW4tmSLT1qukM4QXcHYQNdTIVvbtudITxEfIU5p4g==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
extract-zip "^2.0.0"
|
||||
https-proxy-agent "^3.0.0"
|
||||
jpeg-js "^0.3.7"
|
||||
mime "^2.4.4"
|
||||
pngjs "^5.0.0"
|
||||
progress "^2.0.3"
|
||||
proxy-from-env "^1.1.0"
|
||||
rimraf "^3.0.2"
|
||||
ws "^6.1.0"
|
||||
|
||||
playwright@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.1.1.tgz#20746124542bddd7e925c128b6ec1ace679ffe6a"
|
||||
@@ -14766,7 +14669,17 @@ pretty-format@^24.9.0:
|
||||
ansi-styles "^3.2.0"
|
||||
react-is "^16.8.4"
|
||||
|
||||
pretty-format@^25.2.1, pretty-format@^25.5.0:
|
||||
pretty-format@^25.2.1, pretty-format@^25.2.6:
|
||||
version "25.2.6"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.2.6.tgz#542a1c418d019bbf1cca2e3620443bc1323cb8d7"
|
||||
integrity sha512-DEiWxLBaCHneffrIT4B+TpMvkV9RNvvJrd3lY9ew1CEQobDzEXmYT1mg0hJhljZty7kCc10z13ohOFAE8jrUDg==
|
||||
dependencies:
|
||||
"@jest/types" "^25.2.6"
|
||||
ansi-regex "^5.0.0"
|
||||
ansi-styles "^4.0.0"
|
||||
react-is "^16.12.0"
|
||||
|
||||
pretty-format@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
|
||||
integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
|
||||
@@ -15043,10 +14956,10 @@ query-string@^4.1.0:
|
||||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
query-string@^6.3.0, query-string@^6.8.3:
|
||||
version "6.13.1"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad"
|
||||
integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA==
|
||||
query-string@^6.8.3:
|
||||
version "6.11.1"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.11.1.tgz#ab021f275d463ce1b61e88f0ce6988b3e8fe7c2c"
|
||||
integrity sha512-1ZvJOUl8ifkkBxu2ByVM/8GijMIPx+cef7u3yroO3Ogm4DOdZcF5dcrWTIlSHe3Pg/mtlt6/eFjObDfJureZZA==
|
||||
dependencies:
|
||||
decode-uri-component "^0.2.0"
|
||||
split-on-first "^1.0.0"
|
||||
@@ -16402,7 +16315,7 @@ semver-regex@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
||||
semver@6.3.0, semver@6.x, semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
|
||||
semver@6.3.0, semver@6.x, semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
@@ -17821,7 +17734,7 @@ trim@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd"
|
||||
integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0=
|
||||
|
||||
triplesec@^3.0.26, triplesec@^3.0.27:
|
||||
triplesec@^3.0.27:
|
||||
version "3.0.27"
|
||||
resolved "https://registry.yarnpkg.com/triplesec/-/triplesec-3.0.27.tgz#43ba5a9f0e11ebba20c7563ecca947b2f94e82c5"
|
||||
integrity sha512-FDhkxa3JYnPOerOd+8k+SBmm7cb7KkyX+xXwNFV3XV6dsQgHuRvjtbnzWfPJ2kimeR8ErjZfPd/6r7RH6epHDw==
|
||||
@@ -18072,6 +17985,72 @@ tsdx@^0.12.3:
|
||||
tslib "^1.9.3"
|
||||
typescript "^3.7.3"
|
||||
|
||||
tsdx@^0.13.1:
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/tsdx/-/tsdx-0.13.1.tgz#6fdfb7f70ecd22462d36c80f538d86f42d364b92"
|
||||
integrity sha512-BAp96F6eLZVIk5FETP8kSbkC8sqvDQa0pPMz4euZ8am2aFNzeXNX7DbOxfMxYiwzfyemfUsmyb4p5usVbqM4IQ==
|
||||
dependencies:
|
||||
"@babel/core" "^7.4.4"
|
||||
"@babel/helper-module-imports" "^7.0.0"
|
||||
"@babel/plugin-proposal-class-properties" "^7.4.4"
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.7.4"
|
||||
"@babel/plugin-proposal-optional-chaining" "^7.7.5"
|
||||
"@babel/plugin-syntax-dynamic-import" "^7.2.0"
|
||||
"@babel/plugin-transform-regenerator" "^7.4.5"
|
||||
"@babel/plugin-transform-runtime" "^7.6.0"
|
||||
"@babel/polyfill" "^7.4.4"
|
||||
"@babel/preset-env" "^7.4.4"
|
||||
"@rollup/plugin-commonjs" "^11.0.0"
|
||||
"@rollup/plugin-json" "^4.0.0"
|
||||
"@rollup/plugin-node-resolve" "^7.1.0"
|
||||
"@rollup/plugin-replace" "^2.2.1"
|
||||
"@types/shelljs" "^0.8.5"
|
||||
"@typescript-eslint/eslint-plugin" "^2.12.0"
|
||||
"@typescript-eslint/parser" "^2.12.0"
|
||||
ansi-escapes "^4.2.1"
|
||||
asyncro "^3.0.0"
|
||||
babel-eslint "^10.0.3"
|
||||
babel-plugin-annotate-pure-calls "^0.4.0"
|
||||
babel-plugin-dev-expression "^0.2.1"
|
||||
babel-plugin-macros "^2.6.1"
|
||||
babel-plugin-transform-async-to-promises "^0.8.14"
|
||||
babel-plugin-transform-rename-import "^2.3.0"
|
||||
babel-traverse "^6.26.0"
|
||||
babylon "^6.18.0"
|
||||
camelcase "^5.0.0"
|
||||
chalk "^2.4.2"
|
||||
enquirer "^2.3.4"
|
||||
eslint "^6.1.0"
|
||||
eslint-config-prettier "^6.0.0"
|
||||
eslint-config-react-app "^5.0.2"
|
||||
eslint-plugin-flowtype "^3.13.0"
|
||||
eslint-plugin-import "^2.18.2"
|
||||
eslint-plugin-jsx-a11y "^6.2.3"
|
||||
eslint-plugin-prettier "^3.1.0"
|
||||
eslint-plugin-react "^7.14.3"
|
||||
eslint-plugin-react-hooks "^2.2.0"
|
||||
execa "3.2.0"
|
||||
fs-extra "^8.0.1"
|
||||
jest "^24.8.0"
|
||||
jest-watch-typeahead "^0.4.0"
|
||||
jpjs "^1.2.1"
|
||||
lodash.merge "^4.6.2"
|
||||
ora "^3.4.0"
|
||||
pascal-case "^2.0.1"
|
||||
prettier "^1.19.1"
|
||||
progress-estimator "^0.2.2"
|
||||
rollup "^1.32.1"
|
||||
rollup-plugin-babel "^4.3.2"
|
||||
rollup-plugin-sourcemaps "^0.4.2"
|
||||
rollup-plugin-terser "^5.1.2"
|
||||
rollup-plugin-typescript2 "^0.26.0"
|
||||
sade "^1.4.2"
|
||||
shelljs "^0.8.3"
|
||||
tiny-glob "^0.2.6"
|
||||
ts-jest "^24.0.2"
|
||||
tslib "^1.9.3"
|
||||
typescript "^3.7.3"
|
||||
|
||||
tsdx@^0.13.2:
|
||||
version "0.13.2"
|
||||
resolved "https://registry.yarnpkg.com/tsdx/-/tsdx-0.13.2.tgz#e6b0d5e52fadb3dd993c26887b9b75acd438cd05"
|
||||
@@ -18144,10 +18123,10 @@ tslib@1.10.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||
|
||||
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
|
||||
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
|
||||
tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
|
||||
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
|
||||
|
||||
tslib@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -18298,17 +18277,17 @@ typeforce@^1.11.3, typeforce@^1.11.5:
|
||||
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"
|
||||
integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==
|
||||
|
||||
typescript@3.7.5:
|
||||
version "3.7.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
|
||||
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
|
||||
|
||||
typescript@3.8.3:
|
||||
typescript@^3.6.4, typescript@^3.7.3:
|
||||
version "3.8.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
|
||||
integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==
|
||||
|
||||
typescript@^3.6.4, typescript@^3.7.3, typescript@^3.7.5, typescript@^3.8.2, typescript@^3.9.5:
|
||||
typescript@^3.9.3:
|
||||
version "3.9.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
|
||||
integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
|
||||
|
||||
typescript@^3.9.5:
|
||||
version "3.9.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
|
||||
integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==
|
||||
@@ -19359,11 +19338,16 @@ ws@^6.0.0, ws@^6.1.0, ws@^6.1.2, ws@^6.2.1:
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
ws@^7.0.0, ws@^7.2.0, ws@^7.2.3:
|
||||
ws@^7.0.0, ws@^7.2.0:
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd"
|
||||
integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==
|
||||
|
||||
ws@^7.2.3:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
|
||||
integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==
|
||||
|
||||
xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
|
||||
Reference in New Issue
Block a user