diff --git a/packages/walletconnect/src/constants.ts b/packages/walletconnect/src/constants.ts
index 1d48d6039..e88c619cb 100644
--- a/packages/walletconnect/src/constants.ts
+++ b/packages/walletconnect/src/constants.ts
@@ -16,6 +16,7 @@ You should have received a copy of the GNU Lesser General Public License
along with the library. If not, see .
*/
export const PROVIDER_NAMESPACE = 'alephium'
+export const VALID_ADDRESS_GROUPS = [-1, 0, 1, 2, 3]
// Note:
// 1. the wallet client could potentially submit the signed transaction.
diff --git a/packages/walletconnect/src/provider.ts b/packages/walletconnect/src/provider.ts
index 8027aa071..20246f591 100644
--- a/packages/walletconnect/src/provider.ts
+++ b/packages/walletconnect/src/provider.ts
@@ -50,7 +50,14 @@ import {
EnableOptionsBase
} from '@alephium/web3'
-import { ALEPHIUM_DEEP_LINK, LOGGER, PROVIDER_NAMESPACE, RELAY_METHODS, RELAY_URL } from './constants'
+import {
+ ALEPHIUM_DEEP_LINK,
+ LOGGER,
+ PROVIDER_NAMESPACE,
+ RELAY_METHODS,
+ RELAY_URL,
+ VALID_ADDRESS_GROUPS
+} from './constants'
import {
AddressGroup,
RelayMethodParams,
@@ -518,35 +525,81 @@ export function isCompatibleAddressGroup(group: number, expectedAddressGroup: Ad
return expectedAddressGroup === undefined || expectedAddressGroup === group
}
-export function formatChain(networkId: NetworkId, addressGroup: AddressGroup): string {
- if (addressGroup !== undefined && addressGroup < 0) {
- throw Error('Address group in provider needs to be either undefined or non-negative')
+export function parseChain(chainString: string): ChainInfo {
+ try {
+ const [namespace, _addressGroup, networkId] = chainString.replace(/_/g, ':').split(':')
+ if (namespace !== PROVIDER_NAMESPACE) {
+ throw Error(`Invalid namespace: expected ${PROVIDER_NAMESPACE}, but got ${namespace}`)
+ }
+ const addressGroup = parseInt(_addressGroup, 10)
+ validateAddressGroup(addressGroup)
+
+ const networkIdList = networkIds as ReadonlyArray
+ if (!networkIdList.includes(networkId)) {
+ throw Error(`Invalid network id, expect one of ${networkIdList}`)
+ }
+ return {
+ networkId: networkId as NetworkId,
+ addressGroup: addressGroup === -1 ? undefined : addressGroup
+ }
+ } catch (error) {
+ console.debug('Failed to parse chain, falling back to legacy parsing', chainString)
+ return parseChainLegacy(chainString)
}
- const addressGroupEncoded = addressGroup !== undefined ? addressGroup : -1
- return `${PROVIDER_NAMESPACE}:${networkId}/${addressGroupEncoded}`
}
-export function parseChain(chainString: string): ChainInfo {
- const [_namespace, networkId, addressGroup] = chainString.replace(/\//g, ':').split(':')
- const addressGroupDecoded = parseInt(addressGroup, 10)
- if (addressGroupDecoded < -1) {
- throw Error('Address group in protocol needs to be either -1 or non-negative')
- }
+export function parseChainLegacy(chainString: string): ChainInfo {
+ const [_namespace, networkId, _addressGroup] = chainString.replace(/\//g, ':').split(':')
+ const addressGroup = parseInt(_addressGroup, 10)
+ validateAddressGroup(addressGroup)
+
const networkIdList = networkIds as ReadonlyArray
if (!networkIdList.includes(networkId)) {
throw Error(`Invalid network id, expect one of ${networkIdList}`)
}
return {
networkId: networkId as NetworkId,
- addressGroup: addressGroupDecoded === -1 ? undefined : addressGroupDecoded
+ addressGroup: addressGroup === -1 ? undefined : addressGroup
}
}
+export function formatChain(networkId: NetworkId, addressGroup: AddressGroup): string {
+ const addressGroupNumber = toAddressGroupNumber(addressGroup)
+ return `${PROVIDER_NAMESPACE}:${addressGroupNumber}_${networkId}`
+}
+
+export function formatChainLegacy(networkId: NetworkId, addressGroup: AddressGroup): string {
+ if (addressGroup !== undefined && addressGroup < 0) {
+ throw Error('Address group in provider needs to be either undefined or non-negative')
+ }
+ const addressGroupNumber = toAddressGroupNumber(addressGroup)
+ return `${PROVIDER_NAMESPACE}:${networkId}/${addressGroupNumber}`
+}
+
export function formatAccount(permittedChain: string, account: Account): string {
+ return `${permittedChain}:${account.publicKey}_${account.keyType}`
+}
+
+export function formatAccountLegacy(permittedChain: string, account: Account): string {
return `${permittedChain}:${account.publicKey}/${account.keyType}`
}
-export function parseAccount(account: string): Account & { networkId: NetworkId } {
+export function parseAccount(accountString: string): Account & { networkId: NetworkId } {
+ try {
+ const [_namespace, _group, networkId, publicKey, keyType] = accountString.replace(/_/g, ':').split(':')
+ const address = addressFromPublicKey(publicKey)
+ const group = groupOfAddress(address)
+ if (keyType !== 'default' && keyType !== 'bip340-schnorr') {
+ throw Error(`Invalid key type: ${keyType}`)
+ }
+ return { address, group, publicKey, keyType, networkId: networkId as NetworkId }
+ } catch (error) {
+ console.debug(`Failed to parse account ${accountString}, falling back to legacy parsing`)
+ return parseAccountLegacy(accountString)
+ }
+}
+
+export function parseAccountLegacy(account: string): Account & { networkId: NetworkId } {
const [_namespace, networkId, _group, publicKey, keyType] = account.replace(/\//g, ':').split(':')
const address = addressFromPublicKey(publicKey)
const group = groupOfAddress(address)
@@ -577,3 +630,15 @@ function RateLimit(rps: number) {
setTimeout(() => sema.release(), delay)
}
}
+
+function toAddressGroupNumber(addressGroup: AddressGroup): number {
+ const groupNumber = addressGroup !== undefined ? addressGroup : -1
+ validateAddressGroup(groupNumber)
+ return groupNumber
+}
+
+function validateAddressGroup(addressGroup: number) {
+ if (!VALID_ADDRESS_GROUPS.includes(addressGroup)) {
+ throw Error('Address group must be -1 (for any groups) or between 0 and 3 (inclusive)')
+ }
+}
diff --git a/packages/walletconnect/test/walletconnect.test.ts b/packages/walletconnect/test/walletconnect.test.ts
index 41711db29..5ee94c578 100644
--- a/packages/walletconnect/test/walletconnect.test.ts
+++ b/packages/walletconnect/test/walletconnect.test.ts
@@ -15,7 +15,14 @@ GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the library. If not, see .
*/
-import { formatChain, parseChain, ProviderOptions, WalletConnectProvider } from '../src/index'
+import {
+ parseChain,
+ formatChain,
+ formatChainLegacy,
+ parseChainLegacy,
+ ProviderOptions,
+ WalletConnectProvider
+} from '../src/index'
import { WalletClient } from './shared'
import { web3, node, NodeProvider, verifySignedMessage, groupOfAddress, NetworkId } from '@alephium/web3'
import { PrivateKeyWallet } from '@alephium/web3-wallet'
@@ -112,15 +119,30 @@ describe('Unit tests', function () {
const expectedAddressGroup0 = 2
const expectedAddressGroup1 = 1
+ it('test formatChainLegacy & parseChainLegacy', () => {
+ expect(formatChainLegacy('devnet', expectedAddressGroup0)).toEqual('alephium:devnet/2')
+ expect(formatChainLegacy('devnet', expectedAddressGroup1)).toEqual('alephium:devnet/1')
+ expect(formatChainLegacy('devnet', undefined)).toEqual('alephium:devnet/-1')
+ expect(() => formatChainLegacy('devnet', -1)).toThrow()
+ expect(parseChainLegacy('alephium:devnet/2')).toEqual({ networkId: 'devnet', addressGroup: 2 })
+ expect(parseChainLegacy('alephium:devnet/1')).toEqual({ networkId: 'devnet', addressGroup: 1 })
+ expect(parseChainLegacy('alephium:devnet/-1')).toEqual({ networkId: 'devnet', addressGroup: undefined })
+ expect(() => parseChainLegacy('alephium:devnet/-2')).toThrow()
+ })
+
it('test formatChain & parseChain', () => {
- expect(formatChain('devnet', expectedAddressGroup0)).toEqual('alephium:devnet/2')
- expect(formatChain('devnet', expectedAddressGroup1)).toEqual('alephium:devnet/1')
- expect(formatChain('devnet', undefined)).toEqual('alephium:devnet/-1')
- expect(() => formatChain('devnet', -1)).toThrow()
+ expect(formatChain('devnet', expectedAddressGroup0)).toEqual('alephium:2_devnet')
+ expect(formatChain('devnet', expectedAddressGroup1)).toEqual('alephium:1_devnet')
+ expect(formatChain('devnet', undefined)).toEqual('alephium:-1_devnet')
+ expect(() => formatChain('devnet', -2)).toThrow()
expect(parseChain('alephium:devnet/2')).toEqual({ networkId: 'devnet', addressGroup: 2 })
expect(parseChain('alephium:devnet/1')).toEqual({ networkId: 'devnet', addressGroup: 1 })
expect(parseChain('alephium:devnet/-1')).toEqual({ networkId: 'devnet', addressGroup: undefined })
+ expect(parseChain('alephium:2_devnet')).toEqual({ networkId: 'devnet', addressGroup: 2 })
+ expect(parseChain('alephium:1_devnet')).toEqual({ networkId: 'devnet', addressGroup: 1 })
+ expect(parseChain('alephium:-1_devnet')).toEqual({ networkId: 'devnet', addressGroup: undefined })
expect(() => parseChain('alephium:devnet/-2')).toThrow()
+ expect(() => parseChain('alephium:-2_devnet')).toThrow()
})
it('should initialize providers', async () => {
@@ -153,7 +175,7 @@ describe('WalletConnectProvider with single addressGroup', function () {
walletAddress = walletClient.signer.address
expect(walletAddress).toEqual(ACCOUNTS.a.address)
await provider.connect()
- expect(provider.permittedChain).toEqual('alephium:devnet/0')
+ expect(provider.permittedChain).toEqual('alephium:0_devnet')
const selectetAddress = (await provider.getSelectedAccount()).address
expect(selectetAddress).toEqual(signerA.address)
await waitWalletConnected(walletClient)
@@ -196,7 +218,7 @@ describe('WalletConnectProvider with single addressGroup', function () {
// change to account b, which is not supported
expectThrowsAsync(
async () => await walletClient.changeAccount(ACCOUNTS.b.privateKey),
- 'Error changing account, chain alephium:devnet/1 not permitted'
+ 'Error changing account, chain alephium:1_devnet not permitted'
)
})
@@ -221,7 +243,7 @@ describe('WalletConnectProvider with arbitrary addressGroup', function () {
walletAddress = walletClient.signer.address
expect(walletAddress).toEqual(ACCOUNTS.a.address)
await provider.connect()
- expect(provider.permittedChain).toEqual('alephium:devnet/-1')
+ expect(provider.permittedChain).toEqual('alephium:-1_devnet')
const selectedAddress = (await provider.getSelectedAccount()).address
expect(selectedAddress).toEqual(signerA.address)
await waitWalletConnected(walletClient)