Skip to content

Commit

Permalink
Merge pull request #30 from Darlington02/develop
Browse files Browse the repository at this point in the history
feat: connector base
  • Loading branch information
Darlington02 authored Feb 17, 2024
2 parents 4b49e5b + 9786a8f commit 17c4dc8
Show file tree
Hide file tree
Showing 10 changed files with 2,148 additions and 465 deletions.
2,162 changes: 1,713 additions & 449 deletions connector/package-lock.json

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion connector/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,27 @@
"files": [
"dist"
],
"types": "./dist/index.d.ts",
"main": "./dist/tokenbound-connector.umd.cjs",
"module": "./dist/tokenbound-connector.js",
"exports": {
".": {
"import": "./dist/tokenbound-connector.js",
"require": "./dist/tokenbound-connector.umd.cjs",
"types": "./dist/index.d.ts"
}
},
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"eventemitter3": "^5.0.1",
"get-starknet-core": "^3.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"starknet": "^5.24.3",
"starknetkit": "^1.1.3",
"web-vitals": "^2.1.4"
},
"scripts": {
Expand All @@ -41,7 +54,6 @@
"@types/testing-library__jest-dom": "^5.14.9",
"eslint": "^8.50.0",
"eslint-config-prettier": "^9.0.0",
"starknet": "^6.0.0",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-plugin-dts": "^3.5.1",
Expand Down
21 changes: 21 additions & 0 deletions connector/src/connector/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export class ConnectorNotConnectedError extends Error {
name = "ConnectorNotConnectedError";
message = "Connector not connected";
}

export class ConnectorNotFoundError extends Error {
name = "ConnectorNotFoundError";
message = "Connector not found";
}

export class UserRejectedRequestError extends Error {
name = "UserRejectedRequestError";
message = "User rejected request";
}

export class UserNotConnectedError extends Error {
name = "UserNotConnectedError";
message = "User not connected";
}

export const tokenbound_icon = `data:image/png;base64,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`
22 changes: 22 additions & 0 deletions connector/src/connector/helpers/RPCNodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type PublicRpcNode = {
mainnet: string
testnet: string
}

// Public RPC nodes
export const BLAST_RPC_NODE: PublicRpcNode = {
mainnet: "https://starknet-mainnet.public.blastapi.io",
testnet: "https://starknet-testnet.public.blastapi.io",
} as const

export const LAVA_RPC_NODE: PublicRpcNode = {
mainnet: "https://rpc.starknet.lava.build",
testnet: "https://rpc.starknet-testnet.lava.build",
} as const

export const PUBLIC_RPC_NODES = [BLAST_RPC_NODE, LAVA_RPC_NODE] as const

export function getRandomPublicRPCNode() {
const randomIndex = Math.floor(Math.random() * PUBLIC_RPC_NODES.length)
return PUBLIC_RPC_NODES[randomIndex]
}
11 changes: 11 additions & 0 deletions connector/src/connector/helpers/mapChainToNodeUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { getRandomPublicRPCNode } from "./RPCNodes"

export function mapChainToNodeUrl(chainId: string): string {
const PublicRpcNode = getRandomPublicRPCNode()
if(chainId == "SN_MAIN") {
return PublicRpcNode.mainnet
}
else {
return PublicRpcNode.testnet
}
}
221 changes: 219 additions & 2 deletions connector/src/connector/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,220 @@
// connector base, important links:
import { connect } from "starknetkit"
import { InjectedConnector } from "starknetkit/injected"
import { WebWalletConnector } from "starknetkit/webwallet"
import { ArgentMobileConnector } from "starknetkit/argentMobile"

// https://github.com/argentlabs/starknetkit/blob/develop/src/connectors/webwallet/index.ts
import type {
AccountChangeEventHandler,
StarknetWindowObject,
} from "get-starknet-core"

import type {
AccountInterface,
ProviderInterface,
} from "starknet"

import {
Connector,
type ConnectorIcons
} from "./types/connector"

import {
ConnectorNotConnectedError,
ConnectorNotFoundError,
UserNotConnectedError,
UserRejectedRequestError,
} from "./constants"

import { tokenbound_icon } from "./constants"
import { getTokenboundStarknetWindowObject } from "./windowObject/TBAStarknetWindowObject"

let _wallet: StarknetWindowObject | null = null

interface TokenboundConnectorOptions {
tokenboundAddress: string,
parentAccountId: string,
provider?: ProviderInterface
}

export class TokenboundConnector extends Connector {
private _wallet: StarknetWindowObject | null = null
private _options: TokenboundConnectorOptions

constructor(options: TokenboundConnectorOptions) {
super()
this._options = options
}

available(): boolean {
return true
}

async ready(): Promise<boolean> {
if(!_wallet) {
this._wallet = null
return false
}

this._wallet = _wallet
return this._wallet.isPreauthorized()
}

get id(): string {
this._wallet = _wallet
return this._wallet?.id || "TBA"
}

get name(): string {
this._wallet = _wallet
return this._wallet?.name || "Tokenbound Account"
}

get icon(): ConnectorIcons {
return {
light: tokenbound_icon,
dark: tokenbound_icon
}
}

get wallet(): StarknetWindowObject {
if(!this._wallet) {
throw new ConnectorNotConnectedError()
}
return this._wallet
}

get subtitle(): string {
return "Powered by Starknet Africa"
}

async connect(): Promise<StarknetWindowObject> {
await this.ensureWallet()

if(!this._wallet) {
throw new ConnectorNotFoundError()
}

try {
await this._wallet.enable({ starknetVersion: "v4" })
}
catch {
throw new UserRejectedRequestError()
}

if(!this._wallet.isConnected) {
throw new UserRejectedRequestError()
}

return this._wallet
}

async disconnect(): Promise<void> {
if(!this.available() && !this._wallet) {
throw new ConnectorNotFoundError()
}

if(!this._wallet?.isConnected) {
throw new UserNotConnectedError()
}

_wallet = null
this._wallet = _wallet
}

async account(): Promise<AccountInterface> {
this._wallet = _wallet

if(!this._wallet || !this._wallet.account) {
throw new ConnectorNotConnectedError()
}

return this._wallet.account as unknown as AccountInterface
}

async chainId(): Promise<bigint> {
if (!this._wallet || !this.wallet.account || !this._wallet.provider) {
throw new ConnectorNotConnectedError()
}

const chainIdHex = await this._wallet.provider.getChainId()
const chainId = BigInt(chainIdHex)
return chainId
}

async initEventListener(accountChangeCb: AccountChangeEventHandler) {
this._wallet = _wallet
if (!this._wallet) {
throw new ConnectorNotConnectedError()
}

this._wallet.on("accountsChanged", accountChangeCb)
}

async removeEventListener(accountChangeCb: AccountChangeEventHandler) {
this._wallet = _wallet
if (!this._wallet) {
throw new ConnectorNotConnectedError()
}

this._wallet.off("accountsChanged", accountChangeCb)

_wallet = null
this._wallet = null
}

private async connectParentAccount(id: string): Promise<AccountInterface> {
let parentWallet

if(id == "argentMobile") {
const { wallet } = await connect({
connectors: [
new ArgentMobileConnector()
]
})
parentWallet = wallet
}
else if(id == "argentWebWallet") {
const { wallet } = await connect({
connectors: [
new WebWalletConnector({
url: "https://web.argent.xyz"
})
]
})
parentWallet = wallet
}
else {
const { wallet } = await connect({
connectors: [
new InjectedConnector({
options: {id: id}
})
]
})
parentWallet = wallet
}

return parentWallet?.account as AccountInterface
}

private async ensureWallet(): Promise<void> {
const tokenboundAddress = this._options.tokenboundAddress
const parentAccount = await this.connectParentAccount(this._options.parentAccountId)
const provider = this._options.provider

const wallet = await getTokenboundStarknetWindowObject(
{
id: "TBA",
icon: tokenbound_icon as string,
name: "Tokenbound Account",
version: "1.0.0"
},
tokenboundAddress,
parentAccount,
provider
)

_wallet = wallet ?? null
this._wallet = _wallet
}
}
53 changes: 53 additions & 0 deletions connector/src/connector/types/connector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import EventEmitter from "eventemitter3"
import type { StarknetWindowObject } from "get-starknet-core"
import { AccountInterface } from "starknet"

/** Connector icons, as base64 encoded svg. */
export type ConnectorIcons = {
/** Dark-mode icon. */
dark?: string
/** Light-mode icon. */
light?: string
}

/** Connector data. */
export type ConnectorData = {
/** Connector account. */
account?: string
/** Connector network. */
chainId?: bigint
}

/** Connector events. */
export interface ConnectorEvents {
/** Emitted when account or network changes. */
change(data: ConnectorData): void
/** Emitted when connection is established. */
connect(data: ConnectorData): void
/** Emitted when connection is lost. */
disconnect(): void
}

export abstract class Connector extends EventEmitter<ConnectorEvents> {
/** Unique connector id. */
abstract get id(): string
/** Connector name. */
abstract get name(): string
/** Connector icons. */
abstract get icon(): ConnectorIcons

/** Whether connector is available for use */
abstract available(): boolean
/** Whether connector is already authorized */
abstract ready(): Promise<boolean>
/** Connect wallet. */
abstract connect(): Promise<StarknetWindowObject>
/** Disconnect wallet. */
abstract disconnect(): Promise<void>
/** Get current account. */
abstract account(): Promise<AccountInterface>
/** Get current chain id. */
abstract chainId(): Promise<bigint>
/** Connector StarknetWindowObject */
abstract get wallet(): StarknetWindowObject
}
Loading

0 comments on commit 17c4dc8

Please sign in to comment.