diff --git a/.changeset/tough-boats-fry.md b/.changeset/tough-boats-fry.md new file mode 100644 index 0000000..783736e --- /dev/null +++ b/.changeset/tough-boats-fry.md @@ -0,0 +1,7 @@ +--- +'@fractl-ui/evm': patch +'@fractl-ui/starknet': patch +'fractl-ui': patch +--- + +i will find this text and edit it later diff --git a/.gitignore b/.gitignore index c0820e2..0e9cbc5 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ dist-ssr *.sln *.sw? .vercel +workbench/src/fractl diff --git a/.scripts/mount.sh b/.scripts/mount.sh new file mode 100755 index 0000000..8825080 --- /dev/null +++ b/.scripts/mount.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +echo "mount ./packages/ui/src to ./workbench/src/fractl" +echo "$(pwd) should be the root of the repo" + +sudo mount --bind ./packages/ui/src ./workbench/src/fractl diff --git a/package.json b/package.json index 2ae1ce1..b9ca5f6 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "prepare": "husky install", "build": "pnpm -F='./packages/*' --parallel build", "check": "pnpm -r --no-bail --parallel check", + "dev": "pnpm -F wb dev", "dev:site": "pnpm -F site dev", "dev:pkg": "pnpm -F='./packages/*' --parallel dev", "build:site": "pnpm -F site build", @@ -37,4 +38,4 @@ "tslib": "^2.6.3", "typescript": "^5.5.4" } -} \ No newline at end of file +} diff --git a/packages/evm/package.json b/packages/evm/package.json index ef15d88..261cae1 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -41,7 +41,8 @@ }, "devDependencies": { "@fractl-ui/types": "workspace:^", - "vite-plugin-dts": "^3.7.2", - "vite": "^5.3.5" + "@usecapsule/web-sdk": "^1.23.0", + "vite": "^5.3.5", + "vite-plugin-dts": "^3.7.2" } -} \ No newline at end of file +} diff --git a/packages/evm/src/addConnection.ts b/packages/evm/src/addConnection.ts index 33e184e..e4f5426 100644 --- a/packages/evm/src/addConnection.ts +++ b/packages/evm/src/addConnection.ts @@ -1,6 +1,6 @@ import type { AccountData, AccountDataResponse, Config } from '@fractl-ui/types' import { - connect, + connect as wagmiConnect, reconnect, disconnect, getAccount, @@ -14,30 +14,19 @@ import { } from '@wagmi/core' import { formatUnits } from 'viem/utils' import { map } from 'nanostores' +import { getUri } from './connect.js' type WagmiConfig = Config /** * Provides connection details to fractl-modal passed into it's config parameter */ -export const addEvmConnection = async ( - config: wagmiConfig, - { resolver } = { resolver: 'ENS' } -): Promise => { +export const eip155 = (config: wagmiConfig): WagmiConfig => { const state = map({ current: config.state.connections.get(config.state.current), status: config.state.status }) - const accountData = map({ - account: null, - balance: null, - nameService: { - name: null, - avatar: null - } - }) - config.subscribe( (state) => state.current, (current) => state.setKey('current', config.state.connections.get(current)) @@ -45,75 +34,79 @@ export const addEvmConnection = async ( config.subscribe( (state) => state.status, (status) => { - updateAccount(status) state.setKey('status', status) } ) - async function updateAccount( - status: 'connected' | 'connecting' | 'disconnected' | 'reconnecting' - ) { - if (status !== 'connected') { - accountData.setKey('account', null) - accountData.setKey('balance', null) - accountData.setKey('nameService', { name: undefined, avatar: undefined }) - return - } - - const account = getAccount(config) - accountData.setKey('account', account) - /* Not sure if this is possible */ - if (!account.isConnected) throw Error('account is not connected') - const address = account.address! - - try { - const _balance = await getBalance(config, { address }) - const balance: AccountDataResponse['balance'] = { - symbol: _balance.symbol, - value: formatUnits(_balance.value, _balance.decimals) + type WatchAccountProps = { + address: `0x${string}` + resolver?: string + } + const watchAccount = ({ address, resolver }: WatchAccountProps) => { + resolver = resolver ?? 'ENS' + const account = map({ + address, + balance: null, + nameService: { + name: null, + avatar: null } - accountData.setKey('balance', balance) - - let name: GetEnsNameReturnType, avatar: GetEnsAvatarReturnType + }) + ;(async function () { + try { + const _balance = await getBalance(config, { address }) + const balance: AccountDataResponse['balance'] = { + symbol: _balance.symbol, + value: formatUnits(_balance.value, _balance.decimals) + } + account.setKey('balance', balance) - switch (resolver) { - default: - name = await getEnsName(config, { - address, - blockTag: 'finalized' - }) - - avatar = - name && - (await getEnsAvatar(config, { - name, + let name: GetEnsNameReturnType, avatar: GetEnsAvatarReturnType + switch (resolver) { + default: + name = await getEnsName(config, { + address, blockTag: 'finalized' - })) + }) + + avatar = + name && + (await getEnsAvatar(config, { + name, + blockTag: 'finalized' + })) + } + account.setKey('nameService', { name, avatar }) + console.debug(account.get()) + } catch (error) { + console.error('fetch error', error, account.get()) } + })() + return account + } - // accountData.setKey('account', account) - accountData.setKey('nameService', { name, avatar }) - console.log(accountData.get()) - } catch (error) { - console.log(accountData.get()) - console.error('fetch error', error) - } + const connect = (connector: Connector) => async () => { + const { accounts, chainId } = await wagmiConnect(config, { connector }) + return { accounts, chainId } } return { + namespace: 'eip155', state: { subscribe: state.subscribe }, - accountData: { subscribe: accountData.subscribe }, get connectors() { - return config.connectors - }, - connect: async (connector: Connector) => { - const { accounts, chainId } = await connect(config, { connector }) - accountData.setKey('account', { - address: accounts?.[0], - addresses: accounts + const conn = config.connectors.map((c) => { + c.fractl = { getUri: {}, connect: {} } + + if (c.id === 'walletConnect') + c.fractl.getUri = async (callback: (uri: string) => void) => + await getUri(c, callback) + + c.fractl.connect = connect(c) + return c }) - return { accounts, chainId } + return conn }, + watchAccount, reconnect: async (connectors: Connector[]) => await reconnect(config, { connectors }), disconnect: async (connector?: Connector) => diff --git a/packages/evm/src/connect.ts b/packages/evm/src/connect.ts new file mode 100644 index 0000000..062dd98 --- /dev/null +++ b/packages/evm/src/connect.ts @@ -0,0 +1,12 @@ +import {EthereumProvider} from '@walletconnect/ethereum-provider' +import type { Connector } from '@wagmi/core' +type Provider = Awaited> + +export const getUri = async (connector: Connector, callback: (uri: string)=>void) => { + const provider = await connector.getProvider() as Provider + if (!provider) return + console.log(provider) + provider.on('display_uri', callback) + provider.connect() +} + diff --git a/packages/evm/src/index.ts b/packages/evm/src/index.ts index 81d998f..20b8219 100644 --- a/packages/evm/src/index.ts +++ b/packages/evm/src/index.ts @@ -3,4 +3,4 @@ export { mock } from './connectors/mock.js' /* Could just delete this folder with new project structure... */ export { walletConnect } from './connectors/walletConnect.js' -export { addEvmConnection } from './addConnection.js' +export { eip155 } from './addConnection.js' diff --git a/packages/evm/vite.config.ts b/packages/evm/vite.config.ts index ecba841..d21c6a5 100644 --- a/packages/evm/vite.config.ts +++ b/packages/evm/vite.config.ts @@ -18,11 +18,13 @@ export default defineConfig({ }, rollupOptions: { treeshake: true, - external: ['@walletconnect/ethereum-provider', '@wagmi/core', 'viem'], + external: ['@walletconnect/ethereum-provider', '@wagmi/core', 'viem', '@usecapsule/web-sdk'], output: { globals: { '@wagmi/core': 'wagmi', - '@walletconnect/ethereum-provider': 'walletconnect-ethereum-provider' + '@walletconnect/ethereum-provider': 'walletconnect-ethereum-provider', + '@usecapsule/web-sdk': 'usecapsule-web-sdk', + viem: 'viem' } } } diff --git a/packages/fuel/package.json b/packages/fuel/package.json index 1fed76b..9e779b2 100644 --- a/packages/fuel/package.json +++ b/packages/fuel/package.json @@ -29,14 +29,14 @@ }, "dependencies": { "@fuel-wallet/sdk": "^0.15.2", - "nanostores": "^0.9.5", - "fuels": "^0.74.0" + "fuels": "^0.74.0", + "nanostores": "^0.9.5" }, "devDependencies": { "@fractl-ui/types": "workspace:^", "@fuel-wallet/playwright-utils": "^0.15.2", - "playwright": "^1.41.2", - "vite-plugin-dts": "^3.7.2", - "vite": "^5.3.5" + "playwright": "^1.45.3", + "vite": "^5.3.5", + "vite-plugin-dts": "^3.9.1" } } \ No newline at end of file diff --git a/packages/fuel/src/addConnection.ts b/packages/fuel/src/addConnection.ts index a7e5670..729adc1 100644 --- a/packages/fuel/src/addConnection.ts +++ b/packages/fuel/src/addConnection.ts @@ -19,7 +19,7 @@ type FuelConnectionObj = Config /** * Provides connection details to fractl-modal passed into it's config parameter */ -export const addFuel = async ( +export const fuel = async ( { config, providerUrl, autoconnect }: FuelConnectionProps = { autoconnect: true, providerUrl: 'https://beta-5.fuel.network/graphql' @@ -114,6 +114,7 @@ export const addFuel = async ( } return { + namespace: 'fuel', state: { subscribe: state.subscribe }, accountData: { subscribe: accountData.subscribe }, get connectors() { diff --git a/packages/fuel/src/index.ts b/packages/fuel/src/index.ts index 9432b5a..b97a3e0 100644 --- a/packages/fuel/src/index.ts +++ b/packages/fuel/src/index.ts @@ -1 +1 @@ -export { addFuel } from './addConnection.js' +export { fuel } from './addConnection.js' diff --git a/packages/starknet/src/connections.ts b/packages/starknet/src/connections.ts index 4df623b..70a785e 100644 --- a/packages/starknet/src/connections.ts +++ b/packages/starknet/src/connections.ts @@ -30,16 +30,14 @@ type StarknetConnectProps = { autoconnect: boolean provider: RpcProvider - resolver: 'STARKNET_ID' clearLastWallet?: boolean | undefined } -export const addStarknetConnection = async ( +export const starknet = async ( { starknetVersion, connectors, autoconnect, provider, - resolver, clearLastWallet }: StarknetConnectProps = { starknetVersion: 'v5', @@ -48,8 +46,7 @@ export const addStarknetConnection = async ( provider: new RpcProvider({ nodeUrl: constants.RPC_MAINNET_NODES[0], retries: 2 - }), - resolver: 'STARKNET_ID' + }) } ): Promise> => { /* Check if user passed in any connectors. If empty get connectors from preauthorized wallets. */ @@ -98,98 +95,74 @@ export const addStarknetConnection = async ( } } - /* Manage Account Data */ - const accountData = map({ - account: null, - balance: null, - nameService: { - name: null, - avatar: null - } - }) satisfies MapStore - - state.subscribe( - ($s, changedKey) => - changedKey === 'status' && updateAccount($s.status, $s.current) - ) - - async function updateAccount( - status: 'connected' | 'connecting' | 'disconnected' | 'reconnecting', - current: ConnectedStarknetWindowObject | null - ) { - if (status !== 'connected' || !current) { - accountData.setKey('account', null) - accountData.setKey('balance', null) - accountData.setKey('nameService', { name: null, avatar: null }) - return - } - - const account = { - address: current.selectedAddress as `0x${string}`, - addresses: [current.selectedAddress] as `0x${string}`[] - } - accountData.setKey('account', account) - - try { - const eth = new Contract(ethABI, ETH_ADDR, defaultProvider) - const balance = await eth.balanceOf(account.address) - - accountData.setKey('balance', { - value: formatUnits(balance, DECIMALS), - symbol: 'ETH' - }) + type WatchAccountProps = { address: `0x${string}`; resolver?: string } + const watchAccount = ({ address, resolver }: WatchAccountProps) => { + resolver = resolver ?? 'STARKNET_ID' + const account = map({ + address, + balance: null, + nameService: { + name: null, + avatar: null + } + }) + ;(async function () { + try { + const eth = new Contract(ethABI, ETH_ADDR, defaultProvider) + const balance = await eth.balanceOf(address) - let name, avatar + account.setKey('balance', { + value: formatUnits(balance, DECIMALS), + symbol: 'ETH' + }) - switch (resolver) { - default: - name = await provider.getStarkName(account.address) + let name, avatar = null - /* name && - (await getEnsAvatar(config, { - name, - blockTag: 'finalized' - })) */ + switch (resolver) { + default: + name = await provider.getStarkName(address) + } + account.setKey('nameService', { name, avatar }) + console.debug(account.get()) + } catch (error) { + console.error('fetch error', error, account.get()) } - - // accountData.setKey('account', account) - accountData.setKey('nameService', { name, avatar }) - - // console.log(accountData.get()) - } catch (error) { - // console.log(accountData.get()) - console.error('fetch error', error) - } + })() + return account } - const connect = async (connector = connectors[0]) => { - if (connector === undefined) return - /* Connect and reconnect are effectively the same thing as this does not keep a history of existing connections */ - // if (connector?.isConnected) throw Error('already connected') - state.setKey('activeRequest', connector) - - return SN.enable(connector, { starknetVersion }) - .catch((e) => Error(e)) - .then((res) => res) - .finally(() => { + const connect = + (connector = connectors[0]) => + async () => { + if (connector === undefined) return + /* Connect and reconnect are effectively the same thing as this does not keep a history of existing connections */ + // if (connector?.isConnected) throw Error('already connected') + state.setKey('activeRequest', connector) + try { + await SN.enable(connector, { starknetVersion }) + } finally { state.setKey('activeRequest', null) setCurrent(connector) - }) - } + } + } return { + namespace: 'starknet', state: { subscribe: state.subscribe }, - accountData: { - subscribe: accountData.subscribe - }, get connectors() { - return connectors + const conn = connectors.map((c) => { + c.fractl = { connect: {} } + c.fractl.connect = connect(c) + return c + }) + return conn }, - connect, - reconnect: async (connectors) => - await connectors.forEach(async (c) => await connect(c)), + // connect, + // reconnect: async (connectors) => + // connectors.forEach(async (c) => await connect(c)), + watchAccount, disconnect: async () => { await SN.disconnect({ clearLastWallet }) state.setKey('current', null) diff --git a/packages/starknet/src/index.ts b/packages/starknet/src/index.ts index 9e4dce7..190382a 100644 --- a/packages/starknet/src/index.ts +++ b/packages/starknet/src/index.ts @@ -1 +1 @@ -export { addStarknetConnection } from './connections.js' \ No newline at end of file +export { starknet } from './connections.js' diff --git a/packages/types/src/types.d.ts b/packages/types/src/types.d.ts index 578f917..fd9c620 100644 --- a/packages/types/src/types.d.ts +++ b/packages/types/src/types.d.ts @@ -5,7 +5,7 @@ type Store = { } export type AccountDataError = { - account: null + address: string balance: null nameService: { name: null @@ -14,10 +14,7 @@ export type AccountDataError = { } export type AccountDataResponse = { - account: { - address: string - addresses: string[] - } + address: string balance: { value: string symbol: string @@ -40,6 +37,10 @@ export type Connector = { name: string type?: string icon?: string + fractl: { + getUri?: () => void + connect: () => Promise + } } & T export type StateConnected = { @@ -67,15 +68,21 @@ export type ConfigConnected = { } export type Config = { + /** + CAIP-2 Blockchain ID for the supported blockchain type + example namespaces: eip155, cosmos, starknet, bip155, solana + https://chainagnostic.org/CAIPs/caip-2 + */ + namespace: string connectors: readonly C[] - connect: (connector: C) => Promise /* fix later */ + // connect: (connector: C) => Promise /* fix later */ /** * Checks if a connector in the list is already connected * then sets the first one found as the current Connector * @param {C[]} connectors list of Connectors for the given library * @returns */ - reconnect: (connectors: C[]) => Promise + // reconnect: (connectors: C[]) => Promise disconnect: (connector?: C, opts?: object) => Promise } & (ConfigDisconnected | ConfigConnected) diff --git a/packages/ui/package.json b/packages/ui/package.json index aef4c87..80b1473 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -45,17 +45,17 @@ "check": "svelte-check --tsconfig ./tsconfig.json" }, "devDependencies": { + "@castlenine/svelte-qrcode": "^2.2.0", "@fractl-ui/types": "workspace:^", "@melt-ui/svelte": "^0.67.0", "@sveltejs/package": "^2.3.2", "@sveltejs/vite-plugin-svelte": "^3.0.1", "@tsconfig/svelte": "^5.0.2", "@wagmi/core": "2.0.1", - "@castlenine/svelte-qrcode": "^2.2.0", "postcss": "^8.4.40", "postcss-preset-env": "^9.3.0", "sass": "^1.77.8", - "svelte": "^4.2.9", + "svelte": "5.0.0-next.210", "svelte-check": "^3.6.3", "svelte-preprocess": "^6.0.2", "tslib": "^2.6.2", diff --git a/packages/ui/src/api/index.ts b/packages/ui/src/api/index.ts new file mode 100644 index 0000000..b80f7c6 --- /dev/null +++ b/packages/ui/src/api/index.ts @@ -0,0 +1,21 @@ +import type { + AccountData, + ConfigConnected, + Connector +} from '@fractl-ui/types' +import { actions } from '../createFractl.svelte.js' + +type GetAccountProps = { + address: string + namespace: string +} +export const getAccount = (props: GetAccountProps) => { + const account = actions.get(props.namespace)?.getAccount() + console.log(account) + return account +} + +export const disconnect = async ({namespace, connector}: {namespace: string, connector: Connector}) => { + //from fractl: get namespace and call disconnect + return await actions.get(namespace)?.disconnect(connector) +} diff --git a/packages/ui/src/components/AccountModal.svelte b/packages/ui/src/components/AccountModal.svelte index f6f2fbd..e60006f 100644 --- a/packages/ui/src/components/AccountModal.svelte +++ b/packages/ui/src/components/AccountModal.svelte @@ -1,34 +1,38 @@ @@ -38,7 +42,7 @@ aria-haspopup="dialog" data-fractl-trigger class="address fcl_el {btnClass}" - on:click={open} + onclick={modal.open} > {#if avatar} @@ -49,7 +53,7 @@ {name || truncate(address)} - +
{#if avatar} @@ -58,7 +62,7 @@ {/if} {balance ? `${balance.substring(0, 7)} ${symbol}` : 0}{balance ? `${balance.substring(0, 7)} ${balance.symbol}` : 0}
- - + + - + + {/if} diff --git a/examples/svelte/src/app.css b/workbench/src/app.css similarity index 100% rename from examples/svelte/src/app.css rename to workbench/src/app.css diff --git a/workbench/src/assets/fractl.svg b/workbench/src/assets/fractl.svg new file mode 100644 index 0000000..30964d8 --- /dev/null +++ b/workbench/src/assets/fractl.svg @@ -0,0 +1,6 @@ + + Fractl UI + + + + \ No newline at end of file diff --git a/workbench/src/lib/Anime.svelte b/workbench/src/lib/Anime.svelte new file mode 100644 index 0000000..c73606f --- /dev/null +++ b/workbench/src/lib/Anime.svelte @@ -0,0 +1,67 @@ + + +
+ +
+ +{#if parent} +
+ Parent + {#each children as child} +
+ Child +
+ {/each} + +
+{/if} + + diff --git a/workbench/src/lib/FcScan.svelte b/workbench/src/lib/FcScan.svelte new file mode 100644 index 0000000..4eae2aa --- /dev/null +++ b/workbench/src/lib/FcScan.svelte @@ -0,0 +1,62 @@ + + +
+ {#if data} + + Click to open + {:else} + loading QRCode + {/if} + +
+ + diff --git a/workbench/src/lib/Modal.svelte b/workbench/src/lib/Modal.svelte new file mode 100644 index 0000000..fde350f --- /dev/null +++ b/workbench/src/lib/Modal.svelte @@ -0,0 +1,77 @@ + + + + Content here + + + + + + diff --git a/workbench/src/lib/wagmiConfig.ts b/workbench/src/lib/wagmiConfig.ts new file mode 100644 index 0000000..22e264a --- /dev/null +++ b/workbench/src/lib/wagmiConfig.ts @@ -0,0 +1,52 @@ +import { http, createConfig, createStorage } from '@wagmi/core' +import { mock, walletConnect } from '@wagmi/connectors' +import { mainnet, arbitrum } from '@wagmi/core/chains' +import { readable } from 'svelte/store' + +const storage = createStorage({ storage: localStorage }) + +export const config = createConfig({ + chains: [mainnet, arbitrum], + storage, + transports: { + [mainnet.id]: http(), + [arbitrum.id]: http() + }, + connectors: [ + mock({ + accounts: ['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'] + }), + walletConnect({ + metadata: { + name: 'Fractl', + url: 'https://fractl.click', + verifyUrl: 'https://fractl.click', + icons: ['https://fractl.click/assets/fractl-I45eptSj.svg'], + description: 'dapp UI Library' + }, + projectId: '3baa16893e7c0a8e95029e58bed8768c', + showQrModal: false + }) + ] +}) + +export const mockConfig = createConfig({ + chains: [mainnet, arbitrum], + storage, + transports: { + [mainnet.id]: http(), + [arbitrum.id]: http() + }, + connectors: [ + mock({ + accounts: ['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'] + }) + ] +}) + +export default readable(config, (set) => { + config.subscribe( + (state) => state.status, + () => set(config) + ) +}) diff --git a/workbench/src/main.ts b/workbench/src/main.ts new file mode 100644 index 0000000..32b1546 --- /dev/null +++ b/workbench/src/main.ts @@ -0,0 +1,9 @@ +import './app.css' +import App from './App.svelte' +import { mount } from 'svelte' + +const app = mount(App,{ + target: document.getElementById('app') +}) + +export default app diff --git a/examples/svelte/src/testing.svelte b/workbench/src/testing.svelte similarity index 96% rename from examples/svelte/src/testing.svelte rename to workbench/src/testing.svelte index 4d65eed..35ad51c 100644 --- a/examples/svelte/src/testing.svelte +++ b/workbench/src/testing.svelte @@ -1,4 +1,3 @@ -