Gotowość produkcyjna

Tworzenie lokalnie i testowanie na devnecie to świetny sposób, by zacząć pracę z płatnościami w Solanie. Jednak gdy jesteś gotowy na wdrożenie na Mainnet, musisz być świadomy specyfiki mainnetu. Devnet wybacza błędy. Mainnet – nie. Ten przewodnik omawia kluczowe różnice, które pozwolą zapewnić użytkownikom płynne doświadczenie.

DevnetMainnet
Darmowe SOL z faucetówZdobądź prawdziwe SOL na opłaty
Niska konkurencja o miejsce w blokuOpłaty priorytetowe mają znaczenie
Transakcje przechodzą łatwoKonfiguracja transakcji jest kluczowa
Publiczny RPC wystarczaWymagany produkcyjny RPC
Klucze i minty z devnetuInne klucze i minty tokenów — zaktualizuj konfigurację

Infrastruktura RPC

Publiczne endpointy (api.mainnet-beta.solana.com) mają limity zapytań i brak SLA. Są odpowiednie do developmentu, ale zawiodą w produkcyjnych płatnościach — to jak próba uruchomienia procesora płatności przez współdzielone API bez gwarancji dostępności.

Nigdy nie używaj publicznego RPC w produkcji

Korzystaj z prywatnego dostawcy RPC dla niezawodnego i niskolatencyjnego dostępu.

Wybierając dostawcę RPC, zwróć uwagę na:

  • Niezawodność: SLA z gwarancją dostępności (99,9%+)
  • Opóźnienia: Bliskość geograficzna do użytkowników
  • Funkcje: Funkcje szybkiego zatwierdzania transakcji, indeksowanie, API opłat priorytetowych

Pełną listę dostawców RPC znajdziesz w przewodniku RPC Infrastructure Providers.

Redundantna konfiguracja RPC

Jak każdy dostawca usług sieciowych, dostawcy RPC mogą mieć przestoje lub okresy pogorszonej wydajności. Aby Twoja aplikacja była odporna, skonfiguruj ją do korzystania z wielu dostawców RPC.

Solana Kit to biblioteka umożliwiająca dostosowanie transportu RPC, co pozwala zbudować własnego redundantnego klienta RPC. Oto przykład, jak można jej użyć do stworzenia redundantnego klienta RPC:

import { RpcTransport } from "@solana/rpc-spec";
import { RpcResponse } from "@solana/rpc-spec-types";
import { createHttpTransport } from "@solana/rpc-transport-http";
// Create a transport for each RPC server
const transports = [
createHttpTransport({ url: "https://mainnet-beta.my-server-1.com" }),
createHttpTransport({ url: "https://mainnet-beta.my-server-2.com" }),
createHttpTransport({ url: "https://mainnet-beta.my-server-3.com" })
];
// Create a wrapper transport that distributes requests to them
let nextTransport = 0;
async function roundRobinTransport<TResponse>(
...args: Parameters<RpcTransport>
): Promise<RpcResponse<TResponse>> {
const transport = transports[nextTransport];
nextTransport = (nextTransport + 1) % transports.length;
return await transport(...args);
}

Jeśli nie chcesz budować własnych narzędzi do routingu, możesz skorzystać z usług firm trzecich, takich jak Iron Forge, aby obsłużyć routing za Ciebie.

Lądowanie transakcji

Na Devnecie transakcje są realizowane dość łatwo. Na Mainnecie konkurujesz o miejsce w bloku. Aby zwiększyć szanse na włączenie transakcji do bloku, powinieneś upewnić się, że transakcja została poprawnie złożona. Oznacza to:

  • dołączenie świeżego blockhasha przed wysłaniem transakcji
  • dodanie instrukcji opłaty priorytetowej z konkurencyjną opłatą priorytetową
  • dodanie instrukcji limitu jednostek obliczeniowych z limitem opartym na szacowanej liczbie jednostek obliczeniowych wymaganych dla transakcji

Dodatkowo warto rozważyć inne narzędzia, takie jak Jito Bundles, aby zwiększyć szanse na włączenie transakcji do bloku. Przyjrzyjmy się tym narzędziom bliżej.

