Skip to content

Commit

Permalink
Merge pull request #148 from argentlabs/feat/argent-one-button-connec…
Browse files Browse the repository at this point in the history
…tor-logic

Feat: Argent one button connector logic
  • Loading branch information
Cussone authored Oct 31, 2024
2 parents 8ed021a + 7a8a75e commit 8869caa
Show file tree
Hide file tree
Showing 43 changed files with 861 additions and 1,076 deletions.
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
branches:
- develop
- main
- beta
- hotfix\/v[0-9]+.[0-9]+.[0-9]+

jobs:
Expand Down
19 changes: 12 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,15 @@
"import": "./dist/argentMobile.js",
"require": "./dist/argentMobile.cjs"
},
"./argentCompound": {
"types": "./dist/argentCompound.d.ts",
"import": "./dist/argentCompound.js",
"require": "./dist/argentCompound.cjs"
"./argent": {
"types": "./dist/argent.d.ts",
"import": "./dist/argent.js",
"require": "./dist/argent.cjs"
},
"./argentX": {
"types": "./dist/argentX.d.ts",
"import": "./dist/argentX.js",
"require": "./dist/argentX.cjs"
},
"./braavos": {
"types": "./dist/braavos.d.ts",
Expand All @@ -61,14 +66,14 @@
"main": "./dist/starknetkit.cjs",
"module": "./dist/starknetkit.js",
"types": "./dist/starknetkit.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "vite build",
"check": "svelte-check --tsconfig ./tsconfig.json",
"dev": "vite build --watch"
},
"files": [
"dist"
],
"dependencies": {
"@argent/x-ui": "^1.70.1",
"@starknet-io/get-starknet": "^4.0.0",
Expand Down
27 changes: 21 additions & 6 deletions src/connectors/argent/argentMobile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ import {
type ConnectorData,
type ConnectorIcons,
} from "../../connector"
import { InjectedConnector, InjectedConnectorOptions } from "../../injected"
import { InjectedConnectorOptions } from "../../injected"
import { DEFAULT_ARGENT_MOBILE_ICON, DEFAULT_PROJECT_ID } from "./constants"
import { isInArgentMobileAppBrowser } from "../helpers"
import type { StarknetAdapter } from "./modal/starknet/adapter"
import { ArgentX } from "../../injected/argentX"
import { getModalWallet } from "../../../helpers/mapModalWallets"

export interface ArgentMobileConnectorOptions {
dappName: string
Expand All @@ -41,7 +42,7 @@ export interface ArgentMobileConnectorOptions {
url: string
icons?: string[]
rpcUrl?: string
isCompoundConnector?: boolean
onlyQR?: boolean
}

export class ArgentMobileBaseConnector extends Connector {
Expand Down Expand Up @@ -78,7 +79,7 @@ export class ArgentMobileBaseConnector extends Connector {
}

get name(): string {
return this._options.isCompoundConnector ? "Argent" : "Argent (mobile)" // TODO ditch isCompoundConnector
return "Argent (mobile)"
}

get icon(): ConnectorIcons {
Expand All @@ -95,8 +96,14 @@ export class ArgentMobileBaseConnector extends Connector {
return this._wallet
}

async connect(): Promise<ConnectorData> {
await this.ensureWallet()
async connect(
props:
| {
onlyQRCode?: boolean
}
| undefined,
): Promise<ConnectorData> {
await this.ensureWallet({ onlyQRCode: props?.onlyQRCode })

if (!this._wallet) {
throw new ConnectorNotFoundError()
Expand Down Expand Up @@ -188,7 +195,13 @@ export class ArgentMobileBaseConnector extends Connector {
this._wallet = null
}

private async ensureWallet(): Promise<void> {
private async ensureWallet(
props:
| {
onlyQRCode?: boolean
}
| undefined,
): Promise<void> {
const { getStarknetWindowObject } = await import("./modal")
const { chainId, projectId, dappName, description, url, icons, rpcUrl } =
this._options
Expand All @@ -201,13 +214,15 @@ export class ArgentMobileBaseConnector extends Connector {
: publicRPCNode.testnet)

const options = {
onlyQRCode: props?.onlyQRCode,
chainId: chainId ?? constants.NetworkName.SN_MAIN,
name: dappName,
projectId: projectId ?? DEFAULT_PROJECT_ID,
description,
url,
icons,
rpcUrl: providerRpcUrl,
modalWallet: getModalWallet(this),
}

if (projectId === DEFAULT_PROJECT_ID) {
Expand Down
121 changes: 108 additions & 13 deletions src/connectors/argent/argentMobile/modal/argentModal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { getDevice } from "./getDevice"
import Modal from "../../../../modal/Modal.svelte"
import { Layout, ModalWallet } from "../../../../types/modal"
import { getModalTarget } from "../../../../helpers/modal"
import { StarknetkitConnector } from "../../../connector"

const device = getDevice()

Expand All @@ -7,6 +11,7 @@ export interface RequestArguments {
params?: unknown[] | object
}

// TODO - SK-47 - remove this
const overlayStyle = {
position: "fixed",
top: "0",
Expand All @@ -25,6 +30,7 @@ const overlayStyle = {
fontFamily: "'Barlow', sans-serif",
}

// TODO - SK-47 - remove this
const iframeStyle = {
width: "840px",
height: "540px",
Expand All @@ -40,6 +46,17 @@ const iframeStyle = {
transform: "translate(-50%,-50%)",
}

const iframeStyleOnlyQR = {
width: "245px",
height: "245px",
borderRadius: "40px",
zIndex: "99999",
backgroundColor: "white",
border: "none",
outline: "none",
}

// TODO - SK-47 - remove this
const overlayHtml = `
<div id="argent-mobile-modal-container" style="position: relative">
<iframe class="argent-iframe" allow="clipboard-write"></iframe>
Expand All @@ -52,12 +69,20 @@ const overlayHtml = `
</div>
`

const overlayHtmlOnlyQR = `
<div id="argent-mobile-modal-container" style="position: relative; display: flex; justify-content: center; align-items: center">
<iframe class="argent-iframe" allow="clipboard-write"></iframe>
</div>
`

interface Urls {
readonly desktop: string
readonly ios: string
readonly android: string
}

type ModalWalletExtended = ModalWallet & { dappName: string }

class ArgentModal {
public bridgeUrl = "https://login.argent.xyz"
public mobileUrl = "argent://"
Expand All @@ -67,21 +92,66 @@ class ArgentModal {
private overlay?: HTMLDivElement
private popupWindow?: Window
private closingTimeout?: NodeJS.Timeout
private standaloneConnectorModal?: Modal

public showWalletConnectModal(
wcUri: string,
modalWallet: ModalWalletExtended,
) {
const wcParam = encodeURIComponent(wcUri)
const href = encodeURIComponent(window.location.href)

this.showModal(
{
desktop: `${this.bridgeUrl}?wc=${wcParam}&href=${href}&device=desktop&onlyQR=true`,
ios: `${this.mobileUrl}app/wc?uri=${wcParam}&href=${href}&device=mobile`,
android: `${this.mobileUrl}app/wc?uri=${wcParam}&href=${href}&device=mobile`,
},
modalWallet,
)
}

public showWalletConnectModal(wcUri: string) {
public getWalletConnectQR(wcUri: string) {
const wcParam = encodeURIComponent(wcUri)
const href = encodeURIComponent(window.location.href)

this.showModal({
desktop: `${this.bridgeUrl}?wc=${wcParam}&href=${href}&device=desktop`,
this.getQR({
desktop: `${this.bridgeUrl}?wc=${wcParam}&href=${href}&device=desktop&onlyQR=true`,
ios: `${this.mobileUrl}app/wc?uri=${wcParam}&href=${href}&device=mobile`,
android: `${this.mobileUrl}app/wc?uri=${wcParam}&href=${href}&device=mobile`,
})
}

private getQR(urls: Urls) {
const overlay = document.createElement("div")
const shadow = document.querySelector("#starknetkit-modal-container")

if (shadow?.shadowRoot) {
const slot = shadow.shadowRoot.querySelector(".qr-code-slot")

if (slot) {
slot.innerHTML = overlayHtmlOnlyQR
document.body.appendChild(overlay)
this.overlay = overlay

const iframe = slot.querySelector("iframe") as HTMLIFrameElement
iframe.setAttribute("src", urls.desktop)

for (const [key, value] of Object.entries(iframeStyleOnlyQR)) {
iframe.style[key as any] = value
}
} else {
throw new Error("Cannot find QR code slot")
}
} else {
throw new Error("Cannot find modal")
}
}

// TODO - SK-47 - handle this
public showApprovalModal(_: RequestArguments): void {
if (device === "desktop") {
this.showModal({
this.showModalOld({
desktop: `${this.bridgeUrl}?action=sign`,
ios: "",
android: "",
Expand All @@ -95,26 +165,50 @@ class ArgentModal {
Additionally when there is a signing request triggered by the dapp it will hit the deep link with an incomplete URI,
this should be ignored and not considered valid as it's only used for automatically redirecting the users to approve or reject a signing request.
*/
this.showModal({
this.showModalOld({
desktop: `${this.bridgeUrl}?action=sign&device=desktop&href=${href}`,
ios: `${this.mobileUrl}app/wc/request?href=${href}&device=mobile`,
android: `${this.mobileUrl}app/wc/request?href=${href}&device=mobile`,
})
}

public closeModal(success?: "animateSuccess") {
// TODO - SK-47 - remove this
public closeModal(success?: boolean) {
const modal = this.standaloneConnectorModal
if (success) {
this.overlay
?.querySelector("iframe")
?.contentWindow?.postMessage("argent-login.success", "*")
this.popupWindow?.postMessage("argent-login.success", "*")
this.closingTimeout = setTimeout(this.close, 3400)
modal?.$set({ layout: Layout.success })
setTimeout(() => modal?.$destroy(), 3000)
} else {
this.close()
modal?.$set({ layout: Layout.failure })
}
}

private showModal(urls: Urls) {
private showModal(urls: Urls, modalWallet: ModalWalletExtended) {
this.standaloneConnectorModal = new Modal({
target: getModalTarget(),
props: {
layout: Layout.qrCode,
dappName: modalWallet.dappName,
showBackButton: false,
selectedWallet: modalWallet,
callback: async (wallet: ModalWallet | null) => {
try {
const connector = wallet?.connector as StarknetkitConnector

this.standaloneConnectorModal?.$destroy()
await connector?.connect()
} catch (err) {
this.standaloneConnectorModal?.$set({ layout: Layout.failure })
}
},
},
})

this.getQR(urls)
}

// TODO - SK-47 - remove this
private showModalOld(urls: Urls) {
clearTimeout(this.closingTimeout)
if (this.overlay || this.popupWindow) {
this.close()
Expand Down Expand Up @@ -161,6 +255,7 @@ class ArgentModal {
closeButton.addEventListener("click", () => this.closeModal())
}

// TODO - SK-47 - remove this
private close = () => {
this.overlay?.remove()
this.popupWindow?.close()
Expand Down
24 changes: 17 additions & 7 deletions src/connectors/argent/argentMobile/modal/login.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import SignClient from "@walletconnect/sign-client"
import type { SignClientTypes } from "@walletconnect/types"

import { RpcProvider, constants } from "starknet"

// Using NetworkName as a value.
const Network: typeof constants.NetworkName = constants.NetworkName

import type { NamespaceAdapter, NamespaceAdapterOptions } from "./adapter"
import { argentModal } from "./argentModal"
import { resetWalletConnect } from "../../../../helpers/resetWalletConnect"
import { ModalWallet } from "../../../../types/modal"

// Using NetworkName as a value.
const Network: typeof constants.NetworkName = constants.NetworkName

export interface IArgentLoginOptions {
projectId?: string
Expand All @@ -22,6 +21,8 @@ export interface IArgentLoginOptions {
mobileUrl?: string
modalType?: "overlay" | "window"
walletConnect?: SignClientTypes.Options
onlyQRCode?: boolean
modalWallet?: ModalWallet
}

export const login = async <TAdapter extends NamespaceAdapter>(
Expand All @@ -37,6 +38,8 @@ export const login = async <TAdapter extends NamespaceAdapter>(
url,
icons,
walletConnect,
onlyQRCode,
modalWallet,
}: IArgentLoginOptions,
Adapter: new (options: NamespaceAdapterOptions) => TAdapter,
): Promise<TAdapter | null> => {
Expand Down Expand Up @@ -101,13 +104,20 @@ export const login = async <TAdapter extends NamespaceAdapter>(

// Open QRCode modal if a URI was returned (i.e. we're not connecting an existing pairing).
if (uri) {
argentModal.showWalletConnectModal(uri)
if (onlyQRCode) {
argentModal.getWalletConnectQR(uri)
} else {
argentModal.showWalletConnectModal(uri, {
...modalWallet,
dappName: name || "",
} as ModalWallet & { dappName: string })
}
argentModal.wcUri = uri

// Await session approval from the wallet.
const session = await approval()
adapter.updateSession(session)
argentModal.closeModal("animateSuccess")
argentModal.closeModal(true)
}

return adapter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export class StarknetAdapter
const chainId = this.formatChainId(this.chainId)
argentModal.showApprovalModal(request)
const response = await this.client.request({ topic, chainId, request })
argentModal.closeModal("animateSuccess")
argentModal.closeModal(true)
return response
} catch (error) {
argentModal.closeModal()
Expand Down
Loading

0 comments on commit 8869caa

Please sign in to comment.