Skip to content

Commit

Permalink
Add program counter, fix types issue (#2145)
Browse files Browse the repository at this point in the history
Co-authored-by: Enes <[email protected]>
  • Loading branch information
gooxpf and enesozturk authored Apr 19, 2024
1 parent a713b01 commit 0599513
Show file tree
Hide file tree
Showing 19 changed files with 331 additions and 47 deletions.
14 changes: 13 additions & 1 deletion apps/laboratory/src/components/Solana/SolanaTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
import { SolanaSignTransactionTest } from './SolanaSignTransactionTest'
import { SolanaSendTransactionTest } from './SolanaSendTransactionTest'
import { SolanaSignMessageTest } from './SolanaSignMessageTest'
import { solana } from '../../utils/ChainsUtil'
import { SolanaWriteContractTest } from './SolanaWriteContractTest'
import { solana, solanaDevnet, solanaTestnet } from '../../utils/ChainsUtil'

export function SolanaTests() {
const { isConnected, currentChain } = useWeb3ModalAccount()
Expand Down Expand Up @@ -51,6 +52,17 @@ export function SolanaTests() {
</Heading>
<SolanaSendTransactionTest />
</Box>
{(currentChain?.chainId === solanaTestnet.chainId ||
currentChain?.chainId === solanaDevnet.chainId) && (
<Stack divider={<StackDivider />} spacing="4">
<Box>
<Heading size="xs" textTransform="uppercase" pb="2">
Counter Program Instruction
</Heading>
<SolanaWriteContractTest />
</Box>
</Stack>
)}
</Stack>
</CardBody>
</Card>
Expand Down
108 changes: 108 additions & 0 deletions apps/laboratory/src/components/Solana/SolanaWriteContractTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React, { useState } from 'react'
import { Button, useToast } from '@chakra-ui/react'
import {
SystemProgram,
PublicKey,
Keypair,
Transaction,
TransactionInstruction,
LAMPORTS_PER_SOL
} from '@solana/web3.js'
import { useWeb3ModalAccount, useWeb3ModalProvider } from '@web3modal/solana/react'

import { COUNTER_ACCOUNT_SIZE } from '../../utils/SolanaConstants'
import { deserializeCounterAccount, detectProgramId } from '../../utils/SolanaUtil'

export function SolanaWriteContractTest() {
const toast = useToast()
const { address, currentChain } = useWeb3ModalAccount()
const { walletProvider, connection } = useWeb3ModalProvider()
const [loading, setLoading] = useState(false)

async function onIncrementCounter() {
setLoading(true)

const PROGRAM_ID = new PublicKey(detectProgramId(currentChain?.chainId ?? ''))

try {
if (!walletProvider || !address) {
throw new Error('User is disconnected')
}

if (!connection) {
throw new Error('No connection set')
}

const counterKeypair = Keypair.generate()
const counter = counterKeypair.publicKey

const balance = await connection.getBalance(walletProvider.publicKey)
if (balance < LAMPORTS_PER_SOL / 100) {
throw Error('Not enough SOL in wallet')
}

const allocIx: TransactionInstruction = SystemProgram.createAccount({
fromPubkey: walletProvider.publicKey,
newAccountPubkey: counter,
lamports: await connection.getMinimumBalanceForRentExemption(COUNTER_ACCOUNT_SIZE),
space: COUNTER_ACCOUNT_SIZE,
programId: PROGRAM_ID
})

const incrementIx: TransactionInstruction = new TransactionInstruction({
programId: PROGRAM_ID,
keys: [
{
pubkey: counter,
isSigner: false,
isWritable: true
}
],
data: Buffer.from([0x0])
})

const tx = new Transaction().add(allocIx).add(incrementIx)

tx.feePayer = walletProvider.publicKey
tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash

await walletProvider.signAndSendTransaction(tx, [counterKeypair])

const counterAccountInfo = await connection.getAccountInfo(counter, {
commitment: 'confirmed'
})

if (!counterAccountInfo) {
throw new Error('Expected counter account to have been created')
}

const counterAccount = deserializeCounterAccount(counterAccountInfo?.data)

if (counterAccount.count !== 1) {
throw new Error('Expected count to have been 1')
}

toast({
title: 'Succcess',
description: `[alloc+increment] count is: ${counterAccount.count}`,
status: 'success',
isClosable: true
})
} catch (err) {
toast({
title: 'Transaction failed',
description: 'Failed to increment counter',
status: 'error',
isClosable: true
})
} finally {
setLoading(false)
}
}

return (
<Button isDisabled={loading} data-testid="sign-message-button" onClick={onIncrementCounter}>
Increment Counter With Sign
</Button>
)
}
18 changes: 18 additions & 0 deletions apps/laboratory/src/utils/SolanaConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { solana, solanaDevnet, solanaTestnet } from './ChainsUtil'

export const COUNTER_ACCOUNT_SIZE = 8

export const SolanaConstantsUtil = {
// You can add local net in chains if you're using local validator
chains: [solana, solanaTestnet, solanaDevnet],
programIds: [
{
chainId: solanaDevnet.chainId,
programId: 'Cb5aXEgXptKqHHWLifvXu5BeAuVLjojQ5ypq6CfQj1hy'
},
{
chainId: solanaTestnet.chainId,
programId: 'FZn4xQoKKvcxDADDRdqNAAPnVv9qYCbUTbP3y4Rn1BBr'
}
]
}
15 changes: 15 additions & 0 deletions apps/laboratory/src/utils/SolanaUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SolanaConstantsUtil } from './SolanaConstants'

export function deserializeCounterAccount(data?: Buffer): { count: number } {
if (data?.byteLength !== 8) {
throw Error('Need exactly 8 bytes to deserialize counter')
}

return {
count: Number(data[0])
}
}

export function detectProgramId(chainId: string): string {
return SolanaConstantsUtil.programIds.find(chain => chain.chainId === chainId)?.programId ?? ''
}
8 changes: 4 additions & 4 deletions apps/laboratory/tests/shared/utils/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,17 @@ const customProjectProperties: CustomProjectProperties = {
},
// Exclude email.spec.ts, siwe.spec.ts, and canary.spec.ts from solana, not yet implemented
'Desktop Chrome/solana': {
grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts|wallet\.spec\.ts)).*$/u
grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts)).*$/u
},
'Desktop Brave/solana': {
useOptions: braveOptions,
grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts|wallet\.spec\.ts)).*$/u
grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts)).*$/u
},
'Desktop Firefox/solana': {
grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts|wallet\.spec\.ts)).*$/u
grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts)).*$/u
},
'Desktop Safari/solana': {
grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts|wallet\.spec\.ts)).*$/u
grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts)).*$/u
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/solana/exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Web3Modal } from '../src/client.js'

