mirror of
https://github.com/zhigang1992/xverse-web-extension.git
synced 2026-01-12 18:02:19 +08:00
feat: Get network [ENG-6213] (#866)
This commit is contained in:
17
package-lock.json
generated
17
package-lock.json
generated
@@ -15,10 +15,10 @@
|
||||
"@phosphor-icons/react": "2.1.7",
|
||||
"@playwright/test": "1.46.1",
|
||||
"@react-spring/web": "9.7.3",
|
||||
"@sats-connect/core": "0.5.2",
|
||||
"@sats-connect/core": "0.5.3-b107fb6",
|
||||
"@scure/base": "1.1.9",
|
||||
"@scure/btc-signer": "1.2.1",
|
||||
"@secretkeylabs/xverse-core": "38.0.0",
|
||||
"@secretkeylabs/xverse-core": "38.1.1",
|
||||
"@stacks/connect": "7.9.0",
|
||||
"@stacks/stacks-blockchain-api-types": "7.14.1",
|
||||
"@stacks/transactions": "7.0.2",
|
||||
@@ -1474,9 +1474,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sats-connect/core": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@sats-connect/core/-/core-0.5.2.tgz",
|
||||
"integrity": "sha512-i9bcmGX+ljJ/5MbCiu8CcpwOLNTgwV4MzOcItl0wzMswO7TMJ2wjTxjhjf3A6029Wbb+AKun5OLkmDf0jhuvCA==",
|
||||
"version": "0.5.3-b107fb6",
|
||||
"resolved": "https://registry.npmjs.org/@sats-connect/core/-/core-0.5.3-b107fb6.tgz",
|
||||
"integrity": "sha512-SrEYcCh4P6vhGUtRbsqrrs59qEPW9W9ESbk7DUyDATwhbzTLdp2L/Dg2goNKsU1K0/PidqOFu4DOigRC6x2hHg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "1.7.7",
|
||||
"bitcoin-address-validation": "2.2.3",
|
||||
@@ -1593,9 +1594,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@secretkeylabs/xverse-core": {
|
||||
"version": "38.0.0",
|
||||
"resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/38.0.0/64b997629f051643c9350450f0b23fc7e44db902",
|
||||
"integrity": "sha512-YoBgVS0a8tDKzN2xPM0A8qkiyJfk/uRQsGi2pSdrD5uMaKkuZiH+bAK/mEYa1sqE7tqcLlBOWkuCc/dWC+Pl+Q==",
|
||||
"version": "38.1.1",
|
||||
"resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/38.1.1/714acf65ecd7c61826013538d0e7f2825cb8250e",
|
||||
"integrity": "sha512-YhiQ4TF16jPYljbQzcRFe5EHfYY9xE4n4oo6hzRpArFGmoJKKxxBFP7f/58ZWKKsX8yBqJgqJhS8p+Fs6b6+iw==",
|
||||
"dependencies": {
|
||||
"@bitcoinerlab/secp256k1": "1.0.2",
|
||||
"@keystonehq/hw-app-bitcoin": "0.1.2",
|
||||
|
||||
@@ -44,10 +44,10 @@
|
||||
"@phosphor-icons/react": "2.1.7",
|
||||
"@playwright/test": "1.46.1",
|
||||
"@react-spring/web": "9.7.3",
|
||||
"@sats-connect/core": "0.5.2",
|
||||
"@sats-connect/core": "0.5.3-b107fb6",
|
||||
"@scure/base": "1.1.9",
|
||||
"@scure/btc-signer": "1.2.1",
|
||||
"@secretkeylabs/xverse-core": "38.0.0",
|
||||
"@secretkeylabs/xverse-core": "38.1.1",
|
||||
"@stacks/connect": "7.9.0",
|
||||
"@stacks/stacks-blockchain-api-types": "7.14.1",
|
||||
"@stacks/transactions": "7.0.2",
|
||||
|
||||
@@ -25,7 +25,7 @@ export function PermissionsProvider({ children }: PropsWithChildren) {
|
||||
throw new Error('Failed to initialize the permissions store', { cause: initError });
|
||||
}
|
||||
|
||||
saveStoreGlobal(newStore);
|
||||
setStoreGlobal(newStore);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -481,29 +481,22 @@ const useWalletReducer = () => {
|
||||
dispatchEventAuthorizedConnectedClients(
|
||||
[
|
||||
{
|
||||
type: 'account',
|
||||
resourceId: permissions.resources.account.makeAccountResourceId(
|
||||
permissions.utils.account.makeAccountId({
|
||||
accountId: currentlySelectedAccount.id,
|
||||
masterPubKey: currentlySelectedAccount.masterPubKey,
|
||||
networkType: network.type,
|
||||
}),
|
||||
),
|
||||
actions: { read: true },
|
||||
type: 'wallet',
|
||||
actions: {
|
||||
readNetwork: true,
|
||||
},
|
||||
{
|
||||
type: 'account',
|
||||
resourceId: permissions.resources.account.makeAccountResourceId(
|
||||
permissions.utils.account.makeAccountId({
|
||||
accountId: currentlySelectedAccount.id,
|
||||
masterPubKey: currentlySelectedAccount.masterPubKey,
|
||||
networkType: changedNetwork.type,
|
||||
}),
|
||||
),
|
||||
actions: { read: true },
|
||||
resourceId: 'wallet',
|
||||
},
|
||||
],
|
||||
{ type: 'networkChange' },
|
||||
{
|
||||
type: 'networkChange',
|
||||
bitcoin: {
|
||||
name: changedNetwork.type,
|
||||
},
|
||||
stacks: {
|
||||
name: changedNetwork.type,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ export function useMakeHandleAccept({ context, data }: Args) {
|
||||
origin: context.origin,
|
||||
};
|
||||
|
||||
addClient(client);
|
||||
|
||||
const accountId = permissions.utils.account.makeAccountId({
|
||||
accountId: account.id,
|
||||
networkType: network.type,
|
||||
@@ -52,7 +54,7 @@ export function useMakeHandleAccept({ context, data }: Args) {
|
||||
networkType: network.type,
|
||||
});
|
||||
|
||||
const permission: TPermissions.Store.Permission = {
|
||||
const accountPermission: TPermissions.Store.Permission = {
|
||||
type: 'account',
|
||||
clientId: client.id,
|
||||
resourceId: resource.id,
|
||||
@@ -61,9 +63,26 @@ export function useMakeHandleAccept({ context, data }: Args) {
|
||||
},
|
||||
};
|
||||
|
||||
await addClient(client);
|
||||
await addResource(resource);
|
||||
await setPermission(permission);
|
||||
addResource(resource);
|
||||
setPermission(accountPermission);
|
||||
|
||||
const walletResource: TPermissions.Store.Resource = {
|
||||
type: 'wallet',
|
||||
id: 'wallet',
|
||||
name: 'Wallet',
|
||||
};
|
||||
|
||||
const walletPermission: TPermissions.Store.Permission = {
|
||||
type: 'wallet',
|
||||
clientId: client.id,
|
||||
resourceId: 'wallet',
|
||||
actions: {
|
||||
readNetwork: true,
|
||||
},
|
||||
};
|
||||
|
||||
addResource(walletResource);
|
||||
setPermission(walletPermission);
|
||||
|
||||
if (data.method === 'wallet_requestPermissions')
|
||||
sendRequestPermissionsSuccessResponseMessage({
|
||||
@@ -85,6 +104,14 @@ export function useMakeHandleAccept({ context, data }: Args) {
|
||||
id: accountId,
|
||||
walletType: account.accountType ?? 'software',
|
||||
addresses,
|
||||
network: {
|
||||
bitcoin: {
|
||||
name: network.type,
|
||||
},
|
||||
stacks: {
|
||||
name: network.type,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -118,12 +118,6 @@ export async function sendMessageAuthorizedConnectedClients(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!store) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Unable to notify connected clients, no permissions store found.');
|
||||
return;
|
||||
}
|
||||
|
||||
const authorizedClientIds = store.permissions
|
||||
.filter((storePermission) =>
|
||||
targetPermissions.some(
|
||||
|
||||
@@ -85,10 +85,10 @@ async function loadPermissionsStore(): Promise<
|
||||
* safely be called multiple times.
|
||||
*/
|
||||
export async function initPermissionsStore(): Promise<Result<Permissions.Store.PermissionsStore>> {
|
||||
const [getStoreError, loadedStore] = await loadPermissionsStore();
|
||||
const [storeGetError, loadedStore] = await loadPermissionsStore();
|
||||
|
||||
if (getStoreError) {
|
||||
if (getStoreError.name === 'SchemaParseError') {
|
||||
if (storeGetError) {
|
||||
if (storeGetError.name === 'SchemaParseError') {
|
||||
// The store is outdated and needs to be migrated. For now, the store is
|
||||
// migrated by creating a new store using the current schema and
|
||||
// overwriting the previous one. As the store becomes more complex, a more
|
||||
@@ -98,7 +98,7 @@ export async function initPermissionsStore(): Promise<Result<Permissions.Store.P
|
||||
return success(newStore);
|
||||
}
|
||||
|
||||
if (getStoreError.name === 'StoreNotFoundError') {
|
||||
if (storeGetError.name === 'StoreNotFoundError') {
|
||||
// The store doesn't exist yet, so we create a new one.
|
||||
const newStore = permissions.utils.store.makePermissionsStore();
|
||||
saveStore(newStore);
|
||||
@@ -108,7 +108,7 @@ export async function initPermissionsStore(): Promise<Result<Permissions.Store.P
|
||||
return error({
|
||||
name: 'InitError',
|
||||
message: 'Failed to initialize permissions store.',
|
||||
data: getStoreError,
|
||||
data: storeGetError,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
getAccountRequestMessageSchema,
|
||||
getCurrentPermissionsMethodName,
|
||||
getCurrentPermissionsRequestMessageSchema,
|
||||
getNetworkMethodName,
|
||||
getNetworkRequestMessageSchema,
|
||||
getWalletTypeMethodName,
|
||||
getWalletTypeRequestMessageSchema,
|
||||
renouncePermissionsMethodName,
|
||||
@@ -21,6 +23,7 @@ import { handleConnect } from '../wallet/connect';
|
||||
import { handleDisconnect } from '../wallet/disconnect';
|
||||
import { handleGetAccount } from '../wallet/getAccount';
|
||||
import { handleGetPermissions } from '../wallet/getCurrentPermissions';
|
||||
import { handleGetNetwork } from '../wallet/getNetwork';
|
||||
import { handleGetWalletType } from '../wallet/getWalletType';
|
||||
import { handleRenouncePermissions } from '../wallet/renouncePermissions';
|
||||
import { handleRequestPermissions } from '../wallet/requestPermissions';
|
||||
@@ -73,4 +76,8 @@ export const router: Record<string, Handler> = {
|
||||
],
|
||||
validateMessageSchema(getAccountRequestMessageSchema, handleGetAccount),
|
||||
),
|
||||
[getNetworkMethodName]: requirePermissions(
|
||||
[{ type: 'wallet', resourceId: 'wallet', actions: { readNetwork: true } }],
|
||||
validateMessageSchema(getNetworkRequestMessageSchema, handleGetNetwork),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -102,6 +102,7 @@ export function requirePermissions(
|
||||
|
||||
if (!hasRequiredPermissions) {
|
||||
sendAccessDeniedResponseMessage({ tabId, messageId: message.id });
|
||||
return;
|
||||
}
|
||||
|
||||
return handler(message, port);
|
||||
@@ -112,7 +113,7 @@ export function validateMessageSchema<
|
||||
const TSchema extends v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>,
|
||||
>(
|
||||
schema: TSchema,
|
||||
handler: (message: v.InferOutput<TSchema>, port: chrome.runtime.Port) => Promise<void>,
|
||||
handler: (message: v.InferOutput<TSchema>, port: chrome.runtime.Port) => Promise<void> | void,
|
||||
) {
|
||||
return async (message: RpcRequestMessage, port: chrome.runtime.Port) => {
|
||||
const parseResult = v.safeParse(schema, message);
|
||||
|
||||
@@ -46,8 +46,19 @@ export function sendGetCurrentPermissionsSuccessResponseMessage({
|
||||
sendRpcResponse(tabId, makeRpcSuccessResponse(messageId, result));
|
||||
}
|
||||
|
||||
type GetAccountSuccessArgs = BaseArgs & {
|
||||
result: Return<'wallet_getAccount'>;
|
||||
};
|
||||
export function sendGetAccountSuccessResponseMessage({
|
||||
tabId,
|
||||
messageId,
|
||||
result,
|
||||
}: GetAccountSuccessArgs) {
|
||||
sendRpcResponse(tabId, makeRpcSuccessResponse(messageId, result));
|
||||
}
|
||||
|
||||
type ConnectSuccessArgs = BaseArgs & {
|
||||
result: ConnectResult;
|
||||
result: Return<'wallet_connect'>;
|
||||
};
|
||||
export function sendConnectSuccessResponseMessage({
|
||||
tabId,
|
||||
@@ -56,3 +67,14 @@ export function sendConnectSuccessResponseMessage({
|
||||
}: ConnectSuccessArgs) {
|
||||
sendRpcResponse(tabId, makeRpcSuccessResponse(messageId, result));
|
||||
}
|
||||
|
||||
type GetNetworkSuccessArgs = BaseArgs & {
|
||||
result: Return<'wallet_getNetwork'>;
|
||||
};
|
||||
export function sendGetNetworkSuccessResponseMessage({
|
||||
tabId,
|
||||
messageId,
|
||||
result,
|
||||
}: GetNetworkSuccessArgs) {
|
||||
sendRpcResponse(tabId, makeRpcSuccessResponse(messageId, result));
|
||||
}
|
||||
|
||||
@@ -13,8 +13,14 @@ import { sendInternalErrorMessage } from '../responseMessages/errors';
|
||||
import { sendConnectSuccessResponseMessage } from '../responseMessages/wallet';
|
||||
|
||||
export const handleConnect = async (message: ConnectRequestMessage, port: chrome.runtime.Port) => {
|
||||
// Check if the user already has account read permissions, and if so, return
|
||||
// the account data without opening the popup.
|
||||
// Check if the user already has account & network read permissions, and if
|
||||
// so, return the account data without opening the popup.
|
||||
//
|
||||
// Note: the checks performed in this file for the default permissions that
|
||||
// `wallet_connect` is expected to grant is decoupled from and needs to be
|
||||
// manually kept in sync with those granted in
|
||||
// `src/app/screens/connect/connectionRequest/hooks.ts`.
|
||||
|
||||
const [error, store] = await initPermissionsStore();
|
||||
if (error) {
|
||||
sendInternalErrorMessage({
|
||||
@@ -75,8 +81,14 @@ export const handleConnect = async (message: ConnectRequestMessage, port: chrome
|
||||
resourceId,
|
||||
actions: { read: true },
|
||||
});
|
||||
const hasNetworkReadPermissions = permissions.utils.store.hasPermission(store, {
|
||||
type: 'wallet',
|
||||
clientId,
|
||||
resourceId: 'wallet',
|
||||
actions: { readNetwork: true },
|
||||
});
|
||||
|
||||
if (!hasAccountReadPermissions) {
|
||||
if (!hasAccountReadPermissions || !hasNetworkReadPermissions) {
|
||||
openPopup({
|
||||
path: RequestsRoutes.ConnectionRequest,
|
||||
data: message,
|
||||
@@ -95,6 +107,14 @@ export const handleConnect = async (message: ConnectRequestMessage, port: chrome
|
||||
id: accountId,
|
||||
walletType: account.accountType ?? 'software',
|
||||
addresses,
|
||||
network: {
|
||||
bitcoin: {
|
||||
name: network.type,
|
||||
},
|
||||
stacks: {
|
||||
name: network.type,
|
||||
},
|
||||
},
|
||||
};
|
||||
sendConnectSuccessResponseMessage({
|
||||
tabId: getTabIdFromPort(port),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
import { getTabIdFromPort } from '@common/utils';
|
||||
import getSelectedAccount, { embellishAccountWithDetails } from '@common/utils/getSelectedAccount';
|
||||
import { type ConnectResult, type GetAccountRequestMessage } from '@sats-connect/core';
|
||||
import { type GetAccountRequestMessage, type GetAccountResult } from '@sats-connect/core';
|
||||
import { permissions } from '@secretkeylabs/xverse-core';
|
||||
import rootStore from '@stores/index';
|
||||
import { accountPurposeAddresses } from '../btc/getAddresses/utils';
|
||||
import { sendInternalErrorMessage } from '../responseMessages/errors';
|
||||
import { sendConnectSuccessResponseMessage } from '../responseMessages/wallet';
|
||||
import { sendGetAccountSuccessResponseMessage } from '../responseMessages/wallet';
|
||||
|
||||
export async function handleGetAccount(
|
||||
message: GetAccountRequestMessage,
|
||||
@@ -47,12 +47,12 @@ export async function handleGetAccount(
|
||||
|
||||
const embellishedAccount = embellishAccountWithDetails(account, btcPaymentAddressType);
|
||||
const addresses = accountPurposeAddresses(embellishedAccount, { type: 'all' });
|
||||
const result: ConnectResult = {
|
||||
const result: GetAccountResult = {
|
||||
id: accountId,
|
||||
walletType: account.accountType ?? 'software',
|
||||
addresses,
|
||||
};
|
||||
sendConnectSuccessResponseMessage({
|
||||
sendGetAccountSuccessResponseMessage({
|
||||
tabId: getTabIdFromPort(port),
|
||||
messageId: message.id,
|
||||
result,
|
||||
|
||||
24
src/common/utils/rpc/wallet/getNetwork.ts
Normal file
24
src/common/utils/rpc/wallet/getNetwork.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { getTabIdFromPort } from '@common/utils';
|
||||
import type { GetNetworkRequestMessage, GetNetworkResult } from '@sats-connect/core';
|
||||
import rootStore from '@stores/index';
|
||||
import { sendGetNetworkSuccessResponseMessage } from '../responseMessages/wallet';
|
||||
|
||||
export function handleGetNetwork(message: GetNetworkRequestMessage, port: chrome.runtime.Port) {
|
||||
const { network } = rootStore.store.getState().walletState;
|
||||
|
||||
const result: GetNetworkResult = {
|
||||
bitcoin: {
|
||||
name: network.type,
|
||||
},
|
||||
stacks: {
|
||||
// QUESTION: Do we need to add a stacks network names we could use here?
|
||||
name: network.type,
|
||||
},
|
||||
};
|
||||
|
||||
sendGetNetworkSuccessResponseMessage({
|
||||
tabId: getTabIdFromPort(port),
|
||||
messageId: message.id,
|
||||
result,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user