-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30 from Darlington02/develop
feat: connector base
- Loading branch information
Showing
10 changed files
with
2,148 additions
and
465 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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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` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.