Initial commit to accept native gorbagana chain tokens as payments

This commit is contained in:
Shreerang Kale 2025-07-23 15:18:28 +05:30
parent b4c6b0aa4e
commit 8b220a0dee
9 changed files with 4349 additions and 61 deletions

4276
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,10 @@
"@cosmjs/stargate": "^0.32.3",
"@solana/spl-token": "^0.4.13",
"@solana/web3.js": "^1.98.2",
"@solana/wallet-adapter-backpack": "^0.1.9",
"@solana/wallet-adapter-base": "^0.9.18",
"@solana/wallet-adapter-react": "^0.15.20",
"@solana/wallet-adapter-react-ui": "^0.9.18",
"axios": "^1.6.8",
"big.js": "^6.2.2",
"bn.js": "^5.2.2",

View File

@ -3,6 +3,8 @@ import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import ErrorBoundaryWrapper from "../components/ErrorBoundaryWrapper";
import WalletProviders from "../components/WalletProviders";
const geistSans = Geist({
variable: "--font-geist-sans",
@ -30,7 +32,9 @@ export default function RootLayout({
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ErrorBoundaryWrapper />
{children}
<WalletProviders>
{children}
</WalletProviders>
</body>
</html>
);

View File

@ -1,18 +1,21 @@
'use client';
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import dynamic from 'next/dynamic';
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
import URLForm from '@/components/URLForm';
import StatusDisplay from '@/components/StatusDisplay';
import { createApplicationDeploymentRequest } from '@/services/registry';
import { SolanaWalletState } from '@/types';
import { useWallet } from '@solana/wallet-adapter-react';
// Dynamically import components to avoid SSR issues with browser APIs
const SolanaConnect = dynamic(() => import('@/components/SolanaConnect'), { ssr: false });
const PaymentModal = dynamic(() => import('@/components/PaymentModal'), { ssr: false });
export default function Home() {
const { wallet, connected } = useWallet()
const [showWalletConnection, setShowWalletConnection] = useState(false);
const [solanaWalletState, setSolanaWalletState] = useState<SolanaWalletState>({
connected: false,
@ -33,6 +36,19 @@ export default function Home() {
const [shortCommitHash, setShortCommitHash] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!wallet || !connected || !wallet.adapter.publicKey) {
return;
}
setSolanaWalletState({
connected,
publicKey: wallet.adapter.publicKey.toString(),
walletType: 'backpack'
})
}, [connected, wallet])
const handleConnectWallet = () => {
setShowWalletConnection(true);
};
@ -125,6 +141,10 @@ export default function Home() {
>
Connect Solana Wallet
</button>
<div className="mt-4">
<WalletMultiButton />
</div>
</div>
) : (
<SolanaConnect onConnect={(walletState) => setSolanaWalletState(walletState)} />

View File

@ -9,6 +9,7 @@ import { Connection } from '@solana/web3.js';
import { sendSolanaTokenPayment } from '@/services/solana';
import { getRequiredTokenInfo } from '@/services/jupiter-price';
import { PaymentModalProps } from '@/types';
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
@ -26,6 +27,9 @@ export default function PaymentModal({
const [tokenDecimals, setTokenDecimals] = useState<number>(6); // Default fallback
const [loadingPrice, setLoadingPrice] = useState(true);
const { wallet } = useWallet()
const {connection: backConn} = useConnection();
const connection = useMemo(() => new Connection(SOLANA_RPC_URL), [])
// Get configuration from environment variables
@ -68,7 +72,7 @@ export default function PaymentModal({
try {
const tokenAmountBN = new BN(tokenAmount);
const result = await sendSolanaTokenPayment(connection, walletState.publicKey!, tokenAmountBN, walletState.walletType!);
const result = await sendSolanaTokenPayment(wallet!.adapter, backConn, connection, walletState.publicKey!, tokenAmountBN, walletState.walletType!);
if (result.success && result.transactionSignature) {
onPaymentComplete(result.transactionSignature);
@ -80,7 +84,7 @@ export default function PaymentModal({
} finally {
setLoading(false);
}
}, [connection, walletState, tokenAmount, loadingPrice, onPaymentComplete]);
}, [connection, walletState, tokenAmount, loadingPrice, onPaymentComplete, backConn, wallet]);
if (!isOpen) return null;

View File

@ -0,0 +1,39 @@
'use client';
import { useMemo } from 'react';
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { BackpackWalletAdapter } from '@solana/wallet-adapter-backpack';
// Default styles that can be overridden by your app
import '@solana/wallet-adapter-react-ui/styles.css';
import assert from 'assert';
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
interface WalletProvidersProps {
children: React.ReactNode;
}
export default function WalletProviders({ children }: WalletProvidersProps) {
// Configure the Solana network endpoint
const endpoint = useMemo(() => {
return SOLANA_RPC_URL;
}, []);
const wallets = useMemo(
() => [new BackpackWalletAdapter()],
[]
);
return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>
{children}
</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
);
}

View File

@ -1,12 +1,13 @@
import assert from 'assert';
import BN from 'bn.js';
import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import { Connection, PublicKey, SystemProgram, Transaction } from '@solana/web3.js';
import {
TOKEN_PROGRAM_ID,
createTransferInstruction,
createAssociatedTokenAccountInstruction,
ASSOCIATED_TOKEN_PROGRAM_ID
} from '@solana/spl-token';
import { Adapter } from '@solana/wallet-adapter-base';
import { SolanaPaymentResult, SolanaWalletType, SolanaWalletState } from '../types';
@ -88,18 +89,22 @@ interface WalletAdapter {
}
export async function sendSolanaTokenPayment(
backWallet: WalletAdapter | Adapter,
backConnection: Connection,
connection: Connection,
walletPublicKey: string,
tokenAmount: BN,
walletType: SolanaWalletType
): Promise<SolanaPaymentResult> {
try {
let wallet: WalletAdapter | null = null;
let wallet: WalletAdapter | Adapter | null = null;
if (walletType === 'phantom') {
wallet = window.phantom?.solana || null;
} else if (walletType === 'solflare') {
wallet = window.solflare || null;
} else if (walletType === 'backpack') {
wallet = backWallet
}
if (!wallet) {
@ -182,9 +187,27 @@ export async function sendSolanaTokenPayment(
transaction.recentBlockhash = latestBlockhash.blockhash;
transaction.feePayer = senderPublicKey;
let signature;
console.log('Sending transaction...');
const { signature } = await wallet.signAndSendTransaction(transaction);
console.log('Transaction sent:', signature);
if (isWalletAdapter(wallet)) {
const result = await wallet.signAndSendTransaction(transaction);
signature = result.signature;
console.log('Transaction sent:', signature);
} else {
console.log("IN BACK TXXXXIRIFPOIEPODPOJOPIJOIJIO")
const backTx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: wallet.publicKey!,
toPubkey: receiverPublicKey,
// Note you can't send below minimum required for rent exemption to a random
// account so use something above that value
lamports: 206274714,
})
);
signature = await wallet.sendTransaction(backTx, backConnection)
console.log('BACKKKKK, Transaction sent:', signature);
}
// Confirm transaction
const confirmation = await connection.confirmTransaction({
@ -223,4 +246,12 @@ export const checkSolanaWalletConnection = (walletType: SolanaWalletType): boole
} catch {
return false;
}
};
};
function isWalletAdapter(obj: any): obj is WalletAdapter {
return (
obj !== null &&
typeof obj === 'object' &&
typeof obj.signAndSendTransaction === 'function'
);
}

View File

@ -45,7 +45,7 @@ export interface CreateRecordResponse {
message?: string;
}
export type SolanaWalletType = 'phantom' | 'solflare';
export type SolanaWalletType = 'phantom' | 'solflare' | 'backpack';
export interface SolanaPaymentResult {
success: boolean;

View File

@ -12,15 +12,15 @@ const extractTxInfo = async (connection: Connection, transactionSignature: strin
}
const transferInstruction = result.transaction.message.instructions.find(
(instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID)
(instr) => 'parsed' in instr
);
if (!transferInstruction || !('parsed' in transferInstruction)) {
throw new Error('Transfer instruction not found');
}
const { info: { amount, authority } } = transferInstruction.parsed;
return { authority, amount };
const { info: { lamports, authority } } = transferInstruction.parsed;
return { authority, amount: lamports };
};
export const verifyUnusedSolanaPayment = async (
@ -39,6 +39,8 @@ export const verifyUnusedSolanaPayment = async (
// Fetch transaction details
const transactionResult = await connection.getParsedTransaction(transactionSignature, 'confirmed');
console.dir(transactionResult, {depth: null})
if (!transactionResult) {
return {
valid: false,
@ -97,11 +99,11 @@ export const verifyUnusedSolanaPayment = async (
let foundValidTransfer = false;
for (const instruction of transactionResult.transaction.message.instructions) {
if ('parsed' in instruction && instruction.programId.equals(TOKEN_PROGRAM_ID)) {
if ('parsed' in instruction ) {
const parsed = instruction.parsed;
if (parsed.type === 'transferChecked' || parsed.type === 'transfer') {
// Verify amount and recipient's associated token address
if (parsed.info.amount === amount && parsed.info.destination === expectedTokenAccount.toBase58() ) {
if (parsed.info.destination === process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS ) {
foundValidTransfer = true;
break;
}