Skip to content

Commit

Permalink
Merge pull request #305 from omnisat/feat/sparrow-wallet-connect
Browse files Browse the repository at this point in the history
sparrow wallet connect preliminaries
  • Loading branch information
ufe-pr authored Jan 27, 2025
2 parents 916e628 + 7243341 commit a0bf5b5
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 124 deletions.
72 changes: 72 additions & 0 deletions packages/lasereyes-core/src/client/helpers/sparrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { NetworkType, SparrowWalletProvider } from '../..'

const pendingRequests: Record<string, Promise<string> | undefined> = {}

function waitForConsoleKey(key: string): Promise<string> {
if (pendingRequests[key]) {
console.warn(`Multiple requests for "${key}" detected`)
return pendingRequests[key]
}

const p = new Promise<string>((resolve) => {
const originalConsoleLog = console.log
console.log = (...args: any[]) => {
// originalConsoleLog.apply(console, args)

if (args.length > 0 && typeof args[0] === 'string') {
console.log = originalConsoleLog
pendingRequests[key] = undefined
resolve(args[0])
}
}

originalConsoleLog(
`Please log a value for "${key}" using \n console.log('<your-value>') \n to continue.`
)
})
pendingRequests[key] = p
return p
}

export class DefaultSparrowWalletProvider implements SparrowWalletProvider {
async requestAccounts(): Promise<[string, string]> {
const address = await waitForConsoleKey('address')
if (!address) throw new Error('No address provided')

const paymentAddress = await waitForConsoleKey('paymentAddress')
if (!paymentAddress) throw new Error('No payment address provided')

return [address, paymentAddress]
}

async signMessage(message: string): Promise<string> {
console.log(`sign this message in sparrow wallet:`)
console.log('')
console.log(`${message}`)
console.log('')
return await waitForConsoleKey('message to sign')
}

async signPsbt(psbtBase64: string): Promise<string> {
console.log(`sign this in sparrow wallet:`)
console.log('')
console.log(`${psbtBase64}`)
console.log('')
return await waitForConsoleKey('signed psbt hex')
}

async getPublicKey(): Promise<string> {
const publicKey = await waitForConsoleKey('publicKey')
if (!publicKey) throw new Error('No public key provided')
return publicKey
}

// TODO: Implement network switching between mainnet and testnet
async getNetwork(): Promise<NetworkType> {
return 'mainnet'
}

async switchNetwork(_: NetworkType): Promise<void> {
return
}
}
62 changes: 42 additions & 20 deletions packages/lasereyes-core/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MapStore, WritableAtom, subscribeKeys } from 'nanostores'
import { MapStore, WritableAtom, listenKeys } from 'nanostores'

