Initial commit to accept native gorbagana chain tokens as payments
This commit is contained in:
parent
b4c6b0aa4e
commit
8b220a0dee
4276
package-lock.json
generated
4276
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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)} />
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
39
src/components/WalletProviders.tsx
Normal file
39
src/components/WalletProviders.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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'
|
||||
);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user