Skip to content

Commit

Permalink
feat(webwallet): support of web wallets
Browse files Browse the repository at this point in the history
  • Loading branch information
yogh333 committed Mar 21, 2023
1 parent 8f22f72 commit 6851f10
Show file tree
Hide file tree
Showing 41 changed files with 3,477 additions and 296 deletions.
Binary file added docs/get-starknet flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
82 changes: 77 additions & 5 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<StarknetWindowObject>()
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 (
<div className="App">
<h1>get-starknet</h1>
Expand Down Expand Up @@ -55,10 +121,16 @@ function App() {
Disconnect and reset
</button>
</div>
{walletName && (
{swo && (
<div>
<h2>
Selected Wallet: <pre>{walletName}</pre>
Selected Wallet:
<pre>{swo.name}</pre>
<pre>{swo.selectedAddress}</pre>
<button onClick={getERC20Balance}>Get ERC20 Balance</button>
{balance && <pre>{balance}</pre>}
<button onClick={transferToken}>Transfer Token</button>
{txhash && <pre>Tx hash: {txhash}</pre>}
</h2>
</div>
)}
Expand Down
7 changes: 5 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
22 changes: 19 additions & 3 deletions packages/core/src/discovery.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
},
]
79 changes: 51 additions & 28 deletions packages/core/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<string, any>
Expand All @@ -46,22 +52,29 @@ export interface GetWalletOptions {
export interface DisconnectOptions {
clearLastWallet?: boolean
}

interface GetStarknetResult {
getAvailableWallets: (
getInjectedWallets: (
options?: GetWalletOptions,
) => Promise<StarknetWindowObject[]> // Returns all wallets available in the window object
getPreAuthorizedWallets: (
getWebWallets: (options?: GetWalletOptions) => Promise<WebWalletProvider[]> // Return all available web wallets
getExtensionWallets: (
options?: GetWalletOptions,
) => Promise<StarknetWindowObject[]> // Returns only preauthorized wallets available in the window object
getDiscoveryWallets: (options?: GetWalletOptions) => Promise<WalletProvider[]> // Returns all wallets in existence (from discovery file)
getLastConnectedWallet: () => Promise<StarknetWindowObject | null | undefined> // Returns the last wallet connected when it's still connected
enable: (
) => Promise<ExtensionWalletProvider[]> // Return all available extension wallets
openWebWallet: (
wwp: WebWalletProvider,
) => Promise<StarknetWindowObject | null | undefined> //// Connect with a web wallet to get StarknetWindowOject
connect: (
wallet: StarknetWindowObject,
options?: {
starknetVersion?: "v3" | "v4"
},
) => Promise<ConnectedStarknetWindowObject> // Connects to a wallet
disconnect: (options?: DisconnectOptions) => Promise<void> // Disconnects from a wallet
getPreAuthorizedWallets: (
options?: GetWalletOptions,
) => Promise<StarknetWindowObject[]> // Returns only preauthorized wallets available in the window object
getLastConnectedWallet: () => Promise<StarknetWindowObject | null | undefined> // Returns the last wallet connected when it's still connected
}

export function getStarknet(
Expand All @@ -74,7 +87,7 @@ export function getStarknet(
const lastConnectedStore = storageFactoryImplementation("gsw-last")

return {
getAvailableWallets: async (options = {}) => {
getInjectedWallets: async (options = {}) => {
const availableWallets = scanObjectForWallets(
windowObject,
isWalletObject,
Expand All @@ -84,6 +97,35 @@ export function getStarknet(
(_) => sortBy(_, options.sort),
)(availableWallets)
},
getWebWallets: async (options = {}) => {
return pipe<WebWalletProvider[]>(
(_) => filterBy(_, options),
(_) => sortBy(_, options.sort),
)(wWallets)
},
getExtensionWallets: async (options = {}) => {
return pipe<ExtensionWalletProvider[]>(
(_) => 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,
Expand All @@ -95,12 +137,6 @@ export function getStarknet(
(_) => sortBy(_, options.sort),
)(availableWallets)
},
getDiscoveryWallets: async (options = {}) => {
return pipe<WalletProvider[]>(
(_) => filterBy(_, options),
(_) => sortBy(_, options.sort),
)(discovery)
},
getLastConnectedWallet: async () => {
const lastConnectedWalletId = lastConnectedStore.get()
const allWallets = scanObjectForWallets(windowObject, isWalletObject)
Expand All @@ -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()
}
},
}
}

Expand Down
61 changes: 61 additions & 0 deletions packages/core/src/webwallet/account.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
throw new Error("Method not implemented")
}

async signMessage(): Promise<Signature> {
throw new Error("Method not implemented")
}

async signTransaction(): Promise<Signature> {
throw new Error("Method not implemented")
}

async signDeclareTransaction(): Promise<Signature> {
throw new Error("Method not implemented")
}

async signDeployAccountTransaction(): Promise<Signature> {
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<ChildApi>,
) {
super(provider, address, new UnimplementedSigner())
}

execute(
calls: AllowArray<Call>,
abis?: Abi[] | undefined,
transactionsDetail?: InvocationsDetails | undefined,
): Promise<InvokeFunctionResponse> {
return this.child.execute(calls, abis, transactionsDetail)
}

signMessage(typedData: typedData.TypedData): Promise<Signature> {
return this.child.signMessage(typedData)
}
}
Loading

0 comments on commit 6851f10

Please sign in to comment.