import { Config, ContentType, NetworkType, ProviderType } from '../types'
import {
Expand Down Expand Up @@ -36,8 +36,12 @@ export class LaserEyesClient {
readonly $store: MapStore<LaserEyesStoreType>
readonly $network: WritableAtom<NetworkType>
readonly $providerMap: Partial<Record<ProviderType, WalletProvider>>
private disposed = false

dispose() {
this.disposed = true
this.$store.off()
this.$network.off()
Object.values(this.$providerMap).forEach((provider) => provider?.dispose())
}

Expand All @@ -48,6 +52,7 @@ export class LaserEyesClient {
},
readonly config?: Config
) {
console.log('LaserEyesClient constructor')
this.$store = stores.$store
this.$network = stores.$network
this.$providerMap = {
Expand All @@ -63,18 +68,27 @@ export class LaserEyesClient {
[XVERSE]: new XVerseProvider(stores, this, config),
[WIZZ]: new WizzProvider(stores, this, config),
}
}

initialize() {
this.$network.listen(this.watchNetworkChange.bind(this))

subscribeKeys(this.$store, ['isInitializing'], (v) =>
this.handleIsInitializingChanged(v.isInitializing)
)
listenKeys(this.$store, ['isInitializing'], (v, oldValue) => {
if (this.disposed) {
console.warn('Client disposed, ignoring isInitializing change')
return
}

if (v.isInitializing !== oldValue.isInitializing)
return this.handleIsInitializingChanged(v.isInitializing)
})

if (config && config.network) {
this.$network.set(config.network)
if (this.config && this.config.network) {
this.$network.set(this.config.network)
this.getNetwork().then((foundNetwork) => {
try {
if (config.network !== foundNetwork) {
this.switchNetwork(config.network)
if (this.config!.network !== foundNetwork) {
this.switchNetwork(this.config!.network)
}
} catch (e) {
this.disconnect()
Expand Down Expand Up @@ -107,6 +121,11 @@ export class LaserEyesClient {
}

async connect(defaultWallet: ProviderType) {
if (this.disposed) {
console.warn('Client disposed, ignoring connect')
return
}

this.$store.setKey('isConnecting', true)
try {
localStorage?.setItem(LOCAL_STORAGE_DEFAULT_WALLET, defaultWallet)
Expand All @@ -118,6 +137,7 @@ export class LaserEyesClient {
this.$store.setKey('connected', true)
this.$store.setKey('provider', defaultWallet)
} catch (error) {
console.error('Error during connect:', error)
this.$store.setKey('isConnecting', false)
this.disconnect()
throw error
Expand Down Expand Up @@ -150,14 +170,19 @@ export class LaserEyesClient {
}

disconnect() {
this.$store.setKey('connected', false)
this.$store.setKey('provider', undefined)
this.$store.setKey('address', '')
this.$store.setKey('paymentAddress', '')
this.$store.setKey('publicKey', '')
this.$store.setKey('paymentPublicKey', '')
this.$store.setKey('balance', undefined)
this.$store.setKey('accounts', [])
this.$store.set({
provider: undefined,
address: '',
paymentAddress: '',
publicKey: '',
paymentPublicKey: '',
balance: undefined,
accounts: [],
connected: false,
isConnecting: false,
isInitializing: false,
hasProvider: this.$store.get().hasProvider,
})
localStorage?.removeItem(LOCAL_STORAGE_DEFAULT_WALLET)
}

Expand Down Expand Up @@ -360,10 +385,7 @@ export class LaserEyesClient {
try {
return await this.$providerMap[
this.$store.get().provider!
]?.getInscriptions(
offset,
limit
)
]?.getInscriptions(offset, limit)
} catch (error) {
if (error instanceof Error) {
if (error.message.toLowerCase().includes('not implemented')) {
Expand Down
123 changes: 48 additions & 75 deletions packages/lasereyes-core/src/client/providers/sparrow.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,29 @@

import * as bitcoin from 'bitcoinjs-lib'
import { WalletProvider } from '.'
import { NetworkType, ProviderType } from '../../types'
import { SPARROW } from '../../constants/wallets'
import { listenKeys, MapStore } from 'nanostores'
import { createSendBtcPsbt, getBTCBalance, isMainnetNetwork } from '../../lib/helpers'
import {
createSendBtcPsbt,
getBTCBalance,
isMainnetNetwork,
} from '../../lib/helpers'
import { keysToPersist, PersistedKey } from '../utils'
import { persistentMap } from '@nanostores/persistent'
import { LaserEyesStoreType } from '../types'

let consoleOverridden = false;
function waitForConsoleKey(key: string): Promise<string> {
return new Promise((resolve) => {
if (consoleOverridden) {
console.warn(`Already waiting for console input for "${key}"!`);
return;
}

consoleOverridden = true;
const originalConsoleLog = console.log;

console.log = (...args: any[]) => {
originalConsoleLog.apply(console, args);

if (args.length > 0 && typeof args[0] === "string") {
console.log = originalConsoleLog;
consoleOverridden = false;
resolve(args[0]);
}
};

originalConsoleLog(`Please log a value for "${key}" using \n console.log('<your-value>') \n to continue.`);
});
}
import { LaserEyesStoreType, SparrowWalletProvider } from '../types'
import { DefaultSparrowWalletProvider } from '../helpers/sparrow'

const SPARROW_WALLET_PERSISTENCE_KEY = 'SPARROW_CONNECTED_WALLET_STATE'

export default class SparrowProvider extends WalletProvider {
public get library(): SparrowWalletProvider | undefined {
return (window as any)?.SparrowWalletProvider
}

public get network(): NetworkType {
return this.$network.get()
}

observer?: MutationObserver
$valueStore: MapStore<Record<PersistedKey, string>> = persistentMap(
SPARROW_WALLET_PERSISTENCE_KEY,
Expand All @@ -55,6 +39,11 @@ export default class SparrowProvider extends WalletProvider {
initialize() {
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
this.observer = new window.MutationObserver(() => {
if (!this.library) {
// Create a new instance of the SparrowWalletProvider if it's not already available
;(window as any).SparrowWalletProvider =
new DefaultSparrowWalletProvider()
}
this.$store.setKey('hasProvider', {
...this.$store.get().hasProvider,
[SPARROW]: true,
Expand Down Expand Up @@ -86,7 +75,6 @@ export default class SparrowProvider extends WalletProvider {
})
}


removeSubscriber?: Function

watchStateChange(
Expand Down Expand Up @@ -117,41 +105,38 @@ export default class SparrowProvider extends WalletProvider {
this.observer?.disconnect()
}


async connect(_: ProviderType): Promise<void> {
try {
const { address: foundAddress, paymentAddress: foundPaymentAddress } = this.$valueStore!.get()
const { address: foundAddress, paymentAddress: foundPaymentAddress } =
this.$valueStore!.get()
if (foundAddress && foundPaymentAddress) {
if (foundAddress.startsWith('tb1') && isMainnetNetwork(this.network)) {
this.disconnect()
} else {
this.restorePersistedValues()
this.$store.setKey('provider', SPARROW)
this.$store.setKey('connected', true)
return
}
}

const address = await waitForConsoleKey("address");
if (!address) throw new Error("No address provided");

const paymentAddress = await waitForConsoleKey("paymentAddress");
if (!paymentAddress) throw new Error("No payment address provided");

const publicKey = await waitForConsoleKey("publicKey");
if (!publicKey) throw new Error("No public key provided");

const paymentPublicKey = await waitForConsoleKey("paymentPublicKey");
if (!paymentPublicKey) throw new Error("No payment public key provided");

this.$store.setKey("provider", SPARROW);
this.$store.setKey("accounts", [address, paymentAddress]);
this.$store.setKey("address", address);
this.$store.setKey("paymentAddress", paymentAddress);
this.$store.setKey("publicKey", publicKey);
this.$store.setKey("paymentPublicKey", paymentPublicKey);
this.$store.setKey("connected", true);
if (!this.library) throw new Error("Sparrow wallet isn't supported")
const accounts = await this.library.requestAccounts()
if (!accounts) throw new Error('No accounts found')
await this.getNetwork().then((network) => {
if (this.network !== network) {
this.switchNetwork(this.network)
}
})
const publicKey = await this.library.getPublicKey()
if (!publicKey) throw new Error('No public key found')
this.$store.setKey('accounts', accounts)
this.$store.setKey('address', accounts[0])
this.$store.setKey('paymentAddress', accounts[1])
this.$store.setKey('publicKey', publicKey)
this.$store.setKey('paymentPublicKey', publicKey)
} catch (error) {
this.disconnect();
console.error("Error during connect:", error);
this.disconnect()
console.error('Error during connect:', error)
}
}

Expand All @@ -170,23 +155,15 @@ export default class SparrowProvider extends WalletProvider {
7
)

console.log(`sign this send psbt in with sparrow wallet:`)
console.log('')
console.log(`${psbtBase64}`)
console.log('')
const signedAndFinalizedPsbt = await waitForConsoleKey("signedAndFinalizedPsbt")
if (!signedAndFinalizedPsbt) throw new Error('No signed PSBT provided');
const signedAndFinalizedPsbt = await this.library!.signPsbt(psbtBase64)
if (!signedAndFinalizedPsbt) throw new Error('No signed PSBT provided')
const txId = await this.pushPsbt(signedAndFinalizedPsbt)
if (!txId) throw new Error('send failed, no txid returned');
if (!txId) throw new Error('send failed, no txid returned')
return txId
}

async signMessage(message: string, _?: string | undefined): Promise<string> {
console.log(`sign this message in sparrow wallet:`)
console.log("")
console.log(`${message}`)
console.log("")
return await waitForConsoleKey("message to sign")
return await this.library!.signMessage(message)
}

async signPsbt(
Expand All @@ -197,18 +174,14 @@ export default class SparrowProvider extends WalletProvider {
broadcast?: boolean | undefined
): Promise<
| {
signedPsbtHex: string | undefined
signedPsbtBase64: string | undefined
txId?: string | undefined
}
signedPsbtHex: string | undefined
signedPsbtBase64: string | undefined
txId?: string | undefined
}
| undefined
> {
const preSigned = bitcoin.Psbt.fromBase64(psbtBase64)
console.log(`sign this in sparrow wallet:`)
console.log('')
console.log(`${psbtBase64}`)
console.log('')
const signedPsbt = await waitForConsoleKey("signed psbt hex")
const signedPsbt = await this.library!.signPsbt(psbtBase64)

if (finalize && broadcast) {
const txId = await this.pushPsbt(signedPsbt)
Expand All @@ -227,7 +200,7 @@ export default class SparrowProvider extends WalletProvider {
}

async getPublicKey() {
const publicKey = await waitForConsoleKey("publicKey")
const publicKey = await this.library!.getPublicKey()
this.$store.setKey('publicKey', publicKey)
return publicKey
}
Expand Down
Loading

0 comments on commit a0bf5b5

Please sign in to comment.