Skip to content

chore/add wallet_checkout solana support #867

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions advanced/wallets/react-wallet-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@reown/appkit-experimental": "1.6.8",
"@reown/walletkit": "1.0.0",
"@rhinestone/module-sdk": "0.1.25",
"@solana/spl-token": "^0.4.13",
"@solana/web3.js": "1.89.2",
"@taquito/signer": "^15.1.0",
"@taquito/taquito": "^15.1.0",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

2 changes: 1 addition & 1 deletion advanced/wallets/react-wallet-v2/src/data/EIP155Data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type EIP155Chain = {
namespace: string
smartAccountEnabled?: boolean
}
const blockchainApiRpc = (chainId: number) => {
export const blockchainApiRpc = (chainId: number) => {
return `https://rpc.walletconnect.org/v1?chainId=eip155:${chainId}&projectId=${process.env.NEXT_PUBLIC_PROJECT_ID}`
}
/**
Expand Down
3 changes: 2 additions & 1 deletion advanced/wallets/react-wallet-v2/src/data/SolanaData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@ export const SOLANA_SIGNING_METHODS = {
SOLANA_SIGN_TRANSACTION: 'solana_signTransaction',
SOLANA_SIGN_MESSAGE: 'solana_signMessage',
SOLANA_SIGN_AND_SEND_TRANSACTION: 'solana_signAndSendTransaction',
SOLANA_SIGN_ALL_TRANSACTIONS: 'solana_signAllTransactions'
SOLANA_SIGN_ALL_TRANSACTIONS: 'solana_signAllTransactions',
SOLANA_WALLET_CHECKOUT: 'wallet_checkout'
}
24 changes: 23 additions & 1 deletion advanced/wallets/react-wallet-v2/src/data/tokenUtil.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export type EIP155Token = {
name: string
icon: string
address?: string
assetAddress?: string
symbol: string
decimals: number
}
Expand All @@ -24,9 +24,31 @@ const ALL_TOKENS: EIP155Token[] = [
icon: '/token-logos/ETH.png',
symbol: 'ETH',
decimals: 18
},
{
name: 'SOL',
icon: '/token-logos/SOL.png',
symbol: 'SOL',
decimals: 9
}
]

export function getTokenData(tokenSymbol: string) {
return Object.values(ALL_TOKENS).find(token => token.symbol === tokenSymbol)
}

const SOLANA_KNOWN_TOKENS = [
{
name: 'USDC',
icon: '/token-logos/USDC.png',
symbol: 'USDC',
decimals: 6,
assetAddress: [
'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1/token:4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'
]
}
]

export function getSolanaTokenData(caip19AssetAddress: string) {
return SOLANA_KNOWN_TOKENS.find(token => token.assetAddress.includes(caip19AssetAddress))
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { EIP7715_METHOD } from '@/data/EIP7715Data'
import { refreshSessionsList } from '@/pages/wc'
import WalletCheckoutUtil from '@/utils/WalletCheckoutUtil'
import WalletCheckoutCtrl from '@/store/WalletCheckoutCtrl'
import { CheckoutErrorCode } from '@/types/wallet_checkout'
import { createCheckoutError } from '@/types/wallet_checkout'

export default function useWalletConnectEventsManager(initialized: boolean) {
/******************************************************************************
Expand Down Expand Up @@ -95,10 +97,18 @@ export default function useWalletConnectEventsManager(initialized: boolean) {
return ModalStore.open('SessionSendCallsModal', { requestEvent, requestSession })
}

case 'wallet_checkout':
case EIP155_SIGNING_METHODS.WALLET_CHECKOUT:
try {
await WalletCheckoutCtrl.actions.prepareFeasiblePayments(request.params[0])
} catch (error) {
// If it's not a CheckoutError, create one
if (!(error && typeof error === 'object' && 'code' in error)) {
error = createCheckoutError(
CheckoutErrorCode.INVALID_CHECKOUT_REQUEST,
`Unexpected error: ${error instanceof Error ? error.message : String(error)}`
)
}

return await walletkit.respondSessionRequest({
topic,
response: WalletCheckoutUtil.formatCheckoutErrorResponse(id, error)
Expand Down
138 changes: 136 additions & 2 deletions advanced/wallets/react-wallet-v2/src/lib/SolanaLib.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import { Keypair, Connection, SendOptions, VersionedTransaction } from '@solana/web3.js'
import {
Keypair,
Connection,
SendOptions,
VersionedTransaction,
PublicKey,
Transaction,
SystemProgram
} from '@solana/web3.js'
import bs58 from 'bs58'
import nacl from 'tweetnacl'
import {
getAssociatedTokenAddress,
createTransferInstruction,
TOKEN_PROGRAM_ID,
getOrCreateAssociatedTokenAccount
} from '@solana/spl-token'
import { SOLANA_MAINNET_CHAINS, SOLANA_TEST_CHAINS } from '@/data/SolanaData'

/**
Expand Down Expand Up @@ -101,7 +115,9 @@ export default class SolanaLib {
try {
bytes = bs58.decode(transaction)
} catch {
bytes = Buffer.from(transaction, 'base64')
// Convert base64 to Uint8Array to avoid type issues
const buffer = Buffer.from(transaction, 'base64')
bytes = new Uint8Array(buffer)
}

return VersionedTransaction.deserialize(bytes)
Expand All @@ -110,6 +126,124 @@ export default class SolanaLib {
private sign(transaction: VersionedTransaction) {
transaction.sign([this.keypair])
}

/**
* Send SOL to a recipient
* @param recipientAddress The recipient's address
* @param amount The amount to send in lamports (as a bigint)
* @returns The transaction signature/hash
*/
public async sendSol(recipientAddress: string, chainId: string, amount: bigint): Promise<string> {
console.log({ chainId })
const rpc = { ...SOLANA_TEST_CHAINS, ...SOLANA_MAINNET_CHAINS }[chainId]?.rpc

if (!rpc) {
throw new Error('There is no RPC URL for the provided chain')
}

const connection = new Connection(rpc, 'confirmed')
const fromPubkey = this.keypair.publicKey
const toPubkey = new PublicKey(recipientAddress)

// Create a simple SOL transfer transaction
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey,
toPubkey,
lamports: amount
})
)

