diff --git a/packages/utils/test/cacao.spec.ts b/packages/utils/test/cacao.spec.ts index 3ebfbf556..e0692e6ab 100644 --- a/packages/utils/test/cacao.spec.ts +++ b/packages/utils/test/cacao.spec.ts @@ -256,7 +256,7 @@ describe("URI", () => { expect(message).to.include("Nonce: 32891756"); expect(message).to.include(`URI: ${request.aud}`); }); - describe("resurces", () => { + describe("resources", () => { it("should not add resources to siwe message when missing from request", () => { const request = { type: "caip122", diff --git a/providers/universal-provider/src/UniversalProvider.ts b/providers/universal-provider/src/UniversalProvider.ts index 7474ed080..85f72fd89 100644 --- a/providers/universal-provider/src/UniversalProvider.ts +++ b/providers/universal-provider/src/UniversalProvider.ts @@ -16,6 +16,7 @@ import PolkadotProvider from "./providers/polkadot"; import Eip155Provider from "./providers/eip155"; import SolanaProvider from "./providers/solana"; import CosmosProvider from "./providers/cosmos"; +import AlgorandProvider from "./providers/algorand"; import CardanoProvider from "./providers/cardano"; import ElrondProvider from "./providers/elrond"; import MultiversXProvider from "./providers/multiversx"; @@ -339,6 +340,11 @@ export class UniversalProvider implements IUniversalProvider { namespace: combinedNamespace, }); break; + case "algorand": + this.rpcProviders[namespace] = new AlgorandProvider({ + namespace: combinedNamespace, + }); + break; case "solana": this.rpcProviders[namespace] = new SolanaProvider({ namespace: combinedNamespace, diff --git a/providers/universal-provider/src/providers/algorand.ts b/providers/universal-provider/src/providers/algorand.ts new file mode 100644 index 000000000..6214205b7 --- /dev/null +++ b/providers/universal-provider/src/providers/algorand.ts @@ -0,0 +1,125 @@ +import HttpConnection from "@walletconnect/jsonrpc-http-connection"; +import { JsonRpcProvider } from "@walletconnect/jsonrpc-provider"; +import Client from "@walletconnect/sign-client"; +import { EngineTypes, SessionTypes } from "@walletconnect/types"; +import EventEmitter from "events"; +import { PROVIDER_EVENTS } from "../constants"; +import { + IProvider, + RequestParams, + RpcProvidersMap, + SessionNamespace, + SubProviderOpts, +} from "../types"; +import { getGlobal, getRpcUrl } from "../utils"; + +class AlgorandProvider implements IProvider { + public name = "algorand"; + public client: Client; + public httpProviders: RpcProvidersMap; + public events: EventEmitter; + public namespace: SessionNamespace; + public chainId: string; + + constructor(opts: SubProviderOpts) { + this.namespace = opts.namespace; + this.events = getGlobal("events"); + this.client = getGlobal("client"); + this.chainId = this.getDefaultChain(); + this.httpProviders = this.createHttpProviders(); + } + + public updateNamespace(namespace: SessionTypes.Namespace) { + this.namespace = Object.assign(this.namespace, namespace); + } + + public requestAccounts(): string[] { + return this.getAccounts(); + } + + public request(args: RequestParams): Promise { + if (this.namespace.methods.includes(args.request.method)) { + return this.client.request(args as EngineTypes.RequestParams); + } + return this.getHttpProvider().request(args.request); + } + + public setDefaultChain(chainId: string, rpcUrl?: string | undefined) { + // http provider exists so just set the chainId + if (!this.httpProviders[chainId]) { + const rpc = + rpcUrl || getRpcUrl(`${this.name}:${chainId}`, this.namespace, this.client.core.projectId); + if (!rpc) { + throw new Error(`No RPC url provided for chainId: ${chainId}`); + } + this.setHttpProvider(chainId, rpc); + } + this.chainId = chainId; + this.events.emit(PROVIDER_EVENTS.DEFAULT_CHAIN_CHANGED, `${this.name}:${this.chainId}`); + } + + public getDefaultChain(): string { + if (this.chainId) return this.chainId; + if (this.namespace.defaultChain) return this.namespace.defaultChain; + + const chainId = this.namespace.chains[0]; + if (!chainId) throw new Error(`ChainId not found`); + + return chainId.split(":")[1]; + } + + // --------- PRIVATE --------- // + + private getAccounts(): string[] { + const accounts = this.namespace.accounts; + if (!accounts) { + return []; + } + + return [ + ...new Set( + accounts + // get the accounts from the active chain + .filter((account) => account.split(":")[1] === this.chainId.toString()) + // remove namespace & chainId from the string + .map((account) => account.split(":")[2]), + ), + ]; + } + + private createHttpProviders(): RpcProvidersMap { + const http = {}; + this.namespace.chains.forEach((chain) => { + http[chain] = this.createHttpProvider(chain, this.namespace.rpcMap?.[chain]); + }); + return http; + } + + private getHttpProvider(): JsonRpcProvider { + const chain = `${this.name}:${this.chainId}`; + const http = this.httpProviders[chain]; + if (typeof http === "undefined") { + throw new Error(`JSON-RPC provider for ${chain} not found`); + } + return http; + } + + private setHttpProvider(chainId: string, rpcUrl?: string): void { + const http = this.createHttpProvider(chainId, rpcUrl); + if (http) { + this.httpProviders[chainId] = http; + } + } + + private createHttpProvider( + chainId: string, + rpcUrl?: string | undefined, + ): JsonRpcProvider | undefined { + const rpc = rpcUrl || getRpcUrl(chainId, this.namespace, this.client.core.projectId); + if (typeof rpc === "undefined") return undefined; + const http = new JsonRpcProvider(new HttpConnection(rpc, getGlobal("disableProviderPing"))); + return http; + } +} + +export default AlgorandProvider; diff --git a/providers/universal-provider/src/providers/index.ts b/providers/universal-provider/src/providers/index.ts index 828f72c79..4c91d25fd 100644 --- a/providers/universal-provider/src/providers/index.ts +++ b/providers/universal-provider/src/providers/index.ts @@ -1,3 +1,4 @@ +export * as AlgorandProvider from "./algorand"; export * as Eip155Provider from "./eip155"; export * as SolanaProvider from "./solana"; export * as CosmosProvider from "./cosmos"; diff --git a/providers/universal-provider/test/shared/constants.ts b/providers/universal-provider/test/shared/constants.ts index 80c41e284..36a4bc660 100644 --- a/providers/universal-provider/test/shared/constants.ts +++ b/providers/universal-provider/test/shared/constants.ts @@ -74,6 +74,7 @@ export const EIP155_TEST_METHODS = [ "wallet_switchEthereumChain", ]; +export const ALGORAND_TEST_METHODS = ["algo_signTxn"]; export const COSMOS_TEST_METHODS = ["cosmos_signDirect", "cosmos_signAmino"]; export const ELROND_TEST_METHODS = ["erd_signTransaction", "erd_signLoginToken"]; export const MULTIVERSX_TEST_METHODS = ["multiversx_signTransaction", "multiversx_signMessage"]; @@ -89,6 +90,15 @@ export const TEST_NAMESPACES_CONFIG = { [CHAIN_ID_B]: RPC_URL_B, }, }, + algorand: { + methods: ALGORAND_TEST_METHODS, + chains: [`algorand:${CHAIN_ID}`, `algorand:${CHAIN_ID_B}`], + events: ["chainChanged", "accountsChanged"], + rpcMap: { + [CHAIN_ID]: RPC_URL, + [CHAIN_ID_B]: RPC_URL_B, + }, + }, cosmos: { methods: COSMOS_TEST_METHODS, chains: [`cosmos:${CHAIN_ID}`, `cosmos:${CHAIN_ID_B}`],