diff --git a/examples/next/src/app/page.tsx b/examples/next/src/app/page.tsx index 46f4984ec..adb934ec9 100644 --- a/examples/next/src/app/page.tsx +++ b/examples/next/src/app/page.tsx @@ -1,13 +1,12 @@ import { Transfer } from "components/Transfer"; import { ManualTransferEth } from "components/ManualTransferEth"; -import { ConnectWallet } from "components/ConnectWallet"; import { InvalidTxn } from "components/InvalidTxn"; import { SignMessage } from "components/SignMessage"; import { DelegateAccount } from "components/DelegateAccount"; import { ColorModeToggle } from "components/ColorModeToggle"; import { Profile } from "components/Profile"; -import { Settings } from "components/Settings"; import { LookupControllers } from "components/LookupControllers"; +import Header from "components/Header"; export default function Home() { return ( @@ -18,8 +17,7 @@ export default function Home() { - - +
diff --git a/examples/next/src/components/Header.tsx b/examples/next/src/components/Header.tsx new file mode 100644 index 000000000..76cf50999 --- /dev/null +++ b/examples/next/src/components/Header.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { Button } from "@cartridge/ui-next"; +import ControllerConnector from "@cartridge/connector/controller"; +import { useAccount, useConnect, useDisconnect } from "@starknet-react/core"; +import { useState } from "react"; + +const Header = ({ + showBack, + lockChain, +}: { + showBack?: boolean; + lockChain?: boolean; +}) => { + const { connect, connectors } = useConnect(); + const { disconnect } = useDisconnect(); + const { address, connector } = useAccount(); + const controllerConnector = connector as never as ControllerConnector; + const chains = [ + { name: "Mainnet", id: "mainnet" }, + { name: "Sepolia (Testnet)", id: "sepolia" }, + { name: "Slot (L3)", id: "slot" }, + ]; + + const chainName = "chain name"; + const [networkOpen, setNetworkOpen] = useState(false); + const [profileOpen, setProfileOpen] = useState(false); + + // const chainName = useMemo(() => { + // return chains.find((c) => c.id === getCurrentChain())?.name; + // }, [chains, getCurrentChain]); + + return ( +
+ {showBack && ( + + )} +
+
+ + {networkOpen && ( +
+ {chains.map((c) => ( + + ))} +
+ )} +
+ {address ? ( +
+ + {profileOpen && ( +
+ + + +
+ )} +
+ ) : ( + + )} +
+ ); +}; + +export default Header; diff --git a/examples/next/src/components/providers/StarknetProvider.tsx b/examples/next/src/components/providers/StarknetProvider.tsx index edf0cb66e..3501d1a06 100644 --- a/examples/next/src/components/providers/StarknetProvider.tsx +++ b/examples/next/src/components/providers/StarknetProvider.tsx @@ -108,7 +108,7 @@ export function StarknetProvider({ children }: PropsWithChildren) { const controller = new ControllerConnector({ policies, - rpc, + rpcUrl: rpc, url: process.env.NEXT_PUBLIC_KEYCHAIN_DEPLOYMENT_URL ?? process.env.NEXT_PUBLIC_KEYCHAIN_FRAME_URL, diff --git a/packages/controller/src/account.ts b/packages/controller/src/account.ts index 13499d8e4..52e5b0a0b 100644 --- a/packages/controller/src/account.ts +++ b/packages/controller/src/account.ts @@ -32,7 +32,7 @@ class ControllerAccount extends WalletAccount { options: KeychainOptions, modal: Modal, ) { - super({ nodeUrl: provider.rpc.toString() }, provider); + super(provider, provider); this.address = address; this.keychain = keychain; diff --git a/packages/controller/src/controller.ts b/packages/controller/src/controller.ts index 847259602..066c53369 100644 --- a/packages/controller/src/controller.ts +++ b/packages/controller/src/controller.ts @@ -17,6 +17,7 @@ import { import BaseProvider from "./provider"; import { WalletAccount } from "starknet"; import { Policy } from "@cartridge/presets"; +import { AddStarknetChainParameters } from "@starknet-io/types-js"; export default class ControllerProvider extends BaseProvider { private keychain?: AsyncMethodReturns; @@ -25,9 +26,9 @@ export default class ControllerProvider extends BaseProvider { private iframes: IFrames; constructor(options: ControllerOptions) { - const { rpc } = options; - super({ rpc }); + super(options); + this.rpcUrl = options.rpcUrl; this.iframes = { keychain: new KeychainIFrame({ ...options, @@ -54,9 +55,7 @@ export default class ControllerProvider extends BaseProvider { return; } - const response = (await this.keychain.probe( - this.rpc.toString(), - )) as ProbeReply; + const response = (await this.keychain.probe(this.rpcUrl)) as ProbeReply; this.account = new ControllerAccount( this, @@ -83,7 +82,7 @@ export default class ControllerProvider extends BaseProvider { openPurchaseCredits: () => this.openPurchaseCredits.bind(this), openExecute: () => this.openExecute.bind(this), }, - rpcUrl: this.rpc.toString(), + rpcUrl: this.rpcUrl, username, version: this.version, }); @@ -114,7 +113,7 @@ export default class ControllerProvider extends BaseProvider { try { let response = await this.keychain.connect( this.options.policies || [], - this.rpc.toString(), + this.rpcUrl, ); if (response.code !== ResponseCodes.SUCCESS) { throw new Error(response.message); @@ -137,6 +136,14 @@ export default class ControllerProvider extends BaseProvider { } } + switchStarknetChain(_chainId: string): Promise { + return Promise.resolve(true); + } + + addStarknetChain(_chain: AddStarknetChainParameters): Promise { + return Promise.resolve(true); + } + async disconnect() { if (!this.keychain) { console.error(new NotReadyToConnect().message); diff --git a/packages/controller/src/provider.ts b/packages/controller/src/provider.ts index ab65cb2c6..bb8b93df1 100644 --- a/packages/controller/src/provider.ts +++ b/packages/controller/src/provider.ts @@ -1,10 +1,12 @@ -import { WalletAccount } from "starknet"; +import { Provider, WalletAccount } from "starknet"; import { AddInvokeTransactionParameters, + AddStarknetChainParameters, Errors, Permission, RequestFn, StarknetWindowObject, + SwitchStarknetChainParameters, TypedData, WalletEventHandlers, WalletEventListener, @@ -14,20 +16,22 @@ import { import { icon } from "./icon"; import { ProviderOptions } from "./types"; -export default abstract class BaseProvider implements StarknetWindowObject { +export default abstract class BaseProvider + extends Provider + implements StarknetWindowObject +{ public id = "controller"; public name = "Controller"; public version = "0.4.0"; public icon = icon; - public rpc: URL; public account?: WalletAccount; public subscriptions: WalletEvents[] = []; + protected rpcUrl: string; constructor(options: ProviderOptions) { - const { rpc } = options; - - this.rpc = new URL(rpc); + super({ nodeUrl: options.rpcUrl }); + this.rpcUrl = options.rpcUrl; } request: RequestFn = async (call) => { @@ -65,19 +69,15 @@ export default abstract class BaseProvider implements StarknetWindowObject { data: "wallet_watchAsset not implemented", } as Errors.UNEXPECTED_ERROR; - case "wallet_addStarknetChain": - throw { - code: 63, - message: "An unexpected error occurred", - data: "wallet_addStarknetChain not implemented", - } as Errors.UNEXPECTED_ERROR; + case "wallet_addStarknetChain": { + let params = call.params as AddStarknetChainParameters; + return this.addStarknetChain(params); + } - case "wallet_switchStarknetChain": - throw { - code: 63, - message: "An unexpected error occurred", - data: "wallet_switchStarknetChain not implemented", - } as Errors.UNEXPECTED_ERROR; + case "wallet_switchStarknetChain": { + let params = call.params as SwitchStarknetChainParameters; + return this.switchStarknetChain(params.chainId); + } case "wallet_requestChainId": if (!this.account) { @@ -174,4 +174,8 @@ export default abstract class BaseProvider implements StarknetWindowObject { abstract probe(): Promise; abstract connect(): Promise; + abstract switchStarknetChain(chainId: string): Promise; + abstract addStarknetChain( + chain: AddStarknetChainParameters, + ): Promise; } diff --git a/packages/controller/src/session/provider.ts b/packages/controller/src/session/provider.ts index 5611227ba..3d07bf7d3 100644 --- a/packages/controller/src/session/provider.ts +++ b/packages/controller/src/session/provider.ts @@ -5,6 +5,7 @@ import { KEYCHAIN_URL } from "../constants"; import BaseProvider from "../provider"; import { toWasmPolicies } from "../utils"; import { SessionPolicies } from "@cartridge/presets"; +import { AddStarknetChainParameters } from "@starknet-io/types-js"; interface SessionRegistration { username: string; @@ -32,7 +33,7 @@ export default class SessionProvider extends BaseProvider { protected _policies: SessionPolicies; constructor({ rpc, chainId, policies, redirectUrl }: SessionOptions) { - super({ rpc }); + super({ rpcUrl: rpc }); this._chainId = chainId; this._redirectUrl = redirectUrl; @@ -76,7 +77,7 @@ export default class SessionProvider extends BaseProvider { this._redirectUrl }&redirect_query_name=startapp&policies=${JSON.stringify( this._policies, - )}&rpc_url=${this.rpc}`; + )}&rpc_url=${this.rpcUrl}`; localStorage.setItem("lastUsedConnector", this.id); window.open(url, "_blank"); @@ -84,6 +85,14 @@ export default class SessionProvider extends BaseProvider { return; } + switchStarknetChain(_chainId: string): Promise { + return Promise.resolve(true); + } + + addStarknetChain(_chain: AddStarknetChainParameters): Promise { + return Promise.resolve(true); + } + disconnect(): Promise { localStorage.removeItem("sessionSigner"); localStorage.removeItem("session"); @@ -127,7 +136,7 @@ export default class SessionProvider extends BaseProvider { this._username = sessionRegistration.username; this.account = new SessionAccount(this, { - rpcUrl: this.rpc.toString(), + rpcUrl: this.rpcUrl.toString(), privateKey: signer.privKey, address: sessionRegistration.address, ownerGuid: sessionRegistration.ownerGuid, diff --git a/packages/controller/src/telegram/provider.ts b/packages/controller/src/telegram/provider.ts index 83cc3cbc2..4de75a364 100644 --- a/packages/controller/src/telegram/provider.ts +++ b/packages/controller/src/telegram/provider.ts @@ -11,6 +11,7 @@ import SessionAccount from "../session/account"; import BaseProvider from "../provider"; import { toWasmPolicies } from "../utils"; import { SessionPolicies } from "@cartridge/presets"; +import { AddStarknetChainParameters } from "@starknet-io/types-js"; interface SessionRegistration { username: string; @@ -38,7 +39,7 @@ export default class TelegramProvider extends BaseProvider { tmaUrl: string; }) { super({ - rpc, + rpcUrl: rpc, }); this._tmaUrl = tmaUrl; @@ -77,7 +78,7 @@ export default class TelegramProvider extends BaseProvider { this._tmaUrl }&redirect_query_name=startapp&policies=${JSON.stringify( this._policies, - )}&rpc_url=${this.rpc}`; + )}&rpc_url=${this.rpcUrl}`; localStorage.setItem("lastUsedConnector", this.id); openLink(url); @@ -86,6 +87,14 @@ export default class TelegramProvider extends BaseProvider { return; } + switchStarknetChain(_chainId: string): Promise { + return Promise.resolve(true); + } + + addStarknetChain(_chain: AddStarknetChainParameters): Promise { + return Promise.resolve(true); + } + disconnect(): Promise { cloudStorage.deleteItem("sessionSigner"); cloudStorage.deleteItem("session"); @@ -118,7 +127,7 @@ export default class TelegramProvider extends BaseProvider { this._username = sessionRegistration.username; this.account = new SessionAccount(this, { - rpcUrl: this.rpc.toString(), + rpcUrl: this.rpcUrl.toString(), privateKey: signer.privKey, address: sessionRegistration.address, ownerGuid: sessionRegistration.ownerGuid, diff --git a/packages/controller/src/types.ts b/packages/controller/src/types.ts index 2faf8ac06..f14bcf04d 100644 --- a/packages/controller/src/types.ts +++ b/packages/controller/src/types.ts @@ -17,6 +17,7 @@ import { Policy, SessionPolicies, } from "@cartridge/presets"; +import { RequestFn } from "@starknet-io/types-js"; export type Session = { chainId: constants.StarknetChainId; @@ -133,7 +134,10 @@ export interface Keychain { fetchControllers(contractAddresses: string[]): Promise; openPurchaseCredits(): void; openExecute(calls: Call[]): Promise; + + request: RequestFn; } + export interface Profile { navigate(path: string): void; } @@ -162,8 +166,7 @@ export type IFrameOptions = { }; export type ProviderOptions = { - /** The URL of the RPC */ - rpc: string; + rpcUrl: string; }; export type KeychainOptions = IFrameOptions & {