import type { Web3ModalOptions } from '../src/client.js'

export type { Web3ModalOptions } from '../src/client.js'
export type { Web3Modal, Web3ModalOptions } from '../src/client.js'

export { defaultSolanaConfig } from '../src/utils/defaultConfig.js'

Expand Down
8 changes: 6 additions & 2 deletions packages/solana/exports/react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import { getWeb3Modal } from '@web3modal/scaffold-react'

import { Web3Modal } from '../src/client.js'

import { SolStoreUtil } from '../src/utils/scaffold/index.js'

import type { Web3ModalOptions } from '../src/client.js'
import type { Provider } from '../src/utils/scaffold/SolanaTypesUtil.js'
import { SolStoreUtil } from '../src/utils/scaffold/SolanaStoreUtil.js'
import type { Provider } from '../src/utils/scaffold/index.js'

// -- Setup -------------------------------------------------------------------
let modal: Web3Modal | undefined = undefined

// -- Types -------------------------------------------------------------------
export type { Web3Modal, Web3ModalOptions } from '../src/client.js'

export function createWeb3Modal(options: Web3ModalOptions) {
if (!modal) {
modal = new Web3Modal({
Expand Down
48 changes: 33 additions & 15 deletions packages/solana/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,12 @@ import {
} from '@web3modal/core'
import { ConstantsUtil, HelpersUtil, PresetsUtil } from '@web3modal/scaffold-utils'

import {
createWalletAdapters,
type AdapterKey,
syncInjectedWallets
} from './connectors/walletAdapters.js'
import { SolConstantsUtil } from './utils/scaffold/SolanaConstantsUtil.js'
import { SolHelpersUtil } from './utils/scaffold/SolanaHelpersUtils.js'
import { SolStoreUtil } from './utils/scaffold/SolanaStoreUtil.js'
import { createWalletAdapters, syncInjectedWallets } from './connectors/walletAdapters.js'
import { SolConstantsUtil, SolHelpersUtil, SolStoreUtil } from './utils/scaffold/index.js'
import { WalletConnectConnector } from './connectors/walletConnectConnector.js'

import type { BaseWalletAdapter } from '@solana/wallet-adapter-base'
import type { PublicKey } from '@solana/web3.js'
import type { PublicKey, Commitment, ConnectionConfig } from '@solana/web3.js'
import type UniversalProvider from '@walletconnect/universal-provider'
import type {
CaipNetworkId,
Expand All @@ -33,12 +27,13 @@ import type {
CaipNetwork
} from '@web3modal/scaffold'

import type { ProviderType, Chain, Provider } from './utils/scaffold/SolanaTypesUtil.js'
import type { SolStoreUtilState } from './utils/scaffold/SolanaStoreUtil.js'
import type { AdapterKey } from './connectors/walletAdapters.js'
import type { ProviderType, Chain, Provider, SolStoreUtilState } from './utils/scaffold/index.js'

export interface Web3ModalClientOptions extends Omit<LibraryOptions, 'defaultChain' | 'tokens'> {
solanaConfig: ProviderType
chains: Chain[]
connectionSettings?: Commitment | ConnectionConfig
defaultChain?: Chain
chainImages?: Record<number | string, string>
connectorImages?: Record<string, string>
Expand All @@ -56,8 +51,18 @@ export class Web3Modal extends Web3ModalScaffold {

private chains: Chain[]

public connectionSettings: Commitment | ConnectionConfig

public constructor(options: Web3ModalClientOptions) {
const { solanaConfig, chains, tokens, _sdkVersion, chainImages, ...w3mOptions } = options
const {
solanaConfig,
chains,
tokens,
_sdkVersion,
chainImages,
connectionSettings = 'confirmed',
...w3mOptions
} = options
const { metadata } = solanaConfig

if (!solanaConfig) {
Expand Down Expand Up @@ -156,6 +161,7 @@ export class Web3Modal extends Web3ModalScaffold {
} as ScaffoldOptions)

this.chains = chains
this.connectionSettings = connectionSettings
this.syncRequestedNetworks(chains, chainImages)

const chain = SolHelpersUtil.getChainFromCaip(
Expand All @@ -178,7 +184,7 @@ export class Web3Modal extends Web3ModalScaffold {
SolStoreUtil.setConnection(
new Connection(
SolHelpersUtil.detectRpcUrl(chain, OptionsController.state.projectId),
'recent'
this.connectionSettings
)
)

Expand Down Expand Up @@ -227,6 +233,18 @@ export class Web3Modal extends Web3ModalScaffold {
return address ? SolStoreUtil.state.address : address
}

public getWalletProvider() {
return SolStoreUtil.state.provider
}

public getWalletProviderType() {
return SolStoreUtil.state.providerType
}

public getWalletConnection() {
return SolStoreUtil.state.connection
}

public async checkActiveProviders() {
const walletId = localStorage.getItem(SolConstantsUtil.WALLET_ID)

Expand Down Expand Up @@ -340,7 +358,7 @@ export class Web3Modal extends Web3ModalScaffold {
SolStoreUtil.setConnection(
new Connection(
SolHelpersUtil.detectRpcUrl(chain, OptionsController.state.projectId),
'recent'
this.connectionSettings
)
)
this.setAddress(this.walletAdapters[wallet].publicKey?.toString())
Expand All @@ -355,7 +373,7 @@ export class Web3Modal extends Web3ModalScaffold {
SolStoreUtil.setConnection(
new Connection(
SolHelpersUtil.detectRpcUrl(chain, OptionsController.state.projectId),
'recent'
this.connectionSettings
)
)
universalProvider.connect({ namespaces, pairingTopic: undefined })
Expand Down
12 changes: 9 additions & 3 deletions packages/solana/src/connectors/baseConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import borsh from 'borsh'
import { Buffer } from 'buffer'

import { registerListener, unregisterListener } from '../utils/clusterFactory.js'
import { SolConstantsUtil } from '../utils/scaffold/SolanaConstantsUtil.js'
import { SolStoreUtil } from '../utils/scaffold/SolanaStoreUtil.js'
import { SolConstantsUtil, SolStoreUtil } from '../utils/scaffold/index.js'
import { getHashedName, getNameAccountKey } from '../utils/hash.js'
import { NameRegistry } from '../utils/nameService.js'

import type { ConfirmOptions, Signer, TransactionSignature } from '@solana/web3.js'

import type {
BlockResult,
AccountInfo,
Expand All @@ -26,7 +27,7 @@ import type {
RequestMethods,
TransactionArgs,
TransactionType
} from '../utils/scaffold/SolanaTypesUtil.js'
} from '../utils/scaffold/index.js'

export interface Connector {
id: string
Expand All @@ -40,6 +41,11 @@ export interface Connector {
transaction: Transaction | VersionedTransaction
) => Promise<{ signatures: { signature: string }[] }>
sendTransaction: (transaction: Transaction | VersionedTransaction) => Promise<string>
signAndSendTransaction: (
transaction: Transaction | VersionedTransaction,
signers: Signer[],
confirmOptions?: ConfirmOptions
) => Promise<TransactionSignature>
getAccount: (
requestedAddress?: string,
encoding?: 'base58' | 'base64' | 'jsonParsed'
Expand Down
7 changes: 4 additions & 3 deletions packages/solana/src/connectors/universalProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ export class UniversalProviderFactory {
public static async getProvider() {
if (!UniversalProviderFactory.provider) {
await UniversalProviderFactory.init()
}
if (!UniversalProviderFactory.provider) {
throw new Error('Failed to initialize universal provider')

if (!UniversalProviderFactory.provider) {
throw new Error('Failed to initialize universal provider')
}
}

return UniversalProviderFactory.provider
Expand Down
3 changes: 2 additions & 1 deletion packages/solana/src/connectors/walletAdapters.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom'
import { SolflareWalletAdapter } from '@solana/wallet-adapter-solflare'
import { TrustWalletAdapter } from '@solana/wallet-adapter-trust'
import { BackpackWalletAdapter } from '@solana/wallet-adapter-backpack'

import { PhantomWalletAdapter } from './walletAdapters/index.js'

import type { BaseWalletAdapter } from '@solana/wallet-adapter-base'
import type { Connector } from '@web3modal/scaffold'

Expand Down
Loading

0 comments on commit 0599513

Please sign in to comment.