Konfiguracja wysyłania transakcji

Wysyłając transakcje na Mainnecie, skonfiguruj te parametry, aby uzyskać optymalny wskaźnik lądowania:

Zarządzanie blockhashem:

  • Pobierz z confirmed commitment
  • Zapisz lastValidBlockHeight zwrócony przez getLatestBlockhash — to informuje, kiedy Twoja transakcja wygaśnie
  • Blockhashe wygasają po ok. 150 blokach (ok. 60–90 sekund)

Opcje wysyłania:

  • maxRetries: 0 — Wyłącz automatyczne ponawianie RPC. Obsłuż ponowienia samodzielnie, aby móc odświeżyć blockhash w razie potrzeby.
  • skipPreflight: true — Pomijaj symulację przed wysłaniem. Użyj tego, jeśli transakcja została już zweryfikowana i zależy Ci na najniższym opóźnieniu. Pozostaw to false podczas developmentu, aby wcześnie wychwycić błędy.
import { createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc(process.env.RPC_URL!);
// 1. Get blockhash with confirmed commitment
const { value: latestBlockhash } = await rpc
.getLatestBlockhash({ commitment: "confirmed" })
.send();
// 2. Build and sign your transaction with the blockhash
// ... (transaction building code)
// 3. Send with production settings
const signature = await rpc
.sendTransaction(encodedTransaction, {
encoding: "base64",
maxRetries: 0n, // Handle retries yourself
skipPreflight: true, // Skip simulation for speed (use false during dev)
preflightCommitment: "confirmed"
})
.send();
// 4. Track expiration using lastValidBlockHeight
const { lastValidBlockHeight } = latestBlockhash;
// Stop retrying when current block height exceeds lastValidBlockHeight

Używaj opłat priorytetowych

Każda transakcja w sieci Solana wymaga opłaty transakcyjnej, płatnej w SOL. Opłaty transakcyjne dzielą się na dwie części: opłatę bazową i opłatę priorytetową. Opłata bazowa rekompensuje walidatorom przetwarzanie transakcji. Opłata priorytetowa to opcjonalna opłata, która zwiększa szansę, że obecny lider przetworzy Twoją transakcję. Można to porównać do ekspresowej wysyłki: płacisz więcej za szybszą i bardziej niezawodną realizację.

Jak działają opłaty:

Total fee = Base fee (5,000 lamports per signature) + Priority fee
Priority fee = Compute units x Price per unit (micro-lamports per compute unit)

Koszty w praktyce:

  • Prosty transfer USDC: ~0,001-0,005 USD w normalnych warunkach
  • W czasie przeciążenia: ~0,01-0,05 USD
  • Szczytowe przeciążenie: Może wzrosnąć jeszcze bardziej

Przykładowa implementacja:

Pakiet @solana-program/compute-budget udostępnia funkcję pomocniczą, która pozwala łatwo zaktualizować lub dodać instrukcję ceny jednostki obliczeniowej (w mikro-lamportach) do transakcji.

import { updateOrAppendSetComputeUnitPriceInstruction } from "@solana-program/compute-budget";
const tx = pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayerSigner(payer, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
(m) => appendTransactionMessageInstructions([myInstructions], m),
(m) => updateOrAppendSetComputeUnitPriceInstruction(1000n as MicroLamports, m)
);

Jak uzyskać szacunkowe opłaty: Większość dostawców RPC oferuje API opłat priorytetowych:

Pełną mechanikę opłat znajdziesz w Opłaty transakcyjne oraz w naszym przewodniku: Jak dodać opłaty priorytetowe do transakcji.

Optymalizuj jednostki obliczeniowe

Obliczenia na Solanie to w praktyce miara ilości pracy wykonywanej przez program. Istnieje limit ilości obliczeń, które można wykorzystać w jednej transakcji (obecnie 1,4 miliona jednostek obliczeniowych) oraz limit na ilość obliczeń na konto na blok (obecnie 100 milionów jednostek obliczeniowych).

Podczas wysyłania transakcji musisz oszacować ilość jednostek obliczeniowych, które zostaną użyte, i odpowiednio ustawić limit jednostek obliczeniowych – jest to w praktyce prośba o zarezerwowanie określonej części całkowitej pojemności dla Twojej transakcji. W praktyce oznacza to, że prawidłowe oszacowanie wymaganych jednostek obliczeniowych jest kluczowe, aby Twoja transakcja została uwzględniona w bloku (i ważne dla zarządzania opłatami priorytetowymi).

Solana JSON RPC API posiada metodę simulatetransaction, która może być używana do szacowania jednostek obliczeniowych wymaganych przez transakcję, co obejmuje szacunkową liczbę jednostek obliczeniowych, które zostaną użyte. @solana/kit dostarcza funkcje pomocnicze, które szacują limity zasobów transakcji i ustawiają je w wiadomości w jednym kroku (używając metody simulatetransaction pod spodem). W przypadku transakcji w wersji 1 te funkcje pomocnicze szacują również limit rozmiaru danych załadowanych kont.

import {
estimateResourceLimitsFactory,
estimateAndSetResourceLimitsFactory
} from "@solana/kit";
const estimateResourceLimits = estimateResourceLimitsFactory({ rpc });
const estimateAndSetResourceLimits = estimateAndSetResourceLimitsFactory(
estimateResourceLimits
);
const txWithResourceLimits = await estimateAndSetResourceLimits(tx);

Jeśli budujesz i wysyłasz transakcje za pomocą klienta z wtyczką kit, zazwyczaj nie potrzebujesz tego kroku — klient automatycznie dodaje instrukcje budżetu obliczeniowego podczas wysyłania (.sendTransaction()). Powyższy ręczny przepływ jest przeznaczony dla przypadków, gdy transakcje składane są bezpośrednio za pomocą @solana/kit.

W środowisku produkcyjnym, jeśli wielokrotnie powtarzasz ten sam typ transakcji, powinieneś rozważyć buforowanie szacunku obliczeniowego dla danego typu transakcji, aby uniknąć narzutu związanego z każdorazowym szacowaniem jednostek obliczeniowych.

Pakiety Jito

Pakiety Jito to narzędzie do zarządzania atomowym wykonaniem wielu transakcji. Osiąga się to poprzez wysyłanie wielu transakcji do sieci Jito wraz z naplwą. Napiwki mogą być wykorzystywane do zachęcania sieci Jito do uwzględnienia Twoich transakcji w bloku.

Zasoby:

Strategie Ponownych Prób

Transakcje mogą się nie powieść z wielu powodów. W przeciwieństwie do tradycyjnych API płatności, które natychmiast zwracają wynik sukcesu/niepowodzenia, transakcje blockchain wymagają śledzenia potwierdzenia.

Kluczowe pojęcia:

  • Wygaśnięcie blockhasha: Transakcje są ważne przez ~150 bloków (~60–90 sekund)
  • Idempotentność: Ta sama podpisana transakcja zawsze generuje ten sam podpis — ponowne wysłanie jest bezpieczne
  • Wykładniczy backoff: Unikaj przeciążania sieci przez szybkie ponowne próby
import {
createSolanaRpc,
createSolanaRpcSubscriptions,
sendAndConfirmTransactionFactory,
isSolanaError,
SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED
} from "@solana/kit";
const rpc = createSolanaRpc(process.env.RPC_URL!);
const rpcSubscriptions = createSolanaRpcSubscriptions(process.env.RPC_WSS_URL!);
const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({
rpc,
rpcSubscriptions
});
// Send with automatic confirmation tracking and block height monitoring
try {
await sendAndConfirmTransaction(signedTransaction, {
commitment: "confirmed",
// Optional: abort after 75 seconds
abortSignal: AbortSignal.timeout(75_000)
});
} catch (e) {
if (isSolanaError(e, SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED)) {
// Blockhash expired—rebuild transaction with fresh blockhash and retry
rebuildAndRetryTransaction(); // implement your own logic for rebuilding and retrying the transaction
}
throw e;
}

sendAndConfirmTransactionFactory z @solana/kit automatycznie obsługuje polling potwierdzenia i śledzenie wysokości bloku. Zgłasza SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED gdy blockhash transakcji wygasa, sygnalizując konieczność przebudowania transakcji z nowym blockhash.

Dodatkowe zasoby

Poziomy potwierdzenia

Solana oferuje trzy poziomy potwierdzenia. W terminologii tradycyjnych finansów:

PoziomDefinicja SolanaOdpowiednik tradycyjnyPrzypadek użycia
processedW bloku, bez głosowaniaAutoryzacja oczekującaAktualizacje UI w czasie rzeczywistym
confirmedGłosowanie superwikszościŚrodki rozliczoneWiększość płatności
finalizedZakorzeniony, nieodwracalnyŚrodki rozrachowaneWysokie kwoty, zgodność

Kiedy używać każdego z nich:

  • Aktualizacje UI: Pokazuj processed dla natychmiastowej informacji zwrotnej ("Płatność przesłana")
  • Uznanie konta użytkownika: Czekaj na confirmed (bezpieczne dla większości transakcji)
  • Wysyłka towarów fizycznych: Czekaj na finalized
  • Duże wypłaty: Czekaj na finalized
  • Zgodność/audyt: Zawsze rejestruj status finalized

Więcej informacji na temat sprawdzania statusu transakcji znajdziesz w Interakcja z Solaną.

Obsługa błędów

Solana Kit udostępnia typowane błędy za pomocą isSolanaError(). Używaj konkretnych kodów błędów zamiast dopasowywania ciągów znaków:

import {
isSolanaError,
SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED,
SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_FEE,
SOLANA_ERROR__TRANSACTION_ERROR__BLOCKHASH_NOT_FOUND,
SOLANA_ERROR__INSTRUCTION_ERROR__INSUFFICIENT_FUNDS
} from "@solana/kit";
function handlePaymentError(error: unknown): {
message: string;
retryable: boolean;
} {
// Blockhash expired—rebuild and retry
if (
isSolanaError(error, SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED) ||
isSolanaError(error, SOLANA_ERROR__TRANSACTION_ERROR__BLOCKHASH_NOT_FOUND)
) {
return { message: "Transaction expired—rebuilding", retryable: true };
}
// Insufficient SOL for fees
if (
isSolanaError(
error,
SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_FEE
)
) {
return { message: "Not enough SOL for fees", retryable: false };
}
// Insufficient token balance
if (
isSolanaError(error, SOLANA_ERROR__INSTRUCTION_ERROR__INSUFFICIENT_FUNDS)
) {
return { message: "Insufficient balance", retryable: false };
}
// Unknown error
console.error("Payment error:", error);
return { message: "Payment failed—please retry", retryable: true };
}

Typowe kody błędów:

Kod błęduPrzyczynaRozwiązanie
BLOCK_HEIGHT_EXCEEDEDBlockhash wygasłPrzebuduj z nowym blockhash
BLOCKHASH_NOT_FOUNDBlockhash nie znalezionyPrzebuduj z nowym blockhash
INSUFFICIENT_FUNDS_FOR_FEEZa mało SOLDoładuj płatnika opłat lub użyj abstrakcji opłat
INSUFFICIENT_FUNDSZa mało tokenówUżytkownik potrzebuje większego salda
ACCOUNT_NOT_FOUNDBrak token accountUtwórz ATA w transakcji

Transakcje bez opłat gazowych

Użytkownicy oczekują możliwości płacenia w stablecoinach, a nie zdobywania SOL na pokrycie opłat sieciowych. Transakcje bez opłat gazowych rozwiązują ten problem — podobnie jak użytkownicy Venmo nie zastanawiają się nad opłatami ACH. Siehe Abstrakcja opłat aby zapoznać się z pełną implementacją.

Bezpieczeństwo

Zarządzanie kluczami

  • Nigdy nie ujawniaj kluczy prywatnych w kodzie frontendowym. Używaj podpisywania po stronie backendu, portfeli sprzętowych, portfeli wielopodpisowych lub usług zarządzania kluczami.
  • Rozdzielaj portfele hot i cold. Portfel hot do operacji bieżących, cold do przechowywania środków.
  • Twórz kopie zapasowe wszystkich kluczy produkcyjnych. Przechowuj zaszyfrowane kopie zapasowe w wielu bezpiecznych lokalizacjach. Utrata klucza oznacza trwałą utratę dostępu.
  • Używaj różnych kluczy dla devnet i mainnet. Klucze devnet nie powinny być tymi samymi kluczami co mainnet. Używaj konfiguracji opartej na środowisku, aby zapewnić ładowanie właściwych kluczy dla każdej sieci.

Infrastruktura podpisywania

Do produkcyjnego podpisywania po stronie backendu używaj Keychain — ujednoliconej biblioteki do podpisywania, która abstrahuje wiele backendów zarządzania kluczami przez jeden interfejs: Memory, Vault, Privy, Turnkey, AWS KMS, Fireblocks, GCP KMS, CDP, Para, Dfns, Crossmint, Openfort i Utila. Dzięki temu możesz lokalnie tworzyć aplikacje z kluczami w pamięci, a następnie przełączyć się na produkcyjne backendy bez zmiany kodu aplikacji.

Nie wiesz, który backend wybrać? Zobacz Wybór backendu. Keychain jest dostępny zarówno dla Rust, jak i TypeScript.

Bezpieczeństwo RPC

Traktuj endpointy RPC jak klucze API—nie ujawniaj ich w kodzie frontendowym, gdzie mogą zostać wydobyte i nadużyte. Użyj proxy backendowego lub zmiennych środowiskowych, które nie są pakowane do kodu klienta.

Monitorowanie

Śledź te metryki w środowisku produkcyjnym:

MetrykaDlaczego
Wskaźnik sukcesu transakcjiWczesne wykrywanie problemów
Opóźnienie potwierdzeniaMonitorowanie zdrowia sieci
Wydatki na opłatę priorytetowąZarządzanie kosztami
Wskaźnik błędów RPCZdrowie dostawcy

Skonfiguruj alerty dla:

  • Transferów powyżej progu z treasury
  • Gwałtownych wzrostów wskaźnika nieudanych transakcji
  • Nietypowych wzorców odbiorców
  • Wzrostów wskaźnika błędów RPC

W celu monitorowania transakcji w czasie rzeczywistym na dużą skalę zobacz nasz przewodnik po indeksowaniu.

Weryfikuj adresy

Każdy token i program ma dokładnie jeden poprawny adres na mainnecie. Sfałszowane tokeny imitujące USDC lub inne stablecoiny są powszechne—będą miały tę samą nazwę i symbol, ale inny adres mint. Twoja aplikacja powinna zakodować na stałe lub umieścić na liście dozwolonych adresy mint (w zależności od wymagań), nigdy nie akceptuj ich dynamicznie z niezaufanych źródeł.

Konfiguracja oparta na środowisku: Devnet i Mainnet często używają całkowicie różnych mintów tokenów. Skonfiguruj swoją aplikację tak, aby ładowała właściwe adresy dla każdego środowiska—nie koduj na sztywno adresów mainnet i nie zapomnij o ich zamianie podczas testowania, a co gorsza, nie wysyłaj adresów devnet do produkcji.

Niektóre popularne minty stablecoinów to:

TokenEmitentAdres Mint
USDCCircleEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
USDTTetherEs9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB
PYUSDPayPal2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo
USDGPaxos2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH

Adresy programów również mają znaczenie. Wysyłanie instrukcji do niewłaściwego programu zakończy się niepowodzeniem—lub co gorsza, spowoduje nieodwracalną utratę środków. Adresy Token Program to:

ProgramAdres
Token ProgramTokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Token-2022 ProgramTokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Umieszczenie właściwych adresów na liście dozwolonych chroni przed sfałszowanymi tokenami, ale nie zabezpiecza przed wysłaniem środków na niewłaściwy rodzaj konta. Adres odbiorcy może być portfelem, token account, mennicą lub programem, a każdy wymaga odrębnej obsługi. Natywne SOL wysłane na cokolwiek innego niż portfel wymyka się spod kontroli nadawcy — bezpowrotnie utracone w przypadku mennicy lub programu, możliwe do odzyskania jedynie przez właściciela konta w przypadku token account — bez żadnej nieudanej transakcji, która ostrzegłaby użytkownika.

Zweryfikuj odbiorców przed wysłaniem

Sklasyfikuj każdy adres odbiorcy przed podpisaniem przelewu. Zobacz Weryfikacja adresu, aby zapoznać się z pełnym drzewem decyzyjnym i kodem referencyjnym rozróżniającym portfele, token accounts, mennice i programy.

Lista kontrolna przed uruchomieniem

  • Pozyskano SOL w sieci mainnet na opłaty i rent
  • Skonfigurowano produkcyjny RPC (nie publiczny endpoint)
  • Skonfigurowano zapasowy endpoint RPC
  • Zaimplementowano opłaty priorytetowe z dynamicznym ustalaniem cen
  • Logika ponawiania obsługuje wygaśnięcie blockhasha
  • Poziom potwierdzenia odpowiedni dla przypadku użycia
  • Wszystkie typowe błędy obsługiwane w sposób prawidłowy
  • Skonfigurowano Gasless (jeśli dotyczy)
  • Zweryfikowano adresy tokenów w sieci mainnet (nie mints devnet)
  • Walidacja adresu odbiorcy przed wysłaniem (portfel vs token account vs mint vs program)
  • Wszystkie klucze bezpiecznie zarchiwizowane
  • Przegląd zarządzania kluczami (brak kluczy we frontendzie)
  • Aktywny monitoring transakcji i alerty
  • Przeprowadzono testy obciążeniowe przy oczekiwanym wolumenie

Wdrażanie programów

Jeśli wdrażasz niestandardowy program Solana jako część swojej infrastruktury płatności, istnieją dodatkowe kwestie do rozważenia.

Przed wdrożeniem

  • Wersja Solana CLI: Upewnij się, że używasz najnowszej wersji Solana CLI.
  • keypair programu: Twój program będzie miał inny adres w sieci mainnet niż w devnet (chyba że ponownie używasz tego samego keypair). Zaktualizuj wszystkie odwołania w konfiguracji swojej aplikacji. Przechowuj keypair programu w bezpiecznym miejscu (pamiętaj, że uruchomienie cargo clean prawdopodobnie usunie Twój keypair programu).
  • Inicjalizacja kont: Jeśli Twój program wymaga kont administratora, PDA lub innych kont stanu, upewnij się, że zostały one utworzone w sieci mainnet, zanim użytkownicy zaczną korzystać z Twojej aplikacji. To samo dotyczy wszelkich associated token accounts (ATA), których wymaga Twój program.

Proces wdrożenia

  • Konta buforowe: Duże programy wdrażane są za pośrednictwem kont buforowych. Polecenie solana program deploy obsługuje to automatycznie, jednak należy pamiętać, że wdrożenie nie jest atomowe — w razie przerwania może być konieczne odzyskanie lub zamknięcie kont buforowych. Zobacz Wdrażanie programów.
  • Uprawnienia do aktualizacji: Zdecyduj, czy Twój program powinien być aktualizowalny po uruchomieniu. Aby zapewnić niezmienność, cofnij uprawnienia do aktualizacji po wdrożeniu. Dla większej elastyczności odpowiednio zabezpiecz klucz uprawnień do aktualizacji.
  • Czynsz: Upewnij się, że Twój portfel wdrożeniowy ma wystarczająco dużo SOL, aby pokryć minimalne wymagania zwolnienia z czynszu dla wszystkich program accounts.
  • Weryfikacja: Zweryfikuj swój program, aby upewnić się, że wdrożony w sieci Solana program wykonywalny odpowiada kodowi źródłowemu w Twoim repozytorium

Pełne wskazówki dotyczące wdrażania programów znajdziesz w Wdrażanie programów.

Is this page helpful?