laconic-wallet-web/src/utils/wallet-connect/helpers.ts
2024-08-01 15:37:35 +05:30

195 lines
6.4 KiB
TypeScript

// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a
import { utils } from 'ethers';
import { ProposalTypes } from '@walletconnect/types';
import { Account, NetworksDataState } from '../../types';
import { EIP155_SIGNING_METHODS } from './EIP155Data';
import { mergeWith } from 'lodash';
import { retrieveAccounts } from '../accounts';
import { COSMOS, EIP155 } from '../constants';
import { NETWORK_METHODS } from './common-data';
import { COSMOS_METHODS } from './COSMOSData';
/**
* Converts hex to utf8 string if it is valid bytes
*/
export function convertHexToUtf8(value: string) {
if (utils.isHexString(value)) {
return utils.toUtf8String(value);
}
return value;
}
/**
* Gets message from various signing request methods by filtering out
* a value that is not an address (thus is a message).
* If it is a hex string, it gets converted to utf8 string
*/
export function getSignParamsMessage(params: string[]) {
const message = params.filter(p => !utils.isAddress(p))[0];
return convertHexToUtf8(message);
}
export const getNamespaces = async (
optionalNamespaces: ProposalTypes.OptionalNamespaces,
requiredNamespaces: ProposalTypes.RequiredNamespaces,
networksData: NetworksDataState[],
selectedNetwork: NetworksDataState,
accounts: Account[],
) => {
const namespaceChainId = `${selectedNetwork.namespace}:${selectedNetwork.chainId}`;
const combinedNamespaces = mergeWith(
requiredNamespaces,
optionalNamespaces,
(obj, src) =>
Array.isArray(obj) && Array.isArray(src) ? [...src, ...obj] : undefined,
);
const walletConnectChains: string[] = [];
Object.keys(combinedNamespaces).forEach(key => {
const { chains } = combinedNamespaces[key];
chains && walletConnectChains.push(...chains);
});
// If combinedNamespaces is not empty, send back namespaces object based on requested chains
// Else send back namespaces object using currently selected network
if (Object.keys(combinedNamespaces).length > 0) {
if (!(walletConnectChains.length > 0)) {
return;
}
// Check for unsupported chains
const networkChains = networksData.map(
network => `${network.namespace}:${network.chainId}`,
);
if (!walletConnectChains.every(chain => networkChains.includes(chain))) {
const unsupportedChains = walletConnectChains.filter(
chain => !networkChains.includes(chain),
);
throw new Error(`Unsupported chains : ${unsupportedChains.join(',')}`);
}
// Get required networks
const requiredNetworks = networksData.filter(network =>
walletConnectChains.includes(`${network.namespace}:${network.chainId}`),
);
// Get accounts for required networks
const requiredAddressesPromise = requiredNetworks.map(
async requiredNetwork => {
const retrievedAccounts = await retrieveAccounts(requiredNetwork);
if (!retrievedAccounts) {
throw new Error('Accounts for given network not found');
}
const addresses = retrievedAccounts.map(
retrieveAccount =>
`${requiredNetwork.namespace}:${requiredNetwork.chainId}:${retrieveAccount.address}`,
);
return addresses;
},
);
const requiredAddressesArray = await Promise.all(requiredAddressesPromise);
const requiredAddresses = requiredAddressesArray.flat();
// construct namespace object
const newNamespaces = {
eip155: {
chains: walletConnectChains.filter(chain => chain.includes(EIP155)),
// TODO: Debug optional namespace methods and events being required for approval
methods: [
...Object.values(EIP155_SIGNING_METHODS),
...Object.values(NETWORK_METHODS),
...(optionalNamespaces.eip155?.methods ?? []),
...(requiredNamespaces.eip155?.methods ?? []),
],
events: [
...(optionalNamespaces.eip155?.events ?? []),
...(requiredNamespaces.eip155?.events ?? []),
],
accounts: requiredAddresses.filter(account => account.includes(EIP155)),
},
cosmos: {
chains: walletConnectChains.filter(chain => chain.includes(COSMOS)),
methods: [
...Object.values(COSMOS_METHODS),
...Object.values(NETWORK_METHODS),
...(optionalNamespaces.cosmos?.methods ?? []),
...(requiredNamespaces.cosmos?.methods ?? []),
],
events: [
...(optionalNamespaces.cosmos?.events ?? []),
...(requiredNamespaces.cosmos?.events ?? []),
],
accounts: requiredAddresses.filter(account => account.includes(COSMOS)),
},
};
return newNamespaces;
} else {
switch (selectedNetwork.namespace) {
case EIP155:
return {
eip155: {
chains: [namespaceChainId],
// TODO: Debug optional namespace methods and events being required for approval
methods: [
...Object.values(EIP155_SIGNING_METHODS),
...Object.values(NETWORK_METHODS),
...(optionalNamespaces.eip155?.methods ?? []),
...(requiredNamespaces.eip155?.methods ?? []),
],
events: [
...(optionalNamespaces.eip155?.events ?? []),
...(requiredNamespaces.eip155?.events ?? []),
],
accounts: accounts.map(ethAccount => {
return `${namespaceChainId}:${ethAccount.address}`;
}),
},
cosmos: {
chains: [],
methods: [],
events: [],
accounts: [],
},
};
case COSMOS:
return {
cosmos: {
chains: [namespaceChainId],
methods: [
...Object.values(COSMOS_METHODS),
...Object.values(NETWORK_METHODS),
...(optionalNamespaces.cosmos?.methods ?? []),
...(requiredNamespaces.cosmos?.methods ?? []),
],
events: [
...(optionalNamespaces.cosmos?.events ?? []),
...(requiredNamespaces.cosmos?.events ?? []),
],
accounts: accounts.map(cosmosAccount => {
return `${namespaceChainId}:${cosmosAccount.address}`;
}),
},
eip155: {
chains: [],
methods: [],
events: [],
accounts: [],
},
};
default:
break;
}
}
};