diff --git a/docs/get-starknet flow.png b/docs/get-starknet flow.png new file mode 100644 index 0000000..c73b6ba Binary files /dev/null and b/docs/get-starknet flow.png differ diff --git a/example/package.json b/example/package.json index 7409e28..ea153d6 100644 --- a/example/package.json +++ b/example/package.json @@ -11,7 +11,8 @@ "dependencies": { "get-starknet": "workspace:^2.0.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "starknet": "^4.22.0" }, "devDependencies": { "@types/react": "^18.0.15", diff --git a/example/src/App.tsx b/example/src/App.tsx index c9803a8..9e220d1 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -2,29 +2,95 @@ import "./App.css" import { type ConnectOptions, type DisconnectOptions, + StarknetWindowObject, connect, disconnect, } from "get-starknet" import { useState } from "react" +import { + AccountInterface, + Contract, + ProviderInterface, + stark, + uint256, +} from "starknet" + +const ADDR_GROM_TOKEN = + "0x053fe9d5fdce9c4cae6ad2a03b0ce2a3fc5cb3a4e6a18475addd1e09e67807b5" +const CLASS_HASH_GROM_TOKEN = + "0x2784249255cace9a06ef1e25fa31f438a937a251e41ae80852afb1700755c9e" function App() { - const [walletName, setWalletName] = useState("") + const [swo, setSWO] = useState() + const [balance, setBalance] = useState("") + const [txhash, setTxhash] = useState("") function handleConnect(options?: ConnectOptions) { return async () => { const res = await connect(options) console.log(res) - setWalletName(res?.name || "") + res?.on("accountsChanged", (account: string[]) => + console.log("catch account changed: " + account[0]), + ) + res?.on("networkChanged", (s?: string) => + console.log("catch network change: " + s), + ) + if (res !== null) setSWO(res) } } function handleDisconnect(options?: DisconnectOptions) { return async () => { await disconnect(options) - setWalletName("") + setSWO(undefined) + } + } + + const getERC20Balance = async () => { + if (swo !== undefined && swo.provider != undefined) { + const contractClass = await swo.provider.getClassAt(ADDR_GROM_TOKEN) + if (contractClass.abi) { + const ERC20Contract = new Contract( + contractClass.abi, + ADDR_GROM_TOKEN, + swo.provider, + ) + const balance = await ERC20Contract.balanceOf(swo.account?.address) + console.log(uint256.uint256ToBN(balance.balance).toString()) + setBalance(uint256.uint256ToBN(balance.balance).toString()) + } } } + const transferToken = async () => { + const dest = + "0x448bda3c2c437ca22579fed986eb65519820c52f2249a00370ec0f3d1902d78" + // Execute tx transfer of 10 tokens + console.log(`Invoke Tx - Transfer 10 tokens`) + const toTransferTk = uint256.bnToUint256(10) + const transferCallData = stark.compileCalldata({ + recipient: dest, + amount: { + type: "struct", + low: toTransferTk.low, + high: toTransferTk.high, + }, + }) + + const { transaction_hash: transferTxHash } = await swo?.account?.execute( + { + contractAddress: ADDR_GROM_TOKEN, + entrypoint: "transfer", + calldata: transferCallData, + }, + undefined, + { maxFee: 900_000_000_000_000 }, + ) + + console.log("Transfer Tx hash: " + transferTxHash) + setTxhash(transferTxHash) + } + return (

get-starknet

@@ -55,10 +121,16 @@ function App() { Disconnect and reset
- {walletName && ( + {swo && (

- Selected Wallet:
{walletName}
+ Selected Wallet: +
{swo.name}
+
{swo.selectedAddress}
+ + {balance &&
{balance}
} + + {txhash &&
Tx hash: {txhash}
}

)} diff --git a/packages/core/package.json b/packages/core/package.json index d302c46..8597bee 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -29,17 +29,20 @@ "dev": "vite build --watch", "test": "vitest" }, + "dependencies": { + "penpal": "^6.2.2" + }, "devDependencies": { "c8": "^7.12.0", "happy-dom": "^6.0.4", - "starknet": "^4.9.0", + "starknet": "^4.22.0", "typescript": "^4.6.4", "vite": "^3.0.0", "vite-plugin-dts": "^1.4.0", "vitest": "^0.19.1" }, "peerDependencies": { - "starknet": "^4.9.0" + "starknet": "^4.22.0" }, "peerDependenciesMeta": { "starknet": { diff --git a/packages/core/src/discovery.ts b/packages/core/src/discovery.ts index c7a25d8..da7b621 100644 --- a/packages/core/src/discovery.ts +++ b/packages/core/src/discovery.ts @@ -1,14 +1,22 @@ -export type WalletProvider = { +export interface WalletProvider { id: string name: string icon: string +} + +export interface ExtensionWalletProvider extends WalletProvider { downloads: | { chrome?: `https://chrome.google.com/webstore/detail/${string}` } | { firefox?: `https://addons.mozilla.org/en-US/firefox/addon/${string}` } | { edge?: `https://microsoftedge.microsoft.com/addons/detail/${string}` } } -const wallets: WalletProvider[] = [ +export interface WebWalletProvider extends WalletProvider { + url_login: string | undefined + url_account: string | undefined +} + +export const xWallets: ExtensionWalletProvider[] = [ { id: "argentX", name: "Argent X", @@ -32,4 +40,12 @@ const wallets: WalletProvider[] = [ }, ] -export default wallets +export const wWallets: WebWalletProvider[] = [ + { + id: "fresh", + name: "Fresh", + icon: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDIzLjAuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMTQ5LjA0IDEwNDkuNDciIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDExNDkuMDQgMTA0OS40NzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiNGRkZGRkY7fQo8L3N0eWxlPgo8cmVjdCBjbGFzcz0ic3QwIiB3aWR0aD0iMTE0OS4wNCIgaGVpZ2h0PSIxMDQ5LjQ3Ii8+CjxwYXRoIGQ9Ik0xOTAuMDYsNjY5LjM1djE5MC4wNmgyODkuMjJ2LTQyLjE0SDIzMi4yMVY2NjkuMzVIMTkwLjA2eiBNOTE2LjgzLDY2OS4zNXYxNDcuOTJINjY5Ljc1djQyLjE0aDI4OS4yMlY2NjkuMzVIOTE2LjgzegoJIE00NzkuNywzODAuMTJ2Mjg5LjIyaDE5MC4wNXYtMzguMDFINTIxLjg0VjM4MC4xMkg0NzkuN3ogTTE5MC4wNiwxOTAuMDZ2MTkwLjA2aDQyLjE0VjIzMi4yMWgyNDcuMDh2LTQyLjE0SDE5MC4wNnoKCSBNNjY5Ljc1LDE5MC4wNnY0Mi4xNGgyNDcuMDh2MTQ3LjkyaDQyLjE0VjE5MC4wNkg2NjkuNzV6Ii8+Cjwvc3ZnPgo=", + url_login: "http://localhost:3005/iframes/login", + url_account: "http://localhost:3005/iframes/account", + }, +] diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts index 82fd56d..a0f03ef 100644 --- a/packages/core/src/main.ts +++ b/packages/core/src/main.ts @@ -2,13 +2,19 @@ import { ConnectedStarknetWindowObject, StarknetWindowObject, } from "./StarknetWindowObject" -import discovery, { WalletProvider } from "./discovery" +import { + ExtensionWalletProvider, + WebWalletProvider, + wWallets, + xWallets, +} from "./discovery" import { IStorageWrapper, LocalStorageWrapper } from "./localStorageStore" import { pipe } from "./utils" import { FilterList, filterBy, filterByPreAuthorized } from "./wallet/filter" import { isWalletObj } from "./wallet/isWalletObject" import { scanObjectForWallets } from "./wallet/scan" import { Sort, sortBy } from "./wallet/sort" +import { getWebWalletStarknetObject } from "./webwallet/webWalletObject" export type { AccountChangeEventHandler, @@ -21,7 +27,7 @@ export type { WalletEvents, WatchAssetParameters, } from "./StarknetWindowObject" -export type { WalletProvider } from "./discovery" +export type { WebWalletProvider, ExtensionWalletProvider } from "./discovery" export interface GetStarknetOptions { windowObject: Record @@ -46,22 +52,29 @@ export interface GetWalletOptions { export interface DisconnectOptions { clearLastWallet?: boolean } + interface GetStarknetResult { - getAvailableWallets: ( + getInjectedWallets: ( options?: GetWalletOptions, ) => Promise // Returns all wallets available in the window object - getPreAuthorizedWallets: ( + getWebWallets: (options?: GetWalletOptions) => Promise // Return all available web wallets + getExtensionWallets: ( options?: GetWalletOptions, - ) => Promise // Returns only preauthorized wallets available in the window object - getDiscoveryWallets: (options?: GetWalletOptions) => Promise // Returns all wallets in existence (from discovery file) - getLastConnectedWallet: () => Promise // Returns the last wallet connected when it's still connected - enable: ( + ) => Promise // Return all available extension wallets + openWebWallet: ( + wwp: WebWalletProvider, + ) => Promise //// Connect with a web wallet to get StarknetWindowOject + connect: ( wallet: StarknetWindowObject, options?: { starknetVersion?: "v3" | "v4" }, ) => Promise // Connects to a wallet disconnect: (options?: DisconnectOptions) => Promise // Disconnects from a wallet + getPreAuthorizedWallets: ( + options?: GetWalletOptions, + ) => Promise // Returns only preauthorized wallets available in the window object + getLastConnectedWallet: () => Promise // Returns the last wallet connected when it's still connected } export function getStarknet( @@ -74,7 +87,7 @@ export function getStarknet( const lastConnectedStore = storageFactoryImplementation("gsw-last") return { - getAvailableWallets: async (options = {}) => { + getInjectedWallets: async (options = {}) => { const availableWallets = scanObjectForWallets( windowObject, isWalletObject, @@ -84,6 +97,35 @@ export function getStarknet( (_) => sortBy(_, options.sort), )(availableWallets) }, + getWebWallets: async (options = {}) => { + return pipe( + (_) => filterBy(_, options), + (_) => sortBy(_, options.sort), + )(wWallets) + }, + getExtensionWallets: async (options = {}) => { + return pipe( + (_) => filterBy(_, options), + (_) => sortBy(_, options.sort), + )(xWallets) + }, + openWebWallet: async (w: WebWalletProvider) => { + const wallet = await getWebWalletStarknetObject(w) + return wallet + }, + connect: async (wallet, options) => { + await wallet.enable(options) + if (!wallet.isConnected) { + throw new Error("Failed to connect to wallet") + } + lastConnectedStore.set(wallet.id) + return wallet + }, + disconnect: async ({ clearLastWallet } = {}) => { + if (clearLastWallet) { + lastConnectedStore.delete() + } + }, getPreAuthorizedWallets: async (options = {}) => { const availableWallets = scanObjectForWallets( windowObject, @@ -95,12 +137,6 @@ export function getStarknet( (_) => sortBy(_, options.sort), )(availableWallets) }, - getDiscoveryWallets: async (options = {}) => { - return pipe( - (_) => filterBy(_, options), - (_) => sortBy(_, options.sort), - )(discovery) - }, getLastConnectedWallet: async () => { const lastConnectedWalletId = lastConnectedStore.get() const allWallets = scanObjectForWallets(windowObject, isWalletObject) @@ -118,19 +154,6 @@ export function getStarknet( return firstPreAuthorizedWallet }, - enable: async (wallet, options) => { - await wallet.enable(options) - if (!wallet.isConnected) { - throw new Error("Failed to connect to wallet") - } - lastConnectedStore.set(wallet.id) - return wallet - }, - disconnect: async ({ clearLastWallet } = {}) => { - if (clearLastWallet) { - lastConnectedStore.delete() - } - }, } } diff --git a/packages/core/src/webwallet/account.ts b/packages/core/src/webwallet/account.ts new file mode 100644 index 0000000..4cf505d --- /dev/null +++ b/packages/core/src/webwallet/account.ts @@ -0,0 +1,61 @@ +import { ChildApi } from "./connection" +import type { AsyncMethodReturns } from "penpal" +import { + Abi, + Account, + AccountInterface, + AllowArray, + Call, + InvocationsDetails, + InvokeFunctionResponse, + ProviderInterface, + Signature, + SignerInterface, + typedData, +} from "starknet" + +class UnimplementedSigner implements SignerInterface { + async getPubKey(): Promise { + throw new Error("Method not implemented") + } + + async signMessage(): Promise { + throw new Error("Method not implemented") + } + + async signTransaction(): Promise { + throw new Error("Method not implemented") + } + + async signDeclareTransaction(): Promise { + throw new Error("Method not implemented") + } + + async signDeployAccountTransaction(): Promise { + throw new Error("Method not implemented") + } +} + +export class MessageAccount extends Account implements AccountInterface { + public signer = new UnimplementedSigner() + + constructor( + provider: ProviderInterface, + public address: string, + private readonly child: AsyncMethodReturns, + ) { + super(provider, address, new UnimplementedSigner()) + } + + execute( + calls: AllowArray, + abis?: Abi[] | undefined, + transactionsDetail?: InvocationsDetails | undefined, + ): Promise { + return this.child.execute(calls, abis, transactionsDetail) + } + + signMessage(typedData: typedData.TypedData): Promise { + return this.child.signMessage(typedData) + } +} diff --git a/packages/core/src/webwallet/connection.ts b/packages/core/src/webwallet/connection.ts new file mode 100644 index 0000000..610c609 --- /dev/null +++ b/packages/core/src/webwallet/connection.ts @@ -0,0 +1,150 @@ +import { + AddStarknetChainParameters, + SwitchStarknetChainParameter, + WatchAssetParameters, +} from "../StarknetWindowObject" +import { userEventHandlers } from "./starknetObject" +import { AsyncMethodReturns, connectToChild } from "penpal" +import { + Abi, + AllowArray, + Call, + InvocationsDetails, + InvokeFunctionResponse, + Signature, + constants, + typedData, +} from "starknet" + +const applyModalStyle = (iframe: HTMLIFrameElement) => { + // middle of the screen + iframe.style.position = "fixed" + iframe.style.top = "50%" + iframe.style.left = "50%" + iframe.style.transform = "translate(-50%, -50%)" + iframe.style.width = "380px" + iframe.style.height = "420px" + iframe.style.border = "none" + + // round corners + iframe.style.borderRadius = "40px" + // box shadow + iframe.style.boxShadow = "0px 4px 20px rgba(0, 0, 0, 0.5)" + + const background = document.createElement("div") + background.style.display = "none" + background.style.position = "fixed" + background.style.top = "0" + background.style.left = "0" + background.style.right = "0" + background.style.bottom = "0" + background.style.backgroundColor = "rgba(0, 0, 0, 0.5)" + background.style.zIndex = "9999" + ;(background.style as any).backdropFilter = "blur(4px)" + + background.appendChild(iframe) + + return background +} + +export const showModal = (modal: HTMLDivElement) => { + modal.style.display = "block" +} + +export const hideModal = (modal: HTMLDivElement) => { + modal.style.display = "none" +} + +export const setIframeHeight = (modal: HTMLIFrameElement, height: number) => { + modal.style.height = `min(${height || 420}px, 100%)` +} + +export const createModal = async (targetUrl: string, shouldShow: boolean) => { + const iframe = document.createElement("iframe") + iframe.src = targetUrl + ;(iframe as any).loading = "eager" + iframe.sandbox.add( + "allow-scripts", + "allow-same-origin", + "allow-forms", + "allow-top-navigation", + "allow-popups", + ) + + const modal = applyModalStyle(iframe) + modal.style.display = shouldShow ? "block" : "none" + + console.log("modal", modal) + // append the modal to the body + window.document.body.appendChild(modal) + + // wait for the iframe to load + await new Promise((resolve, reject) => { + const pid = setTimeout( + () => reject(new Error("Timeout while loading an iframe")), + 20000, + ) + + iframe.addEventListener("load", async () => { + clearTimeout(pid) + resolve() + }) + }) + + return { iframe, modal } +} + +export interface ChildApi { + enable: () => { address: string; chainid: constants.StarknetChainId } + execute: ( + calls: AllowArray, + abis?: Abi[] | undefined, + transactionsDetail?: InvocationsDetails | undefined, + ) => InvokeFunctionResponse + isPreauthorized: (host: string) => boolean + signMessage: (typedData: typedData.TypedData) => Signature + addToken: (params: WatchAssetParameters) => boolean + addNetwork: (params: AddStarknetChainParameters) => boolean + switchNetwork: (params: SwitchStarknetChainParameter) => boolean +} + +export const getConnection = async ({ + iframe, + modal, +}: { + iframe: HTMLIFrameElement + modal: HTMLDivElement +}): Promise> => { + const connection = connectToChild({ + // The iframe to which a connection should be made. + iframe, + // Methods the parent is exposing to the child. + methods: { + heightChanged: ([height]: Array) => { + setIframeHeight(iframe, height) + }, + shouldHide: () => { + hideModal(modal) + }, + shouldShow: () => { + showModal(modal) + }, + notifyAccountChange: (address: string) => { + for (const userEvent of userEventHandlers) { + if (userEvent.type === "accountsChanged") { + userEvent.handler([address]) + } + } + }, + notifyNetworkChange: (chainID: constants.StarknetChainId) => { + for (const userEvent of userEventHandlers) { + if (userEvent.type === "networkChanged") { + userEvent.handler(chainID) + } + } + }, + }, + }) + + return await connection.promise +} diff --git a/packages/core/src/webwallet/requestHandlers.ts b/packages/core/src/webwallet/requestHandlers.ts new file mode 100644 index 0000000..ea1c23d --- /dev/null +++ b/packages/core/src/webwallet/requestHandlers.ts @@ -0,0 +1,28 @@ +import { + AddStarknetChainParameters, + SwitchStarknetChainParameter, + WatchAssetParameters, +} from "../StarknetWindowObject" +import { ChildApi } from "./connection" +import { AsyncMethodReturns } from "penpal" + +export async function handleAddTokenRequest( + child: AsyncMethodReturns, + callParams: WatchAssetParameters, +): Promise { + return await child.addToken(callParams) +} + +export async function handleAddNetworkRequest( + child: AsyncMethodReturns, + callParams: AddStarknetChainParameters, +): Promise { + return await child.addNetwork(callParams) +} + +export async function handleSwitchNetworkRequest( + child: AsyncMethodReturns, + callParams: SwitchStarknetChainParameter, +): Promise { + return await child.switchNetwork(callParams) +} diff --git a/packages/core/src/webwallet/starknetObject.ts b/packages/core/src/webwallet/starknetObject.ts new file mode 100644 index 0000000..e856bab --- /dev/null +++ b/packages/core/src/webwallet/starknetObject.ts @@ -0,0 +1,161 @@ +import type { + AccountChangeEventHandler, + AddStarknetChainParameters, + ConnectedStarknetWindowObject, + NetworkChangeEventHandler, + StarknetWindowObject, + SwitchStarknetChainParameter, + WalletEvents, + WatchAssetParameters, +} from "../StarknetWindowObject" +import { MessageAccount } from "./account" +import { ChildApi } from "./connection" +import { + handleAddNetworkRequest, + handleAddTokenRequest, + handleSwitchNetworkRequest, +} from "./requestHandlers" +import type { AsyncMethodReturns } from "penpal" +import { constants } from "starknet" +import { Provider } from "starknet" + +interface network { + name: string + chainid: constants.StarknetChainId + starknetProvider: string +} + +const networks: Array = [ + { + name: "SN_MAIN", + chainid: constants.StarknetChainId.MAINNET, + starknetProvider: "mainnet-alpha", + }, + { + name: "SN_GOERLI", + chainid: constants.StarknetChainId.TESTNET, + starknetProvider: "goerli-alpha", + }, + { + name: "SN_GOERLI2", + chainid: constants.StarknetChainId.TESTNET2, + starknetProvider: "goerli-alpha-2", + }, +] + +export interface StarknetConnectedObjectOption { + id: string + icon: string + name: string + version: string +} + +export const userEventHandlers: WalletEvents[] = [] + +function updateStarknetConnectedObject( + snObject: StarknetWindowObject, + walletAddress: string, + chainID: constants.StarknetChainId, + child: AsyncMethodReturns, +): ConnectedStarknetWindowObject { + if (snObject.isConnected) { + return snObject + } + + const provider = new Provider({ + sequencer: { + network: + networks.find((n) => n.chainid === chainID)?.chainid ?? + constants.StarknetChainId.MAINNET, + }, + }) + + const valuesToAssign: Pick< + ConnectedStarknetWindowObject, + "isConnected" | "chainId" | "selectedAddress" | "account" | "provider" + > = { + isConnected: true, + chainId: provider.chainId, + selectedAddress: walletAddress, + account: new MessageAccount(provider, walletAddress, child), + provider, + } + return Object.assign(snObject, valuesToAssign) +} + +export const getStarknetConnectedObject = ( + options: StarknetConnectedObjectOption, + child: AsyncMethodReturns, +): StarknetWindowObject => { + const starknet: StarknetWindowObject = { + ...options, + isConnected: false, + request: async (call) => { + if (call.type === "wallet_watchAsset" && call.params.type === "ERC20") { + return await handleAddTokenRequest( + child, + call.params as WatchAssetParameters, + ) + } else if (call.type === "wallet_addStarknetChain") { + return await handleAddNetworkRequest( + child, + call.params as AddStarknetChainParameters, + ) + } else if (call.type === "wallet_switchStarknetChain") { + return await handleSwitchNetworkRequest( + child, + call.params as SwitchStarknetChainParameter, + ) + } + throw Error("Not implemented") + }, + enable: async (ops) => { + if (ops?.starknetVersion === "v3") { + throw Error("not implemented") + } + console.log("enable") + + const res = await child.enable() + console.log("Address = " + res.address) + console.log( + "Network = " + + networks.find((n) => n.chainid === res.chainid)?.starknetProvider, + ) + updateStarknetConnectedObject(starknet, res.address, res.chainid, child) + + return [res.address] + }, + isPreauthorized: async () => { + return await child.isPreauthorized(window.location.host) + }, + on: (event, handleEvent) => { + if (event === "accountsChanged") { + userEventHandlers.push({ + type: event, + handler: handleEvent as AccountChangeEventHandler, + }) + } else if (event === "networkChanged") { + userEventHandlers.push({ + type: event, + handler: handleEvent as NetworkChangeEventHandler, + }) + } else { + throw new Error(`Unknwown event: ${event}`) + } + }, + off: (event, handleEvent) => { + if (event !== "accountsChanged" && event !== "networkChanged") { + throw new Error(`Unknwown event: ${event}`) + } + + const eventIndex = userEventHandlers.findIndex( + (userEvent) => + userEvent.type === event && userEvent.handler === handleEvent, + ) + if (eventIndex >= 0) { + userEventHandlers.splice(eventIndex, 1) + } + }, + } + return starknet +} diff --git a/packages/core/src/webwallet/webWalletObject.ts b/packages/core/src/webwallet/webWalletObject.ts new file mode 100644 index 0000000..a4aa194 --- /dev/null +++ b/packages/core/src/webwallet/webWalletObject.ts @@ -0,0 +1,35 @@ +import type { StarknetWindowObject } from "../StarknetWindowObject" +import { WebWalletProvider } from "../discovery" +import { createModal, getConnection } from "./connection" +import { getStarknetConnectedObject } from "./starknetObject" + +export async function getWebWalletStarknetObject( + wp: WebWalletProvider, +): Promise { + const globalWindow = typeof window !== "undefined" ? window : undefined + if (!globalWindow) { + throw new Error("window is not defined") + } + + console.log("in getWebWalletStarknetObject") + + console.log(wp) + + if (wp.url_account === undefined) return null + + const { iframe, modal } = await createModal(wp.url_account, false) + + const connection = await getConnection({ iframe, modal }) + + const starknetConnectedObject = getStarknetConnectedObject( + { + id: wp.id, + icon: wp.icon, + name: wp.name, + version: "online", + }, + connection, + ) + + return starknetConnectedObject +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 60e2ec9..0e79bcc 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -31,7 +31,8 @@ }, "dependencies": { "bowser": "^2.11.0", - "get-starknet-core": "workspace:^2.0.0" + "get-starknet-core": "workspace:^2.0.0", + "penpal": "^6.2.2" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^1.0.1", diff --git a/packages/ui/src/main.ts b/packages/ui/src/main.ts index ce5bc49..d57401a 100644 --- a/packages/ui/src/main.ts +++ b/packages/ui/src/main.ts @@ -1,4 +1,4 @@ -import show, { type WalletProviderWithStoreVersion } from "./modal" +import show, { type ExtensionWalletProviderWithStoreVersion } from "./modal" import Bowser from "bowser" import sn, { type DisconnectOptions, @@ -39,11 +39,12 @@ export interface ConnectOptions extends GetWalletOptions { storeVersion?: StoreVersion } -const enableWithVersion = async (wallet: StarknetWindowObject | null) => { +const connectWithVersion = async (wallet: StarknetWindowObject | null) => { if (!wallet) { return null } - return sn.enable(wallet, { starknetVersion: "v4" }).catch(() => null) + console.log("connectWithVersion") + return sn.connect(wallet, { starknetVersion: "v4" }).catch(() => null) } export const connect = async ({ @@ -64,10 +65,10 @@ export const connect = async ({ // return `wallet` even if it's null/undefined since we aren't allowed // to show any "connect" related UI - return enableWithVersion(wallet) + return connectWithVersion(wallet) } - const installedWallets = await sn.getAvailableWallets(restOptions) + const injectedWallets = await sn.getInjectedWallets(restOptions) if ( modalMode === "canAsk" && // we return/display wallet options once per first-dapp (ever) connect @@ -75,30 +76,33 @@ export const connect = async ({ ) { const wallet = preAuthorizedWallets.find((w) => w.id === lastWallet?.id) ?? - installedWallets.length === 1 - ? installedWallets[0] + injectedWallets.length === 1 + ? injectedWallets[0] : undefined if (wallet) { - return enableWithVersion(wallet) + return connectWithVersion(wallet) } // otherwise fallback to modal } - const discoveryWallets = await sn.getDiscoveryWallets(restOptions) - - const discoveryWalletsByStoreVersion: WalletProviderWithStoreVersion[] = - discoveryWallets + const extensionWallets = await sn.getExtensionWallets(restOptions) + const extensionWalletsByStoreVersion: ExtensionWalletProviderWithStoreVersion[] = + extensionWallets .filter((w) => Boolean(w.downloads[storeVersion])) .map(({ downloads, ...w }) => ({ ...w, download: downloads[storeVersion], })) + const webWallets = await sn.getWebWallets(restOptions) + return show({ lastWallet, preAuthorizedWallets, - installedWallets, - discoveryWallets: discoveryWalletsByStoreVersion, - enable: enableWithVersion, + injectedWallets, + extensionWallets: extensionWalletsByStoreVersion, + webWallets: webWallets, + openWebWallet: sn.openWebWallet, + connect: connectWithVersion, modalOptions: { theme: modalTheme, }, diff --git a/packages/ui/src/modal/Modal.svelte b/packages/ui/src/modal/Modal.svelte index ed18584..6228556 100644 --- a/packages/ui/src/modal/Modal.svelte +++ b/packages/ui/src/modal/Modal.svelte @@ -1,13 +1,23 @@ @@ -70,7 +106,7 @@ on:click={(e) => e.stopPropagation()} on:keyup={(e) => e.stopPropagation()}>
-

Connect a wallet

+

Connect a wallet

+ {#if webWallets.length != 0} + +
    + {#each webWallets as wallet, i} +
  • { + webWalletSelected[i] = true + webWalletLoading[i] = true + }}> + + {#if webWalletSelected[i]} + {#if webWalletLoading[i]} + +
    +
  • +
    + + Loading... +
    +
  • + + {/if} +