// Get recent blockhash
const { blockhash } = await connection.getLatestBlockhash('confirmed')
transaction.recentBlockhash = blockhash
transaction.feePayer = fromPubkey

// Sign the transaction
transaction.sign(this.keypair)

// Send and confirm the transaction
const signature = await connection.sendRawTransaction(transaction.serialize())

// Wait for confirmation
await connection.confirmTransaction(signature, 'confirmed')

return signature
}

/**
* Send an SPL token to a recipient
* @param tokenAddress The token's mint address
* @param recipientAddress The recipient's address
* @param amount The amount to send (as a bigint)
* @returns The transaction signature/hash
*/
public async sendSplToken(
tokenAddress: string,
recipientAddress: string,
chainId: string,
amount: bigint
): Promise<string> {
const rpc = { ...SOLANA_TEST_CHAINS, ...SOLANA_MAINNET_CHAINS }[chainId]?.rpc

if (!rpc) {
throw new Error('There is no RPC URL for the provided chain')
}

const connection = new Connection(rpc, 'confirmed')
const fromWallet = this.keypair
const fromPubkey = fromWallet.publicKey
const toPubkey = new PublicKey(recipientAddress)
const mint = new PublicKey(tokenAddress)

// Get sender's token account (create if it doesn't exist)
const fromTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
fromWallet,
mint,
fromPubkey
)

// Check if recipient has a token account WITHOUT creating one
const associatedTokenAddress = await getAssociatedTokenAddress(mint, toPubkey)

const recipientTokenAccount = await connection.getAccountInfo(associatedTokenAddress)

if (!recipientTokenAccount) {
throw new Error(
`Recipient ${recipientAddress} doesn't have a token account for this SPL token. Transaction cannot proceed.`
)
}

// Create transfer instruction to existing account
const transferInstruction = createTransferInstruction(
fromTokenAccount.address,
associatedTokenAddress,
fromPubkey,
amount,
[],
TOKEN_PROGRAM_ID
)

// Create transaction and add the transfer instruction
const transaction = new Transaction().add(transferInstruction)

// Get recent blockhash
const { blockhash } = await connection.getLatestBlockhash('confirmed')
transaction.recentBlockhash = blockhash
transaction.feePayer = fromPubkey

// Sign the transaction
transaction.sign(fromWallet)

// Send and confirm the transaction
const signature = await connection.sendRawTransaction(transaction.serialize())

// Wait for confirmation
await connection.confirmTransaction(signature, 'confirmed')

return signature
}
}

export namespace SolanaLib {
Expand Down
Loading