[{"content":"Saya Widnyana, seorang tech-agnostic yang tidak terikat pada satu stack tertentu dengan pengalaman profesional lebih dari 11 tahun di bidang Administrasi Sistem, Infrastruktur Cloud, dan Rekayasa Perangkat Lunak.\nSaya terbuka untuk kegiatan mentoring, kolaborasi proyek sampingan, diskusi ide, uji coba produk baru, maupun menulis. Saya juga terbuka untuk kegiatan pro-bono — silakan hubungi saya jika Anda membutuhkannya.\nWork Experiences Self-Employed Cloud Infrastructure \u0026amp; System Architecture Consultant \u0026mdash; January 2025 - Present Helping organizations design and scale resilient infrastructure across AWS, GCP, and other public clouds. Specializing in System Architecture, Team Management, Web3 infrastructure, and Cloud Infrastructure Strategy. Indonesian Stablecoin Provider Sr. Infrastructure Engineer \u0026mdash; June 2025 - Present Designing and maintaining cloud infrastructure for a stablecoin issuance and management platform. Stealth Startup Sr. DevOps Engineer \u0026mdash; June 2024 ‑ January 2025 Built and maintained IaC on GCP; ensuring scalability and reliability on Kubernetes clusters and VMs. Led OpenTelemetry adoption for observability, collaborated with Data Team on data pipelines optimization, and reduced Cloud Infrastructure costs. Koinworks Engineering Manager at Infrastructure.DevOps team \u0026mdash; Apr 2023 - Mar 2024 Led cloud infrastructure cost optimization (33.33% savings) while transforming DevOps culture—enabling engineers to own deployments, CI/CD pipelines, and workflow automation. Established a strong documentation and knowledge-sharing culture, improving onboarding, decision-making (ADR, RFC), and team efficiency through mentorship and system reliability practices. Engineering Manager at Platform.Trust team \u0026mdash; Oct 2021 - Mar 2023 Re-architected cloud infrastructure, shifting from VM-based to container-based deployments on GKE, and led the migration from a monolith to event-driven microservices, adopting GitLab CI and RabbitMQ. Built a high-performing, autonomous engineering team, streamlined Scrum ceremonies (reducing meeting time by 50%), and drove observability adoption using Datadog. Sotfware Engineer \u0026mdash; Jan 2020 - Oct 2021 Built and optimized data pipelines, backend services, and private integrations while independently managing Kubernetes clusters and GCP services. Introduced CI/CD with GitLab CI and ensured security compliance through rigorous report validation. PT. Forta Digital Indonesia Linux System Administrator \u0026mdash; Aug 2019 - Dec 2019 Set up and maintained the office network, local development ecosystem, and key infrastructure components like Tenable.SC, Splunk, MongoDB, and RabbitMQ. Implemented Continuous Deployment with Ansible and GitLab CI, while managing product deployments to client environments. PT Mahapatih Sibernusa Teknologi Full Stack Engineer \u0026mdash; Mar 2019 - Aug 2019 Developed backend API for Sistem Manajemen Kerentanan (Sepatih) and implemented a missing Python SDK for the Tenable.SC API. Automated deployment processes using Ansible and GitLab CI. Binokular Media Utama Senior Software Engineer \u0026mdash; Nov 2015 - Jan 2018 Built and optimized AI-driven media monitoring systems, including entity extraction, sentiment analysis, and topic classification. Designed and maintained high-availability database and search clusters, ensuring reliability and scalability. PT Sebangsa Bersama Backend Engineer ~ Apr 2014 - Oct 2015 Developed Sebangsa Auth with OAuth 1.0 and maintained Sebangsa Premium API. Implemented a fan-out algorithm for efficient post distribution to followers. Detail pengalaman dan keahlian saya dapat Anda temukan melalui tautan berikut CV.\nUses Lihat halaman /uses untuk informasi lebih lanjut mengenai alat dan teknologi yang saya gunakan.\n","permalink":"https://widnyana.web.id/id/about/","summary":"\u003cp\u003eSaya \u003cstrong\u003eWid\u003c/strong\u003enyana, seorang \u003cem\u003etech-agnostic\u003c/em\u003e yang tidak terikat pada satu \u003cem\u003estack\u003c/em\u003e tertentu dengan pengalaman profesional lebih dari 11 tahun di bidang Administrasi Sistem, Infrastruktur Cloud, dan Rekayasa Perangkat Lunak.\u003c/p\u003e\n\u003cp\u003eSaya terbuka untuk kegiatan mentoring, kolaborasi proyek sampingan, diskusi ide, uji coba produk baru, maupun menulis. Saya juga terbuka untuk kegiatan pro-bono — silakan hubungi saya jika Anda membutuhkannya.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"work-experiences\"\u003eWork Experiences\u003c/h2\u003e\n\u003ch3 id=\"self-employed\"\u003eSelf-Employed\u003c/h3\u003e\n\u003ch4 id=\"cloud-infrastructure--system-architecture-consultant--january-2025---present\"\u003eCloud Infrastructure \u0026amp; System Architecture Consultant \u0026mdash; January 2025 - Present\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eHelping organizations design and scale resilient infrastructure across AWS, GCP, and other public clouds. Specializing in System Architecture, Team Management, Web3 infrastructure, and Cloud Infrastructure Strategy.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"indonesian-stablecoin-provider\"\u003eIndonesian Stablecoin Provider\u003c/h3\u003e\n\u003ch4 id=\"sr-infrastructure-engineer--june-2025---present\"\u003eSr. Infrastructure Engineer \u0026mdash; June 2025 - Present\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eDesigning and maintaining cloud infrastructure for a stablecoin issuance and management platform.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"stealth-startup\"\u003eStealth Startup\u003c/h3\u003e\n\u003ch4 id=\"sr-devops-engineer--june-2024--january-2025\"\u003eSr. DevOps Engineer \u0026mdash; June 2024 ‑ January 2025\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eBuilt and maintained IaC on GCP; ensuring scalability and reliability on Kubernetes clusters and VMs.\u003c/li\u003e\n\u003cli\u003eLed OpenTelemetry adoption for observability, collaborated with Data Team on data pipelines optimization, and reduced Cloud Infrastructure costs.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"koinworks\"\u003eKoinworks\u003c/h3\u003e\n\u003ch4 id=\"engineering-manager-at-infrastructuredevops-team--apr-2023---mar-2024\"\u003eEngineering Manager at Infrastructure.DevOps team \u0026mdash; \u003cem\u003eApr 2023 - Mar 2024\u003c/em\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eLed cloud infrastructure cost optimization (33.33% savings) while transforming DevOps culture—enabling engineers to own deployments, CI/CD pipelines, and workflow automation.\u003c/li\u003e\n\u003cli\u003eEstablished a strong documentation and knowledge-sharing culture, improving onboarding, decision-making (ADR, RFC), and team efficiency through mentorship and system reliability practices.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"engineering-manager-at--platformtrust-team--oct-2021---mar-2023\"\u003eEngineering Manager at  Platform.Trust team \u0026mdash; \u003cem\u003eOct 2021 - Mar 2023\u003c/em\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eRe-architected cloud infrastructure, shifting from VM-based to container-based deployments on GKE, and led the migration from a monolith to event-driven microservices, adopting GitLab CI and RabbitMQ.\u003c/li\u003e\n\u003cli\u003eBuilt a high-performing, autonomous engineering team, streamlined Scrum ceremonies (reducing meeting time by 50%), and drove observability adoption using Datadog.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"sotfware-engineer--jan-2020---oct-2021\"\u003eSotfware Engineer \u0026mdash; \u003cem\u003eJan 2020 - Oct 2021\u003c/em\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eBuilt and optimized data pipelines, backend services, and private integrations while independently managing Kubernetes clusters and GCP services.\u003c/li\u003e\n\u003cli\u003eIntroduced CI/CD with GitLab CI and ensured security compliance through rigorous report validation.\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch3 id=\"pt-forta-digital-indonesia\"\u003ePT. Forta Digital Indonesia\u003c/h3\u003e\n\u003ch4 id=\"linux-system-administrator--aug-2019---dec-2019\"\u003eLinux System Administrator \u0026mdash; \u003cem\u003eAug 2019 - Dec 2019\u003c/em\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eSet up and maintained the office network, local development ecosystem, and key infrastructure components like Tenable.SC, Splunk, MongoDB, and RabbitMQ.\u003c/li\u003e\n\u003cli\u003eImplemented Continuous Deployment with Ansible and GitLab CI, while managing product deployments to client environments.\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch3 id=\"pt-mahapatih-sibernusa-teknologi\"\u003e\u003ca href=\"https://mahapatih.id\"\u003ePT Mahapatih Sibernusa Teknologi\u003c/a\u003e\u003c/h3\u003e\n\u003ch4 id=\"full-stack-engineer--mar-2019---aug-2019\"\u003eFull Stack Engineer \u0026mdash; \u003cem\u003eMar 2019 - Aug 2019\u003c/em\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eDeveloped backend API for Sistem Manajemen Kerentanan (Sepatih) and implemented a missing Python SDK for the Tenable.SC API.\u003c/li\u003e\n\u003cli\u003eAutomated deployment processes using Ansible and GitLab CI.\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch3 id=\"binokular-media-utama\"\u003e\u003ca href=\"https://binokular.net\"\u003eBinokular Media Utama\u003c/a\u003e\u003c/h3\u003e\n\u003ch4 id=\"senior-software-engineer--nov-2015---jan-2018\"\u003eSenior Software Engineer \u0026mdash; \u003cem\u003eNov 2015 - Jan 2018\u003c/em\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eBuilt and optimized AI-driven media monitoring systems, including entity extraction, sentiment analysis, and topic classification.\u003c/li\u003e\n\u003cli\u003eDesigned and maintained high-availability database and search clusters, ensuring reliability and scalability.\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch3 id=\"pt-sebangsa-bersama\"\u003e\u003ca href=\"https://sebangsanetwork.com/\"\u003ePT Sebangsa Bersama\u003c/a\u003e\u003c/h3\u003e\n\u003ch4 id=\"backend-engineer--apr-2014---oct-2015\"\u003eBackend Engineer ~ \u003cem\u003eApr 2014 - Oct 2015\u003c/em\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eDeveloped Sebangsa Auth with OAuth 1.0 and maintained Sebangsa Premium API.\u003c/li\u003e\n\u003cli\u003eImplemented a fan-out algorithm for efficient post distribution to followers.\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003cp\u003eDetail pengalaman dan keahlian saya dapat Anda temukan melalui tautan berikut \u003ca href=\"/cv/widnyana-20250300.pdf\"\u003eCV\u003c/a\u003e.\u003c/p\u003e","title":"Hello!"},{"content":"Ini Bagian 6 dari seri Solana Program Lifecycle. Di Bagian 5 kita bahas soal upgrade authority, dari keypair tunggal, multisig Squads, SPL Governance, sampai flag --final yang udah nggak bisa di-undo lagi. Sekarang kita mundur dikit dan liat gambaran besarnya: gimana sih program kamu bergerak dari file di laptop jadi bytecode yang beneran hidup di mainnet.\nJawaban singkatnya: bukan cuma solana program deploy doang. Perintah deploy itu langkah terakhir, bukan keseluruhan proses. Dev yang langsung lompat ke mainnet biasanya nyadarinya pas upload buffer parsial macet jam 3 pagi, atau pas CPI yang sempurna di lokal diam-diam gagal karena akun yang dibutuhkan ternyata nggak pernah ada di mainnet.\nPipeline-nya Progresi standarnya tiga environment: lokal, devnet, mainnet.\nLokal (Surfpool) Devnet Mainnet ──────────────────── ───────────────── ────────────────────── Iterasi tercepat Jaringan nyata Uang nyata SOL palsu SOL palsu SOL nyata State mainnet State devnet State mainnet Tanpa latensi ~400ms per slot ~400ms per slot Cheatcode tersedia Tanpa cheatcode Tanpa cheatcode Testnet? Opsional sih. Solana Foundation pakai itu buat testing validator. Mayoritas dev aplikasi langsung skip aja. Devnet itu checkpoint yang bener-bener penting sebelum mainnet.\nTesting lokal dengan Surfpool solana-test-validator (juga tersedia sebagai solana local-validator di versi CLI yang lebih baru) udah usang. Tool testing lokal yang sekarang adalah Surfpool, di-maintain langsung sama Solana Foundation.\nBedanya yang paling kerasa itu di soal state. solana-test-validator itu jalanin jaringan terisolasi, nggak punya akses ke data akun nyata. Mau ngetes CPI ke Jupiter? Harus clone puluhan akun dan program secara manual dulu sebelum satu test pun bisa jalan. Capek banget. Surfpool itu jaringan yang di-fork secara lazy: dia ngambil data akun mainnet (atau devnet) on-demand pas transaksi kamu ngaksesnya. Akses pertama ke akun yang belum ada di lokal akan trigger fetch data. Akses berikutnya baca dari salinan lokal yang udah di-cache.\nInstall-nya satu baris, terus arahin Solana CLI ke RPC lokal yang jalan di port 8899:\n1 2 3 curl -sL https://run.surfpool.run/ | bash surfpool start solana config set --url localhost Dashboard Studio bisa diakses di http://127.0.0.1:18488 setelah Surfpool jalan.\nBuat project Anchor, Anchor.toml kamu seharusnya udah ngarahin cluster lokal ke http://localhost:8899. Buat test suite Anchor, pakai flag kompatibilitas ini:\n1 surfpool start --legacy-anchor-compatibility Cheatcode itu metode RPC khusus yang Surfpool sediain buat development. Ini nggak ada di jaringan nyata ya.\nsurfnet_setAccount: atur lamports, data, owner, dan flag executable di akun apapun surfnet_setTokenAccount: atur saldo akun SPL token, delegate, dan state surfnet_resetAccount: balikin akun ke state mainnet aslinya Time travel juga tersedia lewat cheatcode (surfnet_timeTravel, surfnet_pauseClock, surfnet_resumeClock) dan lewat UI Studio. Berguna banget buat apapun yang baca Clock::get(): jadwal vesting, lockup, distribusi reward berbasis epoch.\nStudio (di http://127.0.0.1:18488) nampilin detail transaksi, byte akun sebelum dan sesudah tiap instruksi, profil compute unit per instruksi, plus faucet live namanya \u0026ldquo;The Heist\u0026rdquo; yang bisa danai akun apapun dengan token apapun secara instan, tanpa batas rate. Mantap kan.\nBuat fork dari RPC tertentu alih-alih endpoint publik default, pakai --rpc-url:\n1 surfpool start --rpc-url https://your-private-rpc.example.com Endpoint publik default cukup buat testing dengan traffic rendah, tapi ada batas rate-nya. Buat forking mainnet yang lebih berat, pakai RPC privat dari Helius, QuickNode, atau Triton.\nDevnet: ngetes pipeline deployment yang beneran Devnet itu bukan environment lokal lagi ya. Devnet jalanin flow deploy yang sama persis dengan mainnet, artinya dia ngetes edge case yang sama: chunked write, pembuatan buffer account, instruksi aktivasi, penanganan priority fee. Masalah yang cuma muncul di pipeline deploy beneran bakal keliatan di sini, dan untungnya pakai SOL palsu.\nPindah cluster:\n1 solana config set --url devnet Ambil SOL devnet:\n1 solana airdrop 2 Deploy:\n1 2 anchor build solana program deploy ./target/deploy/your_program.so Setelah deploy devnet berhasil, jalanin test suite integrasi lengkap kamu ke devnet. Kalau ada test yang lolos di lokal pakai Surfpool tapi gagal di devnet, bedanya hampir pasti salah satu dari ini:\nAkun yang kamu anggap ada ternyata nggak ada di devnet Logika yang bergantung ke clock atau slot berperilaku beda di timing nyata Priority fee terlalu rendah buat kondisi kemacetan devnet Biasanya orang kelewat ini: devnet di-reset secara berkala. Program yang di-deploy di sana bakal hilang pas reset. Jangan perlakukan state devnet sebagai sesuatu yang permanen.\nBerapa biaya deploy sebenarnya Biaya deploy itu dominan sama rent, bukan fee. Program Account isinya cuma 36 byte data pointer. ProgramData Account yang nyimpen bytecode lengkap, dan di situlah biaya benerannya ada.\nCek minimum rent-exempt buat ukuran apapun:\n1 solana rent \u0026lt;BYTES\u0026gt; Header ProgramData account itu 45 byte. Buat binary program 200KB:\n1 2 solana rent 200045 # Rent-exempt minimum: 1.39644696 SOL Buat program 400KB:\n1 2 solana rent 400045 # Rent-exempt minimum: 2.79261672 SOL SOL itu bukan fee ya. SOL itu dikunci di ProgramData account sebagai rent-exemption. Kalau nanti kamu tutup program, SOL itu dikembalikan ke authority. Kalau kamu jadiin immutable (udah kita bahas di Bagian 5), SOL itu terkunci secara permanen.\nSelama fase upload, deploy juga bikin Buffer account sementara yang ukurannya sama dengan bytecode ditambah header 37 byte. Pas langkah aktivasi, buffer dikosongin dan lamports-nya ditransfer ke ProgramData account, jadi kamu nggak bayar keduanya bareng-bareng. Biaya rent net-nya cuma satu akun.\nSelain rent, deploy juga ngirim puluhan sampai ratusan transaksi (satu per chunk bytecode). Tiap transaksi bayar base fee 5000 lamports. Buat program 400KB dengan kira-kira 450 transaksi, total base fee sekitar 2.250.000 lamports (0,00225 SOL). Priority fee nambah ke biaya itu tergantung kondisi jaringan.\nPriority fee saat deploy Mainnet lebih ramai dari devnet. Transaksi yang lancar di devnet bisa macet atau gagal di mainnet kalau priority fee-nya kebawah.\nPerintah deploy nerima harga compute unit:\n1 2 solana program deploy ./target/deploy/your_program.so \\ --with-compute-unit-price 50000 Nilainya dalam microlamport per compute unit. Transaksi deploy tipikal pakai sekitar 3.000 sampai 5.000 compute unit. Dengan 50.000 microlamport per CU, priority fee per transaksi sekitar 150.000 sampai 250.000 microlamport (0,00000015 sampai 0,00000025 SOL). Buat 450 transaksi, itu nambah kira-kira 0,00007 sampai 0,0001 SOL ke total biaya. Kecil sih secara dolar, tapi cukup buat dorong transaksi melewati antrean tanpa fee.\nPas periode kemacetan tinggi, 50.000 microlamport mungkin kurang. Cek level fee saat ini:\n1 solana fees --url mainnet-beta Atau pakai API priority fee dari provider RPC kamu, yang ngasih estimasi per-persentil berdasar transaksi yang baru saja berhasil.\nBuat flow write-buffer-then-upgrade yang dipakai di setup multisig dan governance, atur harga compute unit di langkah write-buffer juga:\n1 2 solana program write-buffer ./target/deploy/your_program.so \\ --with-compute-unit-price 50000 Fase write punya lebih banyak transaksi daripada langkah aktivasi. Kalau transaksi write macet, kamu bakal dapet buffer yatim yang nahan SOL. Jadi jangan cuma pasang priority fee di deploy aja dong.\nFlag --max-len Udah dibahas di Bagian 4, tapi perlu ditegaskan lagi nih. Pas pertama kali deploy, ProgramData account diukur persis sesuai bytecode saat ini. Upgrade berikutnya yang lebih besar dari ukuran itu bakal gagal kecuali kamu panggil solana program extend dulu, atau kecuali CLI modern yang auto-extend (yang nggak kamu dapet secara otomatis di flow multisig).\nDeploy dengan ruang cadangan:\n1 2 solana program deploy ./target/deploy/your_program.so \\ --max-len 600000 Ini ngalokasiin 600.000 byte meskipun binary saat ini 400.000 byte. Biayanya:\n1 2 solana rent 600045 # Rent-exempt minimum: ~4.18893 SOL dibanding deploy dengan ukuran pas:\n1 2 solana rent 400045 # Rent-exempt minimum: ~2.79262 SOL Kira-kira 1,4 SOL ekstra yang terkunci buat ngindarin transaksi extend di masa depan. Apakah tradeoff ini sepadan, tergantung seberapa sering kamu rencana upgrade dan apakah flow upgrade kamu nyangkut proposal multisig yang butuh langkah extend terpisah.\nVerifikasi pasca-deploy Setelah deploy ke mainnet, pastiin bytecode on-chain cocok dengan yang kamu build di lokal. Pengecekan dasarnya gini:\n1 2 3 4 5 6 # Dump bytecode on-chain solana program dump \u0026lt;PROGRAM_ID\u0026gt; on-chain-dump.so --url mainnet-beta # Bandingkan hash sha256sum on-chain-dump.so sha256sum ./target/deploy/your_program.so Kalau hash-nya cocok, binary yang bener udah di-deploy. Kalau nggak cocok, berarti kamu ke-deploy file yang salah atau build-nya nggak deterministik.\nsolana program dump ngambil bytecode mentah dari ProgramData account, nge-strip header 45 byte, terus nulis byte ELF ke sebuah file. Hasilnya harus identik byte-per-byte dengan binary yang kamu deploy.\nVerifiable build dengan solana-verify Pengecekan SHA-256 ngebuktikan kalau kamu bisa bikin ulang binary yang sama. Tapi ini nggak ngebuktikan kalau orang lain juga bisa, atau kalau source code di repo kamu cocok dengan binary yang di-deploy.\nVerifiable build nyelesain ini lewat proses build deterministik berbasis Docker. Environment build di-pin: toolchain Rust yang sama, toolchain Solana yang sama, dependensi yang sama. Siapapun bisa bikin ulang binary dari commit source yang sama.\nInstall:\n1 cargo install solana-verify Build binary verifiable:\n1 solana-verify build --library-name your_program Ini jalan di dalam container Docker yang di-pin. Binary hasilnya ada di target/deploy/your_program.so.\nAmbil hash binary lokal:\n1 solana-verify get-executable-hash ./target/deploy/your_program.so Ambil hash program on-chain (-um itu singkatan dari mainnet):\n1 solana-verify get-program-hash -um \u0026lt;PROGRAM_ID\u0026gt; Keduanya harus cocok. Kalau nggak, kemungkinan kamu ke-deploy binary yang beda atau kamu jalanin build non-verifiable setelah solana-verify build.\nBuat daftarin verifikasi on-chain biar siapapun bisa verifikasi secara trustless tanpa harus jalanin build sendiri:\n1 2 3 4 solana-verify verify-from-repo \\ --program-id \u0026lt;PROGRAM_ID\u0026gt; \\ -um \\ https://github.com/your-org/your-repo Ini bikin PDA buat program verify yang nyimpen git URL, commit hash, dan argumen build. API verifikasi publik OtterSec baca PDA ini dan bikin status verifikasi bisa di-query dari Solana Explorer dan tooling lainnya. Setelah terdaftar, halaman Explorer program bakal nampilin badge verified.\nYang jadi kendala praktisnya itu waktu build. Build berbasis Docker bisa makan sampai 30 menit di Apple Silicon karena emulasi x86. Di mesin Linux x86 native, jauh lebih cepat. Kalau kamu build di CI, perhitungkan ini dalam perencanaan waktu pipeline ya.\nChecklist pra-mainnet Build dan binary:\nBuild pakai solana-verify build kalau kamu rencana ngirim verified build, bukan anchor build Hash binary sebelum deploy: sha256sum ./target/deploy/your_program.so Cek ukuran binary: ls -lh ./target/deploy/your_program.so Jalanin solana rent \u0026lt;bytes\u0026gt; buat ukuran ProgramData dan pastiin wallet kamu cukup Persiapan cluster:\nPindah ke mainnet: solana config set --url mainnet-beta Konfirmasi wallet deployer dan saldonya: solana address terus solana balance Cek fee saat ini dan pilih nilai --with-compute-unit-price: solana fees --url mainnet-beta Authority:\nTentuin upgrade authority awal sebelum deploy (default-nya keypair deployer) Kalau pakai multisig: siapin alamat Squad PDA buat di-transfer segera setelahnya Jangan biarin hot keypair jadi upgrade authority lebih lama dari yang diperlukan Deploy:\nPakai --max-len kalau kamu perkirain program bakal berkembang Catat alamat buffer kalau deploy gagal di tengah jalan (CLI bakal nyetaknya) Verifikasi:\nKonfirmasi authority: solana program show \u0026lt;PROGRAM_ID\u0026gt; --url mainnet-beta Dump dan hash binary on-chain, bandingin dengan binary lokal (liat \u0026ldquo;Verifikasi pasca-deploy\u0026rdquo; di atas) Kalau pakai verifiable build: jalanin solana-verify verify-from-repo dan konfirmasi hash-nya cocok Transfer authority:\nTransfer ke multisig: solana program set-upgrade-authority \u0026lt;PROGRAM_ID\u0026gt; --new-upgrade-authority \u0026lt;SQUAD_PDA\u0026gt; Konfirmasi transfer: solana program show \u0026lt;PROGRAM_ID\u0026gt; --url mainnet-beta Hal-hal yang sering menjebak developer solana-test-validator ngasih kepercayaan palsu. Dia jalan dalam isolasi. CPI ke protokol nyata gagal karena program dan akunnya emang nggak ada di sana. Surfpool ngekspos kelas kegagalan ini secara lokal sebelum sampai ke devnet atau mainnet.\nWallet deployer butuh lebih banyak SOL dari yang lu kira. Rent buffer ditambah rent ProgramData bisa lebih dari 3 SOL buat program ukuran normal. Tambahin fee transaksi, priority fee, dan margin keamanan. Datang ke mainnet dengan 3 SOL terus program butuh 2,8 SOL, cuma butuh satu gangguan jaringan doang sebelum deploy parsial yang perlu pemulihan.\nPriority fee pengaruhi fase write, bukan cuma aktivasi. Ada ratusan transaksi write. Kalau macet, kamu dapet buffer yatim yang nahan SOL terkunci. Setel --with-compute-unit-price di write-buffer maupun deploy.\nBuild yang kamu tes nggak selalu build yang kamu deploy. Jalanin anchor build setelah solana-verify build bakal nimpa binary verified dengan binary yang nggak deterministik. Kalau niatnya ngirim verified build, deploy binary yang dihasilkan solana-verify build, dan jangan rebuild di antara itu sama perintah deploy.\nTransfer authority itu nggak otomatis. Setelah deploy mainnet pertama kali, upgrade authority adalah keypair deployer. Biarin di situ itu risiko dong. Transfer ke multisig sebelum kamu tutup sesi.\nDevnet dan mainnet bisa berbagi program ID yang sama. Anchor.toml pakai section terpisah buat localnet, devnet, dan mainnet, tapi semuanya bisa ngarah ke file keypair yang sama. Ini disengaja buat project yang mau alamat yang sama di seluruh cluster. Ini juga jadi jebakan kalau kamu keliru soal cluster yang sedang ditarget pas jalanin anchor deploy.\nMengikat semuanya Perintah deploy itu lima detik terakhir dari proses yang butuh perencanaan. Surfpool buat iterasi lokal, devnet buat validasi pipeline end-to-end, mainnet buat produksi. Tiap environment nangkap sesuatu yang nggak bisa ditangkap environment sebelumnya.\nKejutan menit-menit akhir yang paling sering terjadi itu soal SOL. Rent itu biayanya, bukan fee. Program 400KB ngunci 2,79 SOL sebelum satu pun transaksi pengguna jalan. Jalanin solana rent sebelum kamu mulai, bukan setelah upload buffer macet.\nUpgrade authority dimulai sebagai keypair deployer. Transfer sebelum kamu tutup terminal. Bagian 5 bahas apa yang terjadi kalau kamu lupa.\nReferensi Surfpool Documentation - environment testing lokal dan IaC untuk Solana Surfpool CLI Basics, Solana Docs - dokumentasi resmi Solana untuk perintah Surfpool Deploying Programs, Solana Docs - workflow dan flag CLI deploy Verified Builds, Solana Docs - workflow verifiable build dan pendaftaran on-chain solana-verify CLI (Ellipsis Labs) - sumber tool original dan rilis solana-verify CLI (Solana Foundation) - fork yang di-maintain Solana Foundation Program Deployment, Solana Docs - referensi instruksi loader-v3 ","permalink":"https://widnyana.web.id/id/posts/solana/solana-deployment-pipeline-local-to-mainnet/","summary":"\u003cp\u003eIni Bagian 6 dari seri \u003ca href=\"/id/series/solana-program-lifecycle/\"\u003eSolana Program Lifecycle\u003c/a\u003e. Di \u003ca href=\"/id/posts/solana/solana-upgrade-authority-keypair-to-immutable/\"\u003eBagian 5\u003c/a\u003e kita bahas soal upgrade authority, dari keypair tunggal, multisig Squads, SPL Governance, sampai flag \u003ccode\u003e--final\u003c/code\u003e yang udah nggak bisa di-undo lagi. Sekarang kita mundur dikit dan liat gambaran besarnya: gimana sih program kamu bergerak dari file di laptop jadi bytecode yang beneran hidup di mainnet.\u003c/p\u003e\n\u003cp\u003eJawaban singkatnya: bukan cuma \u003ccode\u003esolana program deploy\u003c/code\u003e doang. Perintah deploy itu langkah terakhir, bukan keseluruhan proses. Dev yang langsung lompat ke mainnet biasanya nyadarinya pas upload buffer parsial macet jam 3 pagi, atau pas CPI yang sempurna di lokal diam-diam gagal karena akun yang dibutuhkan ternyata nggak pernah ada di mainnet.\u003c/p\u003e","title":"Dari Lokal ke Mainnet: Pipeline Deployment"},{"content":"Ini bagian 5 dari seri Solana Program Lifecycle. Di Bagian 4 kita udah bahas alur deploy dan upgrade: akun Buffer, chunked writes, dan langkah aktivasi yang nyalin bytecode ke akun ProgramData. Setiap instruksi upgrade ngecek satu hal sebelum nerusin: siapa yang boleh ngejalaninnya.\nSatu hal itu adalah field upgrade_authority_address: 33 byte di akun ProgramData yang ngontrol siapa bisa ganti bytecode program. Tulisan ini bakal nge-track siklus hidup lengkap field itu, dari keypair yang nge-deploy program sampe flag --final yang irreversibel dan nulis None, ngebekuin program selamanya.\nField upgrade authority Kamu masih inget kan dari Bagian 3 kalau akun ProgramData nyimpen bytecode plus beberapa metadata? Layout-nya, diserialisasi pake bincode:\n[0..4] u32 discriminator = 3 (varian ProgramData) [4..12] u64 slot (slot deployment/upgrade terakhir) [12..13] u8 option discriminant (0 = None, 1 = Some) [13..45] Pubkey upgrade_authority_address (hanya ada jika option = 1) [45..] byte program mentah (ELF bytecode) Tiga puluh tiga byte ngontrol semuanya. Byte 12 adalah flag option. Kalau nilainya 1, byte 13 sampe 44 nyimpen public key authority. Kalau nilainya 0, berarti nggak ada authority. Programnya immutable.\nProgram BPF Loader Upgradeable (BPFLoaderUpgradeab1e11111111111111111111111) ngecek field ini sebelum ngejalanin instruksi-instruksi berikut:\nUpgrade (discriminator 1): ganti bytecode. Butuh tanda tangan authority. Ditolak kalau field-nya None. SetAuthority (discriminator 4): ubah authority. Butuh tanda tangan authority yang sekarang. Authority baru nggak usah nandatangani. SetAuthorityChecked (discriminator 7): sama kayak SetAuthority, tapi authority baru juga harus nandatangani. Close (discriminator 5): tutup akun ProgramData dan klaim balik lamports. Butuh tanda tangan authority. Ditolak kalau field-nya None. Saat byte 12 bernilai 0, nggak ada satu pun instruksi di atas yang bisa jalan. Nggak ada signer yang valid. Program dibekuin secara permanen.\nNgecek authority saat ini CLI kasih jawaban cepat:\n1 solana program show \u0026lt;PROGRAM_ID\u0026gt; Output-nya kira-kira gini:\nProgram Id: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU Program Data Address: 5UUJQbPM6X7m2cawNGm8F95Z3LeLRBCE4F8xBrfTDdaF Authority: Ds3Z9QxexjxtkWvhLnbmGzAuyxZrhk8VmN8cc8P2R5eS Last Deployed In Slot: 284531193 Baris Authority nunjukin upgrade authority yang sekarang. Kalau program udah immutable, dia nunjukin:\nAuthority: none Untuk mainnet, tinggal tambah flag cluster:\n1 solana program show \u0026lt;PROGRAM_ID\u0026gt; --url mainnet-beta Mau liat semua program beserta authority-nya? Gini:\n1 solana program show --programs Pengecekan programatik. Turunkan alamat ProgramData dari program ID, terus baca data akunnya:\n1 2 3 4 5 6 7 8 9 10 11 use solana_sdk::pubkey::Pubkey; use solana_sdk::bpf_loader_upgradeable; let (program_data_address, _) = Pubkey::find_program_address( \u0026amp;[program_id.as_ref()], \u0026amp;bpf_loader_upgradeable::id(), ); // Fetch akunnya, lalu baca byte [12..45] // Byte 12: 0 = None (immutable), 1 = Some // Byte 13-44: pubkey authority (saat byte 12 == 1) Derivasinya deterministik: program ID yang sama selalu map ke alamat ProgramData yang sama. Kamu bisa verifikasi secara lokal tanpa musti akses jaringan.\nTransfer upgrade authority Transfer dasar pake SetAuthority:\n1 2 solana program set-upgrade-authority \u0026lt;PROGRAM_ID\u0026gt; \\ --new-upgrade-authority \u0026lt;NEW_PUBKEY\u0026gt; Di balik layar, ini bikin instruksi dengan byte discriminator 4. Authority yang sekarang nandatangani. Authority baru cuma jadi referensi akun di dalam instruksi, tapi TIDAK perlu nandatangani.\nKalimat terakhir itulah bahayanya. Salah ketik di alamat berarti kamu kirim authority ke public key acak yang nggak dikontrol siapa pun. Programnya emang nggak immutable, tapi nggak ada yang bisa ngupgrade juga dong. Buatnya sama aja kayak ngebakar authority, cuma ProgramData masih nunjukin Some(wrong_address) bukan None.\nVarian yang lebih aman adalah SetAuthorityChecked (byte discriminator 7):\n1 2 3 solana program set-upgrade-authority \u0026lt;PROGRAM_ID\u0026gt; \\ --new-upgrade-authority \u0026lt;NEW_PUBKEY\u0026gt; \\ --new-upgrade-authority-signer \u0026lt;PATH_TO_KEYPAIR\u0026gt; Di sini authority baru juga nandatangani. Ini ngebuktikan kalau key authority baru beneran punya private key yang cocok. Kalau kamu salah ketik alamatnya, instruksi langsung gagal karena keypair yang salah nggak bisa ngasilin tanda tangan yang valid.\nSelalu pake SetAuthorityChecked kalau transfer ke keypair. Kalau transfer ke PDA (multisig, governance), SetAuthority satu-satunya opsi karena PDA nggak punya private key buat nandatangani.\nKamu bisa transfer ke pubkey valid apa aja: keypair lain, PDA multisig Squads, PDA governance, atau bahkan 11111111111111111111111111111111 (System Program, yang nggak punya private key, efektif ngebakar authority tanpa bikin program jadi fully immutable).\nPola progresi Sebagian besar proyek yang serius ngikutin jalur kematangan buat upgrade authority mereka. Setiap transisi nyempitin siapa yang bisa bertindak: dari satu orang, ke anggota M-of-N, ke voter berbobot token, ke nggak ada seorang pun. Setiap transisi bersifat satu arah dalam praktiknya.\nTahap 1: Keypair (pengembangan) Keypair deployer pegang authority. Ini default setelah solana program deploy. Iterasi cepat. Satu perintah buat ngirim build baru:\n1 solana program deploy ./target/deploy/program.so --program-id \u0026lt;KEYPAIR\u0026gt; Single point of failure. Kalau keypair-nya ke-compromise, penyerang bisa ganti bytecode program dengan apa aja. Nggak ada delay, nggak ada proses approval, nggak ada rollback.\nCocok buat devnet dan testnet awal sih. Tapi nggak cocok buat apapun yang nangani nilai riil di mainnet.\nTahap 2: Multisig (peluncuran produksi) Transfer authority ke PDA multisig Squads. Multisig ngelakuin threshold approval M-of-N sebelum instruksi apapun bisa dijalanin sebagai authority.\nAlur setup-nya:\nBuat Squad lewat UI Squads atau SDK Konfigurasi anggota dan threshold (misalnya, 3-of-5) Dapetin alamat PDA Squad Transfer upgrade authority: 1 2 solana program set-upgrade-authority \u0026lt;PROGRAM_ID\u0026gt; \\ --new-upgrade-authority \u0026lt;SQUAD_PDA_ADDRESS\u0026gt; Alur upgrade lengkap di bawah kontrol multisig dibahas di bagian end-to-end di bawah.\nApa yang kamu hilangin: kecepatan. Upgrade butuh quorum. Satu orang nggak bisa ngirim hotfix jam 3 pagi tanpa ngumpulin cukup anggota buat penuhin threshold. Respons darurat jadi lebih lambat.\nApa yang kamu dapet: nggak ada kompromi key tunggal yang bisa ganti program. Penyerang harus compromise M dari N anggota secara barengan.\nTahap 3: Governance (desentralisasi) Transfer authority ke PDA SPL Governance yang terikat ke Realm. Voting berbobot token nentuin apakah upgrade dilakuin.\nAlur setup-nya:\nBuat Realm dengan community token mint pake Realms Buat akun ProgramGovernance buat program tersebut (upgrade authority yang sekarang harus nandatangani) PDA Governance jadi upgrade authority Buat upgrade: buat proposal, pemegang token voting, kalau voting lolos ada masa timelock, baru eksekusi Apa yang kamu hilangin: kontrol tim. Komunitas yang nentuin. Eksekusi lebih lambat karena periode voting dan timelock udah built-in di konfigurasi governance. Setup tipikal mungkin butuh periode voting 3 hari ditambah timelock 1 hari sebelum bisa eksekusi.\nDistribusi token itu penting banget. Kalau satu entitas pegang 51% token governance, sistemnya secara efektif terpusat cuma with langkah ekstra. Governance cuma sedesentralisasi distribusi token-nya.\nApa yang kamu dapet: deliberasi publik. Setiap proposal upgrade keliatan on-chain. Pemegang token bisa nolak perubahan yang mereka nggak setuju. Tim nggak bisa ngirim upgrade kejutan.\nTahap 4: Immutable (final) 1 solana program set-upgrade-authority \u0026lt;PROGRAM_ID\u0026gt; --final Ini map ke SetAuthority tanpa akun authority baru. Loader nulis 0 ke byte 12 akun ProgramData. 32 byte yang nyimpen pubkey authority di-zero-in.\nNggak bisa dibatalkan. Nggak ada instruksi yang bisa balikin authority. Instruksi SetAuthority butuh authority yang sekarang buat nandatangani. Saat field-nya None, nggak ada signer yang valid. Loader nolak instruksinya.\nApa yang kamu hilangin: seluruh kemampuan upgrade. Perbaikan bug mustahil dilakuin. Kalau kerentanan kritis ketemu setelah jadi immutable, nggak ada jalur pemulihan lewat program ID asli. Satu-satunya opsi adalah deploy program baru di alamat baru dan migrasi semua pengguna.\nApa yang kamu dapet: jaminan kepercayaan absolut. Pengguna bisa verifikasi kalau bytecode nggak akan pernah berubah. Nggak ada kompromi key, nggak ada voting governance, nggak ada approval multisig yang bisa ubah program. Kode on-chain adalah kode yang dijalanin, secara permanen.\nRingkasan progresi Transisi Apa yang hilang Keypair ke Multisig Kecepatan. Upgrade butuh quorum. Multisig ke Governance Kontrol tim. Komunitas yang nentuin. Eksekusi lebih lambat. Governance ke Immutable Seluruh kemampuan upgrade. Nggak ada perbaikan bug. Selamanya. Kapan ngelakuin setiap transisi Keypair ke Multisig: saat peluncuran mainnet, atau lebih awal lagi. Begitu dana pengguna berisiko, authority key tunggal itu udah kelalaian. Siapin multisig sebelum deployment mainnet, bukan setelahnya.\nMultisig ke Governance: saat protokol udah stabil, udah diaudit, dan komunitas sebaiknya punya suara dalam upgrade. Kalau fungsionalitas inti masih sering berubah, governance cuma nambah latensi tanpa nambah nilai.\nGovernance ke Immutable: setelah audit, pengujian ketat, dan periode upgrade yang dikontrol governance tanpa insiden. Infrastruktur fondasional (program token, program sistem) masuk kategori ini. Apapun yang seharusnya dipercaya pengguna tanpa syarat.\nJangan skip tahap. Langsung lompat ke immutable berarti kamu nggak bisa benerin bug. Langsung ke governance berarti pemegang token yang milih patch darurat, itu kebanyakan terlalu lambat buat penanganan insiden. Progresi harus sesuai kematangan protokol kamu.\nContoh nyata di mainnet Multisig Squads: sebagian besar program DeFi di mainnet yang masih bisa di-upgrade pake Squads atau multisig serupa. Authority-nya PDA yang butuh approval quorum. Tim tetep punya kontrol tapi didistribusiin ke beberapa signer.\nSPL Governance: Marinade Finance pake SPL Governance buat upgrade program. Pemegang token voting pada proposal upgrade. Tim nggak bisa ngirim perubahan secara sepihak.\nProgram immutable: Token Program (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA) dan Associated Token Account Program (ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL) udah dibekuin. Upgrade authority mereka None. Ini infrastruktur fondasional. Bayangin kalau mereka punya authority key yang aktif, setiap token di Solana bakal bergantung ke keamanan satu key itu.\nAlur upgrade dengan Squads (end to end) Buat tim yang pake Squads, siklus upgrade lengkapnya gini nih:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 # 1. Build versi baru secara lokal anchor build # 2. Tulis bytecode yang sudah dikompilasi ke akun buffer solana program write-buffer ./target/deploy/program.so # Output: Buffer: \u0026lt;BUFFER_ADDRESS\u0026gt; # 3. Transfer authority buffer ke PDA Squad # Tanpa langkah ini, multisig tidak bisa menggunakan buffer solana program set-buffer-authority \u0026lt;BUFFER_ADDRESS\u0026gt; \\ --new-buffer-authority \u0026lt;SQUAD_PDA\u0026gt; # 4. Dapatkan hash buffer untuk verifikasi solana-verify get-buffer-hash \u0026lt;BUFFER_ADDRESS\u0026gt; # Bagikan hash ini ke semua anggota multisig # 5. Ajukan upgrade di Squads # Proposal berisi instruksi loader-v3 Upgrade: # - Akun ProgramData (writable) # - Akun Program # - Akun Buffer (writable) # - Akun Spill (menerima sisa lamports) # - Rent sysvar # - Clock sysvar # - Authority = PDA Squad (menandatangani via CPI) # 6. Anggota memverifikasi hash buffer cocok dengan build lokal mereka # anchor build \u0026amp;\u0026amp; solana-verify get-executable-hash ./target/deploy/program.so # Jika hash cocok, setujui. # 7. Setelah threshold terpenuhi, eksekusi proposal # Squads melakukan CPI ke instruksi Upgrade BPF Loader # Buffer dikosongkan dan dipotong. Bytecode program diganti. # Versi baru aktif di slot berikutnya. Langkah 3 itu yang paling sering kelupaan. Kalau kamu nggak transfer authority buffer ke PDA Squad sebelum ajukan proposal, instruksi upgrade bakal gagal karena authority buffer (wallet deployer kamu) nggak cocok sama authority program (PDA Squad). Loader ngecek keduanya dan nolak kalau nggak cocok.\nHal yang sering menjebak developer Transfer ke alamat yang salah itu katastrofik dengan SetAuthority. Alamat baru nggak nandatangani. Nggak ada validasi bahwa ada yang pegang private key-nya. Satu karakter salah ketik di pubkey berarti authority hilang. Selalu pake SetAuthorityChecked kalau authority baru itu keypair.\nKamu nggak bisa batalkan --final. Nggak ada pemulihan. Nggak ada override admin. Nggak ada instruksi \u0026ldquo;gue salah nih\u0026rdquo;. Instruksi SetAuthority butuh authority yang sekarang buat nandatangani. Saat authority yang sekarang None, nggak ada signer yang valid dan instruksi ditolak. Ini emang sengaja didesain begitu.\nPDA multisig atau governance nggak punya private key. Satu-satunya cara buat bertindak sebagai authority adalah lewat proses approval program tersebut. Buat Squads, berarti musti penuhin threshold. Buat SPL Governance, berarti harus lolos voting dan nunggu timelock. Kamu nggak bisa ekstrak private key dari PDA karena memang nggak ada.\nDistribusi token governance itu vektor serangan. Seekor ikan paus yang ngakumulasi 51% token governance bisa ngelolosin proposal upgrade apapun. Governance berbobot token cuma sedesentralisasi distribusi token-nya. Kalau satu entitas pegang mayoritas, teater governance cuma nambah latensi tanpa nambah keamanan.\nSetelah --final, kamu juga nggak bisa Close program. Instruksi Close butuh authority buat nandatangani, sama kayak Upgrade dan SetAuthority. Saat authority None, program dan akun ProgramData-nya ada selamanya. SOL rent yang terkunci di akun ProgramData nggak bisa diklaim balik. Buat program besar, itu bisa nyampe 2 sampe 3 SOL.\nMengikat semuanya Bayangin semua bergerak. Kamu deploy sebuah program. Keypair deployer pegang upgrade authority. Byte 12 di akun ProgramData bernilai 1, dan byte 13 sampe 44 nyimpen public key keypair itu. Satu orang bisa ganti bytecode.\nKamu transfer authority itu ke multisig Squads. Sekarang butuh approval M-of-N buat upgrade. Kamu pindahin ke SPL Governance. Sekarang butuh voting berbobo token. Setiap langkah nyempitin siapa yang bisa bertindak, dan setiap langkah sulit dibatalkan.\nTerus kamu jalanin --final. Loader nulis 0 ke byte 12. Field authority 32-byte di-zero-in. Nggak ada instruksi yang bisa balikin. Program dibekuin secara permanen. Nggak ada kompromi key, nggak ada voting governance, nggak ada approval multisig yang bisa ubah lagi.\nProgresinya cenderung sesuai tingkat kematangan protokol. Keypair pas ngembangin. Multisig pas peluncuran mainnet. Governance buat desentralisasi. Immutable buat infrastruktur yang seharusnya nggak pernah berubah. Jangan skip tahap.\nSetAuthorityChecked itu ada karena dulu ada orang salah ketik alamat transfer. Satu salah ketik di SetAuthority sama permanennya dengan --final.\nBagian 6 bahas pipeline deployment lengkap dari pengujian lokal sampe mainnet.\nReferensi Solana Program Deployment Docs - referensi instruksi loader-v3 dan mekanisme upgrade solana-sdk/loader-v3-interface instruction.rs - discriminator instruksi dan layout akun Squads Protocol - alur kerja upgrade multisig dan praktik keamanan terbaik SPL Governance - governance on-chain untuk upgrade program solana-verify - verifikasi hash buffer untuk build yang bisa diverifikasi SIMD-0432: Loader v3 Reclaim Closed Program - perilaku instruksi close untuk program immutable ","permalink":"https://widnyana.web.id/id/posts/solana/solana-upgrade-authority-keypair-to-immutable/","summary":"\u003cp\u003eIni bagian 5 dari seri \u003ca href=\"/id/series/solana-program-lifecycle/\"\u003eSolana Program Lifecycle\u003c/a\u003e. Di \u003ca href=\"/id/posts/solana/solana-program-deploy-upgrade-buffer/\"\u003eBagian 4\u003c/a\u003e kita udah bahas alur deploy dan upgrade: akun Buffer, chunked writes, dan langkah aktivasi yang nyalin bytecode ke akun ProgramData. Setiap instruksi upgrade ngecek satu hal sebelum nerusin: siapa yang boleh ngejalaninnya.\u003c/p\u003e\n\u003cp\u003eSatu hal itu adalah field \u003ccode\u003eupgrade_authority_address\u003c/code\u003e: 33 byte di akun ProgramData yang ngontrol siapa bisa ganti bytecode program. Tulisan ini bakal nge-track siklus hidup lengkap field itu, dari keypair yang nge-deploy program sampe flag \u003ccode\u003e--final\u003c/code\u003e yang irreversibel dan nulis \u003ccode\u003eNone\u003c/code\u003e, ngebekuin program selamanya.\u003c/p\u003e","title":"Upgrade Authority: Dari Keypair hingga Immutable"},{"content":"Ini Bagian 4 dari seri Solana Program Lifecycle. Di Bagian 3 kita udah bahas kalau satu program itu ternyata dua akun: Program Account 36-byte yang nge-point ke ProgramData Account yang nyimpen bytecode aslinya. Sekarang kita masuk ke bagian yang lebih seru: gimana kedua akun itu dibikin dan diganti.\nVersi singkatnya: ada akun ketiga yang ikut campur. Akun sementara. Dia cuma ada selama deploy atau upgrade berlangsung, dan kalau ada yang nggak beres, dia bisa ngendon di chain nahan SOL lu sampai lu sadar dan klaim balik.\nAkun ketiga itu adalah Buffer.\nKenapa deploy bukan satu transaksi Transaksi Solana punya batas keras 1232 byte. Itu sudah termasuk signature, message header, daftar akun, dan data instruksi. Setelah dikurangi overhead, payload instruksi yang bisa dipakai cuma sekitar 800 sampai 1000 byte per transaksi.\nProgram Anchor tipikal itu ukurannya 200KB sampai 500KB bytecode ELF yang udah dikompilasi. Hitung-hitungannya nggak bakal cocok. Nggak mungkin muat nge-load bytecode program beneran di satu transaksi.\nJadi Solana mutusin bagi kerjanya ke banyak transaksi. Bytecode ditaruh dulu di area penampungan, baru diaktifin di langkah terakhir. Area penampungan itulah Buffer Account.\nAlurnya sama baik untuk deploy pertama kali maupun upgrade. Cuma langkah aktivasi doang yang beda.\nBuild file .so (offchain) | v Buat akun Buffer onchain | v Tulis bytecode dalam potongan (banyak transaksi) | v Aktivasi: baik DeployWithMaxDataLen atau Upgrade | v Buffer dikosongkan, lamports diteruskan Setiap potongan itu transaksi terpisah. Setiap transaksi bayar fee. solana program deploy sih nyembunyiin semua ini dari lu, tapi sebenarnya dia jalanin puluhan sampai ratusan transaksi atas nama lu.\nBuffer account, secara detail Buffer account itu cuma akun biasa, punya program BPF Loader Upgradeable, dengan layout tertentu. Ingat tabel discriminator dari Bagian 3:\nNilai Varian 1 Buffer 2 Program 3 ProgramData Sebuah Buffer punya header 37-byte diikuti byte program mentah:\n[0..4] u32 discriminator = 1 [4..5] u8 option (1 = Some, 0 = None) [5..37] Pubkey authority_address (ada jika option = 1) [37..] byte program mentah (diunggah dalam potongan) Field authority_address itu gerbangnya. Cuma buffer authority yang bisa nulis byte tambahan ke buffer, ngerubah buffer authority, atau make buffer buat deploy atau upgrade program. Kalau lu bikin buffer terus tinggal pergi, nggak ada orang lain yang bisa make buffer itu. Bytecode di dalamnya udah terkunci ke authority lu.\nBuffer bayar rent buat ukuran penuhnya. Buffer 400KB nahan sekitar 2.79 SOL dalam lamport rent-exempt. SOL itu punya lu sih, tapi terkunci di buffer sampai langkah aktivasi ngosongin itu (jalur sukses) atau lu nutup buffer secara manual (kalau gagal).\nAlur deploy lengkap, instruksi per instruksi Saat lu jalanin solana program deploy ./target/deploy/program.so, CLI ngejalanin urutan ini. Instruksi loader-v3 yang relevan didokumentasikan di solana_loader_v3_interface::instruction.\nLangkah 1: Buat akun Buffer.\nCLI manggil System Program buat alokasi akun kosong, ukurannya 37 + max_data_len byte, didanai sampai rent-exempt. Terus manggil InitializeBuffer di loader, yang nulis discriminator dan authority ke 37 byte pertama.\nSystem Program: CreateAccount -\u0026gt; akun kosong, dimiliki oleh loader v3 Loader v3: InitializeBuffer -\u0026gt; ngatur discriminator dan authority Langkah 2: Tulis bytecode dalam potongan.\nCLI manggil Write berkali-kali. Setiap instruksi Write nerima offset sama potongan byte, terus nyalin ke buffer di offset tersebut. Setiap transaksi bawa satu instruksi Write dengan payload sebanyak yang muat di batas transaksi 1232 byte.\nBuat program 400KB dengan kira-kira 900 byte payload per transaksi, itu sekitar 450 transaksi. CLI ngirimnya paralel kalau bisa, ngulang kalau gagal, sampai setiap byte berhasil mendarat.\nNah, ini bagian yang lama. Ini juga bagian yang paling sering gagal kalau jaringan lagi padet. Kalau RPC ngedrop beberapa transaksi ini, buffer bakal berakhir dengan lubang di dalamnya, dan langkah aktivasi bakal nolak bytecode tersebut.\nLangkah 3: Aktivasi dengan DeployWithMaxDataLen.\nSetelah buffer penuh, CLI ngirim satu transaksi terakhir dengan instruksi DeployWithMaxDataLen. Instruksi ini ngerjain beberapa hal secara atomik:\nBikin akun Program di alamat program ID. Bikin akun ProgramData di PDA yang diturunkan. Baca bytecode dari buffer, verifikasi ELF, dan salin ke akun ProgramData. Atur upgrade_authority_address akun ProgramData ke siapa pun yang nandatangani. Tandai akun Program sebagai executable: true. Kosongin akun Buffer dan potong datanya. Lamports buffer nutup rent buat akun ProgramData baru. Setelah transaksi tunggal ini mendarat, program udah aktif dong. Transaksi apa pun di slot berikutnya bisa manggil dia.\nAkun-akun yang disentuh sama DeployWithMaxDataLen:\n0. [writable, signer] Payer untuk akun ProgramData baru 1. [writable] Akun ProgramData baru 2. [writable] Akun Program baru 3. [writable] Buffer (tempat bytecode diunggah) 4. [] Rent sysvar 5. [] Clock sysvar 6. [] System program 7. [signer] Upgrade authority program Authority buffer harus cocok sama upgrade authority yang nandatangani deploy. Loader ngecek ini dan nolak deploy kalau beda. Ini yang ngehalang penyerang bikin buffer atas nama lu terus nyuruh lu deploy barangnya.\nAlur upgrade lebih pendek Upgrade program yang udah ada itu make ulang Langkah 1 dan 2 dari alur deploy, persis sama. Satu-satunya perbedaan cuma di langkah aktivasi.\nDaripada DeployWithMaxDataLen, CLI ngirim Upgrade. Dari sumber runtime:\nSaat UpgradeableLoaderInstruction::Upgrade diproses, runtime memverifikasi bahwa akun Program writable dan dimiliki oleh loader-v3, memverifikasi bahwa akun Buffer berisi state Buffer dengan authority yang benar, memverifikasi bahwa upgrade_authority_address akun ProgramData cocok dan bukan None, memverifikasi bahwa program belum di-deploy di slot saat ini, memuat dan memverifikasi byte ELF baru dari buffer, menyalin bytecode baru dari buffer ke akun ProgramData dan meng-nol-kan byte sisanya, mendanai akun ProgramData sampai rent-exempt, mengosongkan akun buffer dan memotong datanya.\nVersi baru langsung berlaku di slot berikutnya (deployment_slot + 1).\nAkun Program nggak berubah. Pointer programdata_address nggak berubah. Cuma bytecode di dalam akun ProgramData yang diganti. Semua yang nge-referensikan program ID sebelum upgrade tetap jalan setelahnya.\nInstruksi Upgrade butuh akun keempat yang nggak ada di versi deploy: spill account. Lamports buffer pertama-tama nambah dana akun ProgramData sampai rent-exempt, dan sisanya mendarat di spill account. CLI ngatur defaultnya ke wallet deployer, jadi SOL-nya balik lagi, cuma lewat beberapa lompatan.\nTiga batasan yang harus lu inget:\nBuffer authority harus cocok sama upgrade authority program saat ini. Loader nolak upgrade kalau beda. Ini kenapa lu nggak bisa upgrade program orang lain meskipun bisa bikin buffer dengan bytecode mereka. Lu nggak bisa upgrade dua kali di slot yang sama. Pengecekan slot (clock.slot != slot) ngehalang ini. Kalau nyoba, lu bakal kena error dan harus nunggu slot berikutnya. Bytecode baru harus muat di akun ProgramData yang ada. Kalau upgrade lu lebih besar dari max_data_len awal, lu harus perbesar program dulu pake solana program extend, yang butuh rent tambahan. Kenapa upload dipisahin dari aktivasi Pola buffer kelihatannya kayak upacara tambahan yang ribet, tapi sebenarnya dia nyelesain tiga masalah nyata.\nAtomisitas. Langkah aktivasi itu satu transaksi. Entah program berakhir dengan bytecode baru yang lengkap, atau nggak sama sekali. Nggak pernah ada momen di mana program punya setengah bytecode baru dan setengah yang lama. Buffer nge-akumulasi semua penulisan tanpa nyentuh program yang aktif, terus commit dalam satu tembakan.\nMultisig dan governance. Pihak yang ngunggah bytecode dan pihak yang ngotorisasi deploy nggak harus sama. Seorang developer bisa nulis buffer pake hot wallet, transfer buffer authority ke multisig, terus minta multisig nyelesaiin instruksi Upgrade. Beginilah cara tim yang pake Squads atau SPL Governance ngirim upgrade.\nAlurnya kayak gini nih:\n1 2 3 4 5 6 7 8 9 10 11 # Developer mengunggah bytecode ke buffer solana program write-buffer ./target/deploy/program.so # Output: Buffer: \u0026lt;BUFFER_ADDRESS\u0026gt; # Developer mentransfer buffer authority ke multisig solana program set-buffer-authority \u0026lt;BUFFER_ADDRESS\u0026gt; \\ --new-buffer-authority \u0026lt;MULTISIG_ADDRESS\u0026gt; # Anggota multisig mengusulkan dan menyetujui transaksi yang berisi # instruksi loader-v3 Upgrade dengan buffer ini Setelah multisig nandatangani upgrade, program ke-update dan lamports buffer ngalir ke spill address yang ditentuin proposal. Para penandatangan multisig nggak perlu ngurus bytecode mentah sendiri. Mereka cuma verifikasi hash buffer sebelum approve.\nBuat build yang bisa diverifikasi, perintah solana-verify get-buffer-hash \u0026lt;BUFFER_ADDRESS\u0026gt; ngasih anggota multisig cara buat konfirmasi kalau buffer onchain cocok sama bytecode dari build yang diketahui.\nIsolasi kegagalan. Kalau unggahan gagal di tengah jalan, program yang aktif nggak terpengaruh sama sekali. Buffer sih rusak, tapi akun ProgramData yang ada nggak tersentuh. Bayangin kalau modelnya potongan ditulis langsung ke akun ProgramData: unggahan parsial bakal nyisain program aktif dalam keadaan rusak. Berabe kan?\nSaat deploy gagal (dan itu emang sering terjadi) Deploy di dunia nyata gagal lebih sering daripada yang diakui dokumentasi. Penyebab paling umum: kemacetan RPC, SOL nggak cukup di wallet deployer, dan masalah jaringan sementara pas fase penulisan berpotongan.\nSaat deploy gagal di tengah jalan, lu bakal berakhir di salah satu dari tiga keadaan ini:\nKeadaan 1: Buffer udah dibuat tapi penulisan gagal. Lu punya akun onchain yang nahan SOL rent dengan bytecode parsial atau bahkan tanpa bytecode sama sekali. CLI biasanya nyetak sesuatu kayak:\nError: Data writes to account failed: Custom program error To recover the buffer, run `solana-keygen recover` and then `solana program deploy --buffer \u0026lt;RECOVERED_BUFFER\u0026gt;` Keadaan 2: Semua penulisan berhasil tapi DeployWithMaxDataLen gagal. Buffer udah lengkap tapi aktivasi nggak pernah terjadi. Jalur pemulihannya sama aja.\nKeadaan 3: Deploy sebenarnya berhasil tapi CLI crash sebelum nglapor. Program udah aktif. Coba jalanin solana program show \u0026lt;PROGRAM_ID\u0026gt; buat konfirmasi.\nBuat Keadaan 1 dan 2, lu punya dua opsi nih.\nOpsi A: Lanjutin dari buffer yang udah ada. CLI nyetak recovery seed phrase pas bikin buffer. Pake solana-keygen recover buat bikin ulang keypair buffer, terus kembaliin ke perintah deploy:\n1 2 3 4 5 6 solana-keygen recover -o recovered-buffer.json # masukkan seed phrase yang dicetak oleh deploy yang gagal solana program deploy ./target/deploy/program.so \\ --buffer recovered-buffer.json \\ --program-id \u0026lt;PROGRAM_KEYPAIR\u0026gt; CLI cukup pintar buat skip potongan yang udah ditulis dan cuma ngisi yang hilang. Ini jalur yang murah sih. Lu cuma bayar penulisan yang nggak mendarat di percobaan pertama.\nOpsi B: Tutup buffer dan mulai dari awal. Kalau buffer udah terlalu rusak, atau kalau lu cuma pengen mulai bersih, tutup aja:\n1 2 3 4 5 6 7 8 # Daftar semua buffer yang dimiliki authority kamu solana program show --buffers # Tutup satu buffer tertentu solana program close \u0026lt;BUFFER_ADDRESS\u0026gt; # Atau tutup semuanya sekaligus solana program close --buffers Ini ngosongin SOL rent balik ke wallet lu. Setiap buffer biasanya nahan 2 sampai 3 SOL buat program ukuran normal, jadi biarin mereka bertebaran di chain itu mubazir banget.\nBiaya percobaan ulang yang bersih adalah rent buat buffer baru ditambah fee transaksi buat penulisan. Buat program 400KB dengan 450 penulisan berpotongan di fee prioritas tipikal, itu kira-kira 0.02 SOL dalam fee di atas rent yang dikunci terus dikembalikan.\nFlag --max-len dan extend Saat lu deploy pertama kali, akun ProgramData dibikin dengan ukuran tetap. Ukurannya berasal dari salah satu dua sumber:\nFlag --max-len yang dikasih ke solana program deploy, kalau diatur. Ukuran yang cukup buat nampung bytecode saat ini, kalau --max-len nggak disertakan. Ukuran ini jadi batas atas buat upgrade in-place selanjutnya. Kalau bytecode lu tumbuh melewatinya, upgrade gagal dengan account data too small for instruction kecuali akun di-gedein dulu.\nVersi CLI Solana modern otomatis ngeperbesar akun ProgramData selama redeploy kalau perlu, jadi nggak bakal kena kegagalan itu. Tapi alur multisig yang bikin instruksi Upgrade secara manual nggak punya safety net ini, jadi mereka harus manggil solana program extend sendiri sebelum upgrade bisa mendarat.\nLu bisa perbesar secara manual pake perintah solana program extend:\n1 solana program extend \u0026lt;PROGRAM_ID\u0026gt; 10000 Ini nambah 10000 byte kapasitas ke akun ProgramData dan bayar rent tambahan dari wallet deployer. Nggak ada perintah buat ngecilin balik. ProgramData cuma bisa tumbuh, nggak bisa nyusut.\nPola umum buat proyek serius: deploy dengan nilai --max-len yang besar (misalnya, 2x binary saat ini) supaya upgrade di masa depan punya ruang tanpa perlu langkah extend. Tarifnya adalah ngunci SOL rent ekstra di awal. Imbalannya adalah upgrade jadi satu transaksi, bukan dua. Pilihan lu.\nVerifikasi alurnya sendiri Lu bisa saksikan setiap langkah ini terjadi di devnet. Jalanin deploy dengan output verbose dan lu bakal ngelihat buffer dibikin, penulisan berpotongan, dan aktivasi akhir:\n1 solana program deploy ./target/deploy/program.so --verbose Atau pecah jadi langkah-langkah manual buat ngelihat buffer secara terpisah:\n1 2 3 4 5 6 7 8 9 10 11 12 # Langkah 1 + 2: buat buffer dan tulis semua potongan, tapi jangan aktifkan solana program write-buffer ./target/deploy/program.so # Output: Buffer: \u0026lt;BUFFER_ADDRESS\u0026gt; # Periksa buffer solana account \u0026lt;BUFFER_ADDRESS\u0026gt; solana program show \u0026lt;BUFFER_ADDRESS\u0026gt; # Langkah 3: aktivasi dengan deploy dari buffer solana program deploy --buffer \u0026lt;BUFFER_ADDRESS\u0026gt; \\ --program-id \u0026lt;PROGRAM_KEYPAIR\u0026gt; Setelah deploy, jalanin solana program show --buffers lagi. Buffer udah ilang. Lamports-nya ditransfer selama aktivasi, dan loader motong datanya jadi nol.\nApa yang sering bikin developer kaget Program ID yang ditutup nggak bisa dipake ulang. Kalau lu jalanin solana program close \u0026lt;PROGRAM_ID\u0026gt;, program ID itu dipensiunkan secara permanen. Nyoba deploy program baru di alamat yang sama bakal gagal dengan Program \u0026lt;ID\u0026gt; has been closed, use a new Program Id. Ini disengaja. Ini ngehalang program ID lama, yang mungkin di-hardcode contract lain, dari diam-diam diganti bytecode baru.\nNutup buffer bukan sama dengan nutup program. solana program close --buffers cuma hapus akun Buffer, bukan akun Program atau ProgramData. Flag --buffers itu yang aman buat dijalanin rutin. Nutup akun program itu langkah yang nggak bisa dibatalkan, jadi hati-hati.\nBuffer authority default ke wallet deployer. Kalau lu pengen multisig ngekontrol upgrade, lu harus eksplisit transfer buffer authority sebelum ngusulin transaksi upgrade. Lupa langkah ini bikin buffer terkunci ke hot wallet lu dan multisig nggak bisa make dia. Pusing nanti.\nDeploy bisa berhasil meskipun CLI lapor gagal. Masalah jaringan kadang bikin CLI timeout nunggu konfirmasi, padahal transaksi aktivasi udah mendarat. Selalu jalanin solana program show \u0026lt;PROGRAM_ID\u0026gt; setelah deploy yang \u0026ldquo;gagal\u0026rdquo; sebelum nyoba lagi. Kalau nggak, lu bisa buang SOL senilai satu buffer lagi buat sesuatu yang sebenarnya udah sukses.\nsolana program deploy dan solana program upgrade ngerjain hal yang kurang lebih sama. Subcommand upgrade itu pembungkus tipis di atas alur yang sama, cuma dia skip pembuatan akun Program baru kalau udah ada. Kebanyakan orang pake solana program deploy buat keduanya sih. Dia otomatis milih instruksi aktivasi yang tepat berdasar apakah program ID udah ada atau belum.\nMengikat semuanya Yuk taruh semuanya jadi satu. Lu jalanin solana program deploy. Di balik layar, CLI bikin akun Buffer, nulis bytecode lu ke dalamnya lewat ratusan transaksi, terus ngirim satu instruksi aktivasi terakhir yang nyalin bytecode ke akun ProgramData dan ngosongin Buffer. Program udah aktif.\nKalau fase penulisan gagal, lu berakhir dengan Buffer yatang yang nahan SOL. Kalau aktivasi gagal, buffer udah lengkap tapi program nggak tersentuh. Dalam kedua kasus, program yang aktif nggak bermasalah. Pola Buffer ngisolasi kegagalan. solana program close --buffers ngembaliin SOL-nya.\nPemisahan upload-lalu-aktivasi punya efek samping yang berguna: pihak yang ngunggah bytecode nggak harus sama dengan pihak yang nge-approve deploy. Seorang developer nulis buffer, transfer authority-nya ke multisig, dan multisig approve upgrade. Pemisahan inilah yang bikin upgrade yang dikontrol tim dan governance jadi mungkin.\nBagian 5 bahas upgrade authority itu sendiri, dari keypair tunggal lewat multisig Squads dan SPL Governance, sampai ke flag --final yang nggak bisa dibatalkan dan nge-bekuin program selamanya.\nReferensi Program Deployment, Solana Docs Deploying Programs, Solana Docs Loader v3 Instruction Reference (docs.rs) UpgradeableLoaderState, solana-loader-v3-interface (docs.rs) solana-verify get-buffer-hash, Solana Verifiable Build ","permalink":"https://widnyana.web.id/id/posts/solana/solana-program-deploy-upgrade-buffer/","summary":"\u003cp\u003eIni Bagian 4 dari seri \u003ca href=\"/id/series/solana-program-lifecycle/\"\u003eSolana Program Lifecycle\u003c/a\u003e. Di \u003ca href=\"/id/posts/solana/solana-program-lifecycle-two-account-model/\"\u003eBagian 3\u003c/a\u003e kita udah bahas kalau satu program itu ternyata dua akun: Program Account 36-byte yang nge-point ke ProgramData Account yang nyimpen bytecode aslinya. Sekarang kita masuk ke bagian yang lebih seru: gimana kedua akun itu dibikin dan diganti.\u003c/p\u003e\n\u003cp\u003eVersi singkatnya: ada akun ketiga yang ikut campur. Akun sementara. Dia cuma ada selama deploy atau upgrade berlangsung, dan kalau ada yang nggak beres, dia bisa ngendon di chain nahan SOL lu sampai lu sadar dan klaim balik.\u003c/p\u003e","title":"Deploy, Upgrade, dan Pola Buffer Account"},{"content":"Ini bagian 3 dari seri Solana Program Lifecycle. Di Bagian 1 kita bahas model akun. Di Bagian 2 kita bahas compute unit. Sekarang kita masuk ke sesuatu yang jarang kepikiran sama developer, biasanya cuma kepikiran pas sesuatu udah rusak: gimana sih program bener-bener hidup di on-chain?\nBegini nih: program Solana itu bukan satu akun. Dia dua akun. Dan hubungan antara keduanya adalah yang bikin upgrade bisa dilakuin tanpa merusak semua yang bergantung ke program tersebut.\nPlot twist: program kamu itu dua akun Waktu kamu deploy program pakai solana program deploy, kamu dapet program ID. ID itu adalah alamat Solana. Kebanyakan developer ngira bytecode hidup di alamat itu. Nggak juga sih.\nProgram ID nunjuk ke sebuah Program Account yang kecil, cuma 36 byte. Akun itu cuma berisi satu hal yang berguna: sebuah pointer ke akun kedua, yaitu ProgramData Account, yang naro ELF bytecode yang beneran, nomor slot, dan upgrade authority.\nProgram Account (program ID kamu) ├── discriminator: 4 byte (u32 = 2, mengidentifikasi ini sebagai varian \u0026#34;Program\u0026#34;) └── programdata_address: 32 byte (menunjuk ke bytecode yang sebenarnya) ProgramData Account (menyimpan kode yang sebenarnya) ├── discriminator: 4 byte (u32 = 3, mengidentifikasi ini sebagai varian \u0026#34;ProgramData\u0026#34;) ├── slot: 8 byte (kapan program terakhir di-deploy/upgrade) ├── authority option: 1 byte (0 = immutable, 1 = punya upgrade authority) ├── upgrade_authority_address: 32 byte (siapa yang bisa upgrade, jika ada) └── ELF bytecode: sisa dari akun Kedua akun punya owner program BPF Loader Upgradeable di BPFLoaderUpgradeab1e11111111111111111111111. Perhatikan 1 nggantikan l di \u0026ldquo;Upgradeable.\u0026rdquo; Alamat Solana di-encode dalam base58, dan dalam base58 karakter 1 nunjukin zero byte. Hasilnya kebaca kayak \u0026ldquo;Upgradeable\u0026rdquo; dengan typo, tapi emang begitulah hasil encoding-nya.\nAkun ProgramData diturunkan secara deterministik dari akun Program. Punya owner BPF Loader:\n1 2 3 4 5 6 pub fn get_program_data_address(program_address: \u0026amp;Pubkey) -\u0026gt; Pubkey { Pubkey::find_program_address( \u0026amp;[program_address.as_ref()], \u0026amp;id(), // BPFLoaderUpgradeab1e11111111111111111111111 ).0 } Alamat program yang sama, alamat ProgramData yang sama. Setiap saat. Ini adalah PDA yang diturunkan dari alamat program itu sendiri, punya owner BPF Loader.\nKenapa dibagi dua akun sih? Pemisahan ini ada karena satu alasan: upgrade.\nKalau program ID dan bytecode hidup di akun yang sama, ng-upgrade bytecode berarti ngubah akun tersebut. Padahal program ID direferensikan di mana-mana: PDA, kode client, file konfigurasi, CPI call dari program lain, token accounts, governance proposal. Ubah itu, semuanya rusak.\nJadi, Program Account nggak pernah berubah. Dia pointer tetap sebesar 36 byte. Waktu kamu upgrade, cuma akun ProgramData yang dapet bytecode baru. Pointer-nya tetap sama. Semua yang mereferensikan program ID tetap jalan.\nSebelum upgrade: Program Account (ID: ABC...123) → ProgramData (slot 200, bytecode v1) Setelah upgrade: Program Account (ID: ABC...123) → ProgramData (slot 450, bytecode v2) ^^^ tidak berubah ^^^ ^^^ slot baru, bytecode baru ^^^ Program ID itu identitas yang stabil. Akun ProgramData itu implementasi yang bisa diganti.\nApa sih \u0026ldquo;executable\u0026rdquo; itu beneran? Di Bagian 1, kita udah lihat kalau setiap akun punya field executable. Buat kebanyakan akun, nilainya false. Buat akun program, nilainya true. Tapi apa yang beneran dilakuin runtime sama field ini?\nWaktu transaksi manggil sebuah program, runtime ngecek:\nApakah akun ditandai executable: true? Apakah akun punya owner program loader (BPF Loader Upgradeable, atau BPF Loader legacy)? Kalau keduanya true, runtime ngikutin struktur akun loader buat nemuin bytecode. Buat program upgradeable, itu berarti baca akun Program, ambil programdata_address, load akun itu, dan ekstrak ELF bytecode mulai dari byte offset 45 (ukuran header ProgramData).\nLoader terus verifikasi bytecode (checksum, validasi format) dan compile via JIT buat eksekusi. Ini cuma terjadi di pemanggilan pertama dalam sebuah slot. Hasil kompilasi di-cache sama runtime, jadi pemanggilan berulang dalam slot yang sama nggak usah compile ulang.\nIntinya: executable: true bukan berarti \u0026ldquo;akun ini isinya kode yang bisa dijalanin.\u0026rdquo; Artinya \u0026ldquo;akun ini adalah entry point program. Ikutin struktur loader buat nemuin kode yang beneran.\u0026rdquo; Program Account itu entry point. ProgramData Account itu kodenya.\nLayout akun lengkap dalam byte Enum UpgradeableLoaderState pakai serialisasi bincode. Ini layout byte-nya yang persis.\nNilai discriminator:\nNilai Varian Artinya 0 Uninitialized Akun kosong, belum dipakai 1 Buffer Akun sementara buat upload bytecode 2 Program Entry point program (total 36 byte) 3 ProgramData Bytecode yang beneran + metadata Program Account (total 36 byte):\n[0..4] u32 discriminator = 2 [4..36] Pubkey programdata_address ProgramData Account (header 45 byte + ELF bytecode):\n[0..4] u32 discriminator = 3 [4..12] u64 slot [12..13] u8 option (0 = None = immutable, 1 = Some = punya authority) [13..45] Pubkey upgrade_authority_address (ada kalau option = 1) [45..] ELF bytecode mentah Buffer Account (header 37 byte + bytecode parsial):\n[0..4] u32 discriminator = 1 [4..5] u8 option (0 = None, 1 = Some) [5..37] Pubkey authority_address (ada kalau option = 1) [37..] byte program mentah (diupload dalam potongan-potongan) Akun Buffer itu tempat penampungan sementara yang dipakai pas deploy dan upgrade. Detailnya ada di Bagian 4.\nKenapa BPF Loader punya program kamu Baik Program Account maupun ProgramData Account punya field owner yang di-set ke BPFLoaderUpgradeab1e11111111111111111111111. Ini bukan cuma saran. Runtime ngejalanin ini keras.\nIngat aturan kepemilikan dari Bagian 1: cuma program owner yang bisa ngubah data sebuah akun. Karena BPF Loader punya akun Program dan ProgramData, dia satu-satunya entitas yang bisa ngubah pointer bytecode atau bytecode itu sendiri. Program kamu nggak bisa modif kodenya sendiri. Program lain juga nggak bisa.\nMakannya upgrade dilakuin lewat instruksi BPF Loader (Upgrade, DeployWithMaxDataLen, SetAuthority). Loader ngecek tanda tangan upgrade authority sebelum ngizinin perubahan apapun. Tanpa tanda tangan authority yang valid, nggak ada yang terjadi deh.\nWaktu kamu bikin program jadi immutable (pakai solana program deploy --final atau solana program set-upgrade-authority --final), loader set field upgrade_authority_address ke None (byte option di offset 12 jadi 0). Setelah itu, loader nolak semua instruksi upgrade secara permanen. Nggak ada rollback. Nggak ada undo. Bytecode-nya dibekukan.\nBiaya runtime dari model dua akun Di Bagian 2 kita udah bahas biaya compute unit. Loader upgradeable punya biaya CU sendiri:\nUPGRADEABLE_LOADER_COMPUTE_UNITS: 2,370 CU Itu biaya dasar yang dibebankan loader pas proses instruksi apapun (deploy, upgrade, close). Biaya aktual buat load dan eksekusi program itu terpisah dan tergantung ukuran bytecode.\nModel dua akun juga berarti runtime load dua akun buat setiap pemanggilan program: Program Account (36 byte, cepat) dan ProgramData Account (seukuran bytecode, bisa gede). Buat program Anchor tipikal sekitar 300-500KB bytecode, berarti dua pemuatan akun per pemanggilan. Biaya transfer data saat CPI adalah 1 CU per 250 byte (cpi_bytes_per_unit), jadi memuat akun ProgramData sebesar 400KB makan sekitar 1,600 CU cuma buat transfer data.\nIni overhead yang nggak bisa kamu kontrol. Itu harga dari arsitektur upgradeable. Trade-off-nya worth it sih: kamu dapet hot upgrade dengan biaya beberapa ribu CU per pemanggilan.\nLoader legacy: BPF Loader v1 Sebelum loader upgradeable ada, dulu ada dua loader non-upgradeable:\nDeprecated loader (BPFLoader1111111111111111111111111111111111): yang asli. Udah nggak dipakai buat deploy baru. BPF Loader v2 (BPFLoader2111111111111111111111111111111111): masih jalan, tapi bytecode hidup langsung di akun program. Nggak ada pointer, nggak ada akun ProgramData, nggak ada upgrade. Program yang di-deploy pakai salah satu loader ini immutable dari sananya. Kamu nggak bisa upgrade. Kalau ada bug, harus deploy program baru di alamat baru dan migrasi semuanya. Nyebelin banget, dan itulah kenapa loader upgradeable dibikin.\nProgram baru sebaiknya selalu pakai loader upgradeable. solana program deploy udah pakai itu secara default, jadi itulah yang kamu dapet kecuali kamu sengaja maksa pakai loader legacy.\nVerifikasi model dua akun langsung on-chain Kamu bisa lihat semuanya sendiri nih. Ambil program ID apapun dan cek akun-akunnya.\n1 2 3 4 5 6 7 8 9 # Periksa akun program solana program show \u0026lt;PROGRAM_ID\u0026gt; # Output: # Program ID: \u0026lt;PROGRAM_ID\u0026gt; # Owner: BPFLoaderUpgradeab1e11111111111111111111111 # ProgramData Address: \u0026lt;PROGRAMDATA_ADDRESS\u0026gt; # Authority: \u0026lt;UPGRADE_AUTHORITY atau none\u0026gt; # Last Deployed In Slot: \u0026lt;SLOT\u0026gt; Atau query data akun mentah via RPC:\n1 2 3 4 5 # Dapatkan akun program solana account \u0026lt;PROGRAM_ID\u0026gt; --output json # Dapatkan akun programdata (bytecode yang sebenarnya) solana account \u0026lt;PROGRAMDATA_ADDRESS\u0026gt; --output json Akun program bakal kecil banget (36 byte). Akun ProgramData bakal jauh lebih gede, sesuai ukuran bytecode ditambah header 45 byte.\nBuat verifikasi derivasi alamat ProgramData:\n1 2 3 4 5 6 7 8 9 import { getProgramDerivedAddress, address } from \u0026#34;@solana/kit\u0026#34;; const programId = address(\u0026#34;\u0026lt;PROGRAM_ID\u0026gt;\u0026#34;); const [programDataAddress] = await getProgramDerivedAddress({ programAddress: programId, programId: address(\u0026#34;BPFLoaderUpgradeab1e11111111111111111111111\u0026#34;), seeds: [programId], }); console.log(\u0026#34;ProgramData:\u0026#34;, programDataAddress); Ini harus cocok sama ProgramData Address yang ditunjukin sama solana program show.\nHal-hal yang sering bikin Kaget developer Ukuran akun ProgramData ditentukan saat deploy. Waktu kamu deploy pakai solana program deploy --max-len 400000, akun ProgramData dibuat dengan ruang buat 400KB bytecode. Kalau bytecode hasil upgrade melebihi itu, upgrade gagal dong. Kamu nggak bisa gedein akun tanpa redeploy dari awal (atau pakai instruksi ExtendProgram buat nambah ruang, yang butuh SOL buat tambahan saldo rent-exempt).\nAkun buffer yatim bikin SOL terbuang. Kalau deploy gagal di tengah jalan, akun buffer tetap ada on-chain dengan SOL yang terkunci di dalamnya. Pakai solana program close --buffers buat klaim balik.\nBikin program immutable itu permanen. Nggak ada perintah \u0026ldquo;unfinalize.\u0026rdquo; Setelah authority dihapus, program ID itu dibekuin selamanya. Kalau ada bug, satu-satunya pilihan deploy program baru di alamat baru dan migrasi semuanya. Ini by design. Immutability itu fitur keamanan, bukan bug.\nAkun ProgramData adalah PDA, tapi kamu nggak ngelola dia. BPF Loader yang nurunin dan ngelola itu. Kamu nggak pernah berinteraksi langsung dengannya. Semua operasi dilakuin lewat instruksi loader.\nMengikat Semuanya Yuk taruh semuanya dalam konteks. Sebuah transaksi manggil program kamu. Runtime ngecek: apakah akun executable: true? Apakah punya owner loader? Kalau iya, dia ngikutin pointer dari Program Account ke ProgramData Account, load ELF bytecode, dan mulai jalanin.\nProgram ID nggak pernah berubah. Pointer nggak pernah berubah. Waktu kamu upgrade, loader ganti bytecode di dalam ProgramData Account dan naikin nomor slot. Setiap client, setiap derivasi PDA, setiap CPI call yang mereferensikan program ID sebelum upgrade tetap jalan setelahnya. Itulah tujuan dari pemisahan ini.\nBPF Loader punya kedua akun. Dia yang mutusin apa yang boleh dimodif dan apa yang nggak. Program kamu nggak bisa nyentuh kodenya sendiri. Program lain juga nggak bisa. Semua modifikasi dilakuin lewat instruksi loader, dan setiap instruksi ngecek upgrade authority.\nBagian 4 bahas alur deploy dan upgrade secara lengkap, termasuk pola akun buffer dan apa yang terjadi kalau sesuatu gagal di tengah jalan.\nReferensi BPF Loader Upgradeable, Solana Docs UpgradeableLoaderState enum, solana-loader-v3-interface (docs.rs) Loader Upgradeable Instructions (docs.rs) Accounts, Solana Docs ","permalink":"https://widnyana.web.id/id/posts/solana/solana-program-lifecycle-two-account-model/","summary":"\u003cp\u003eIni bagian 3 dari seri \u003ca href=\"/id/series/solana-program-lifecycle/\"\u003eSolana Program Lifecycle\u003c/a\u003e. Di \u003ca href=\"/id/posts/solana/solana-accounts-storage-rent/\"\u003eBagian 1\u003c/a\u003e kita bahas model akun. Di \u003ca href=\"/id/posts/solana/solana-compute-units/\"\u003eBagian 2\u003c/a\u003e kita bahas compute unit. Sekarang kita masuk ke sesuatu yang jarang kepikiran sama developer, biasanya cuma kepikiran pas sesuatu udah rusak: gimana sih program bener-bener hidup di on-chain?\u003c/p\u003e\n\u003cp\u003eBegini nih: program Solana itu bukan satu akun. Dia dua akun. Dan hubungan antara keduanya adalah yang bikin upgrade bisa dilakuin tanpa merusak semua yang bergantung ke program tersebut.\u003c/p\u003e","title":"Bagaimana Program Solana Benar-Benar Hidup On-Chain: Model Dua Akun"},{"content":"Ini Bagian 2 dari seri Solana Program Lifecycle. Di Bagian 1 kita udah bahas cara kerja akun. Sekarang gue mau bahas hal yang bakal bikin kode lu hancur kalau dibiariin: compute units.\nTransaksi Solana nggak ngasih peringatan saat kita mendekatin batas compute. Dia cuma gagal aja. User kehilangan fees. Nggak ada pengembalian. Banyak developer baru nyadar masalah ini pas mainnet mulai nolak transaksi yang sebelumnya lancar jaya di devnet.\nPost ini about cara nghindarin pelajaran mahal itu.\nMasalahnya: Transaksi kita lebih mahal dari yang kita kira Transaksi Solana nggak gagal secara elegan saat kehabisan compute. Dia cuma mati begitu aja. User kehilangan fees. Nggak ada pengembalian. Nggak ada peringatan.\nMasalahnya: kebanyakan developer menebak biaya compute mereka alih-alih ngukur. \u0026ldquo;Oh, CPI kayanya aman sih, gue bisa muat 10.\u0026rdquo; Terus pas di-profiling, ternyata 10 CPI aja udah bakar porsi gede dari budget 1.4M. Tambah verifikasi tanda tangan, beberapa account load, dikit deserialization, dan tiba-tiba transaksi udah jebol budget.\nMasalah kedua: nggak jelas bagian kode mana yang mahal sampai lu ukur. Verifikasi tanda tangan 25,000 CU. Operasi aritmatika sederhana sekitar 100-150 CU di praktiknya (termasuk overhead serialisasi). Bedanya signifikan banget. Tanpa profiling, semuanya cuma tebakan doang.\nPost ini ngebahas cara lihat biaya aslinya. Dan begitu biayanya kelihatan, optimasi jadi kebaca sendiri.\nApa itu compute unit (CU)? Compute unit itu kayak meter pada transaksi. Setiap operasi makan jumlah tertentu. Kalau habis, transaksi gagal.\nBudget-nya:\nApa Budget Catatan Per instruksi 200,000 CU Alokasi default. Beberapa instruksi sistem dikasih lebih dikit. Per transaksi 1,400,000 CU Total dari semua instruksi. Kalau kode kita murah, kita bisa minta lebih dikit. Batas akun 32 akun per transaksi (64 dengan Address Lookup Tables) Transaksi standar 32. Address Lookup Tables (ALT) naikin ini ke 64. Setiap akun yang diload makan CU berdasar ukuran data. Konsepnya mirip gas di Ethereum: setiap operasi punya biaya. Ethereum ngasih estimasi biaya sebelum pengiriman; Solana nggak nunjukin estimasi itu secara default, jadi biaya CU harus diukur eksplisit. Kalau nggak, siap-siap aja gagal diam-diam.\nKenapa operasi yang beda biayanya beda banget Nggak semua kode itu sama. Ada operasi yang 10 CU. Ada yang 25,000 CU. Selisihnya tiga orde magnitudo. Runtime nge-charge berdasar seberapa berat setiap operasi sebenernya.\nBiaya CU di bawah berasal dari sumber Agave runtime. Nilai-nilai ini bisa berubah antar versi runtime. Selalu profil pake cargo test-sbf buat angka aslinya dari kode kamu.\nMurah (10-100 CU):\nOperasi memori (mem_op_base_cost): 10 CU Logging (log_64_units): 100 CU Akses Sysvar (sysvar_base_cost): 100 CU Sedang (85-1,500 CU):\nHashing SHA-256 (sha256_base_cost): 85 CU dasar + 1 CU per byte. Hash 32-byte sekitar ~117 CU. Derivasi alamat program (create_program_address_units): 1,500 CU Mahal (10,000+ CU):\nRecovery tanda tangan secp256k1 (secp256k1_recover_cost): 25,000 CU per tanda tangan Deserialisasi Borsh untuk struct besar (bervariasi berdasar ukuran) Cross-program invocations (CPI): 946 CU biaya invokasi dasar (invoke_units), ditambah seluruh biaya eksekusi program yang dipanggil dan transfer data akun (cpi_bytes_per_unit = 250 bytes/CU) Angka CPI ini sering bikin orang kaget. Invokasi dasarnya cuma 946 CU, tapi total biaya CPI nyangkup semua yang dikerjain program yang dipanggil. CPI ke System Program untuk transfer mungkin totalnya ~26,000 CU setelah eksekusi programnya disertakan. CPI ke program DeFi yang kompleks bisa bakar 100,000+ CU. Biaya dasarnya kecil; eksekusi callee-nya yang dominan.\nContoh: Berapa biaya sebuah transfer\nTransfer sederhana ke System Program:\nOverhead instruksi: 200-500 CU Muat 3 akun: ~500 CU Bangun instruksinya: ~100 CU Invokasi dasar CPI: ~946 CU System Program memprosesnya: ~200 CU ───────────────────────────────────── Total: ~2,000 CU (dasar) sampai ~26,000 CU (dengan overhead CPI penuh) Transfer sederhana muat dengan nyaman. Tapi tambahin satu verifikasi tanda tangan secp256k1 (+25,000 CU) dan totalnya ~27,000 CU. Tambah deserialisasi Borsh 10KB (~40 CU untuk transfer data dengan 250 bytes/CU) dan satu CPI lagi ke program kompleks (~50,000+ CU), dan tiba-tiba udah 77,000+ CU. Masih di bawah 200K sih, tapi akumulasi ini kelihatan banget.\nUkur, jangan tebak Sebelum ngoptimasi apapun, kita perlu lihat angka aslinya. Menebak itu buang waktu doang.\nPake cargo test-sbf\ncargo test-sbf (dipanggil secara internal oleh anchor test) ngompilasi dan jalanin program kita ke BanksClient lokal. Saat test jalan, log program nunjukin persis berapa banyak CU yang dimakan:\n1 cargo test-sbf Output-nya nyertain satu baris per instruksi:\nProgram \u0026lt;id\u0026gt; invoke [1] Program log: ... Program \u0026lt;id\u0026gt; consumed 45,231 of 200,000 compute units Program \u0026lt;id\u0026gt; success Itu data nyata dari kode kita yang sebenernya. Kita juga bisa pake metode RPC simulateTransaction (diekspos sebagai solana confirm -v di CLI) buat dapetin estimasi CU buat transaksi ke cluster live sebelum kita kirim.\nContoh test Anchor yang nyata:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #[tokio::test] async fn test_my_instruction_cu_usage() { let program_test = ProgramTest::new( \u0026#34;my_program\u0026#34;, id(), processor!(process_instruction), ); let mut ctx = program_test.start_with_context().await; // Siapkan akun dan instruksi... let tx = Transaction::new_signed_with_payer( \u0026amp;[your_instruction], Some(\u0026amp;ctx.payer.pubkey()), \u0026amp;[\u0026amp;ctx.payer], ctx.last_blockhash, ); // Jalanin. Log program bakal cetak CU yang dimakan. ctx.banks_client .process_transaction(tx) .await .expect(\u0026#34;Transaksi gagal\u0026#34;); } Jalanin ini dan perhatiin outputnya. Kita bakal ngeliat sesuatu kayak:\nProgram \u0026lt;id\u0026gt; consumed 78,432 of 200,000 compute units Angka 78,432 itu angka asli kita. Bukan tebakan, bukan hitung-hitungan. Pengukuran nyata.\nPola optimasi (dan di mana beneran membantu) Begitu kita bisa ngukur, kita bisa ngoptimasi. Ini pola-pola yang beneran penting.\nPola 1: Minimalkan CPI (dampak paling gede) Setiap cross-program invoke punya biaya dasar 946 CU, tapi total biayanya nyangkup semua yang dikerjain program yang diinvoke. CPI ke Token Program mungkin makan 15,000 CU total. CPI ke program kompleks bisa bakar 100,000+ CU. Numpuk 10 CPI ke berbagai program dan biayanya numpuk cepet banget.\nJebakannya:\n1 2 3 4 5 // 10 CPI ke instruksi transfer. Meskipun masing-masing \u0026#34;cuma\u0026#34; ~26,000 CU total, // itu udah 260,000+ CU sebelum logic bisnis apapun for user in users { invoke(\u0026amp;transfer_instruction, \u0026amp;[...])?; } Solusinya: Kalau program target nyangkep batching, pake dong. Kalau nggak, pindahin loop ke luar transaksi dan lakuin beberapa transaksi sebagai gantinya.\nIni perbaikan dengan dampak paling gede. Setiap CPI yang nggak perlu itu buang biaya invokasi dasarnya (946 CU) plus seluruh biaya eksekusi callee-nya. Ngilangin CPI hampir selalu jadi optimasi dengan dampak tertinggi.\nPola 2: Jangan deserialisasi yang nggak kita butuhin Mendeserialisasi struct Borsh 10KB itu makan CU. Tapi kalau kita cuma butuh byte 64-72, mendeserialisasi semuanya itu pemborosan.\n1 2 3 4 5 6 7 // Buruk: deserialisasi seluruh struct let full_state: MyState = MyState::try_from_slice(\u0026amp;account.data)?; let field_i_need = full_state.specific_field; // Lebih baik: baca cuma yang dibutuhin let bytes = \u0026amp;account.data[64..72]; // 8 byte untuk u64 let field_i_need = u64::from_le_bytes(bytes.try_into()?); Ini cuma masuk akal kalau profiling nunjukin deserialisasi emang bottleneck-nya. Kebanyakan waktu sih nggak. Pake sebagai jalan terakhir pas udah deket batas CU dan optimasi lain nggak nolong.\nPola 3: Berhenti nyertain akun yang nggak kita pake Batas akun itu 32 per transaksi standar (64 dengan Address Lookup Tables). Transfer data akun makan 1 CU per 250 byte selama CPI (cpi_bytes_per_unit). Kalau akun nggak disentuh, jangan disertakan.\nPemborosannya:\n1 2 3 4 5 6 7 8 #[derive(Accounts)] pub struct MyInstruction\u0026lt;\u0026#39;info\u0026gt; { pub signer: Signer\u0026lt;\u0026#39;info\u0026gt;, pub state: Account\u0026lt;\u0026#39;info, State\u0026gt;, pub unused1: UncheckedAccount\u0026lt;\u0026#39;info\u0026gt;, pub unused2: UncheckedAccount\u0026lt;\u0026#39;info\u0026gt;, // ... 20 akun lagi yang nggak kita sentuh } 22 akun yang nggak dipake itu? Biaya loading data yang kebuang. Setiap byte yang ditransfer selama CPI makan CU.\nSolusinya:\n1 2 3 4 5 #[derive(Accounts)] pub struct MyInstruction\u0026lt;\u0026#39;info\u0026gt; { pub signer: Signer\u0026lt;\u0026#39;info\u0026gt;, pub state: Account\u0026lt;\u0026#39;info, State\u0026gt;, } Sertakan cuma yang beneran kita baca atau tulis.\nPola 4: Jangan verifikasi 10 tanda tangan dalam satu instruksi Recovery secp256k1 makan 25,000 CU per tanda tangan (secp256k1_recover_cost). Verifikasi 10 tanda tangan dan itu 250,000 CU cuma buat verifikasi. Budget hampir abis sebelum ada kerjaan nyata yang kejadian.\nKalau butuh banyak tanda tangan:\nVerifikasi di offchain dan cuma validasi hasilnya di onchain Pake program multisig yang mem-batch verifikasi Pecah jadi beberapa transaksi Ini batasan keras, bukan pola. Kalau use case butuh 10 verifikasi tanda tangan dalam satu instruksi, berarti lu melawan arsitekturnya, bukan kerja samaama dia.\nPriority fees: bayar buat lompat antrian Saat mainnet sibuk, transaksi nunggu. Bayar priority fee bisa motong antrian. Semakin banyak bayar per CU, semakin tinggi prioritasnya.\nset_compute_unit_price nerima micro-lamports per compute unit. 1 lamport = 1,000,000 micro-lamports.\n1 2 3 4 5 6 7 8 9 10 11 use solana_sdk::compute_budget::ComputeBudgetInstruction; // Minta 50,000 CU (bukan default 200K) // Bayar 1,000 micro-lamports per CU sebagai priority fee let mut instructions = vec![ ComputeBudgetInstruction::set_compute_unit_limit(50_000), ComputeBudgetInstruction::set_compute_unit_price(1_000), ]; instructions.push(your_instruction); // Total priority fee: 50,000 * 1,000 micro-lamports = 50,000,000 micro-lamports = 50,000 lamports = 0.00005 SOL Kapan pakenya:\nSaat kemacetan, kalau transaksi kita terus timeout Operasi yang sensitif ke waktu: MEV (nyusun ulang trade buat keuntungan), liquidation (nutup pinjaman berisiko dengan cepet), arbitrage (manfaatin beda harga sebelum market berubah) Load testing tekanan mainnet Di devnet/testnet:\n1 ComputeBudgetInstruction::set_compute_unit_price(0) // Gratis Budget transaksi: kapan perlu dipecah Budget-nya 1.4M CU total per transaksi. Kalau merangkai beberapa instruksi, biaya masing-masing harus keliatan.\nContoh: Transaksi aman\nInstruksi 1: 45,000 CU Instruksi 2: 60,000 CU Instruksi 3: 35,000 CU Total: 140,000 CU \u0026lt; 1,400,000 CU ✓ Contoh: Jebol budget\nInstruksi 1: 600,000 CU Instruksi 2: 600,000 CU Instruksi 3: 600,000 CU Total: 1,800,000 CU \u0026gt; 1,400,000 CU ✗ GAGAL Kalau mendekatin 1.4M, pecah jadi dua transaksi. Lakukan pas development dong, bukan pas insiden produksi.\nChecklist sebelum mainnet Ini yang bedain kode yang bisa di-deploy dari kode yang gagal di produksi.\nProfil pake cargo test-sbf: ukur penggunaan CU aslinya. Jangan nebak. Test kasus terberat: akun maksimal, data maksimal, semua dibatasin. Berapa biayanya? Estimasikan priority fees: saat jam sibuk, berapa biaya 1,000 micro-lamports per CU dalam SOL? Rencanain pemecahan: kalau deket 1.4M, pecah jadi beberapa transaksi sekarang. Review hot paths: fungsi yang sering dipanggil harus pake pola optimasi di atas. Contoh output checklist:\ntest_initialize_state ... ok (12,500 CU) test_transfer ... ok (26,100 CU) test_complex_update ... ok (145,000 CU) test_batch_operation ... ok (850,000 CU) ← deket 1.4M, perlu dipecah Kalau ada test yang melebihi 1.4M, refactor dulu sebelum deploy.\nApa yang sering bikin developer kaget Kejutan terbesar: kebanyakan developer Solana nggak tahu berapa banyak CU program mereka sebenernya pake. Semuanya lancar di devnet, terus mainnet mulai nolak transaksi dengan pesan \u0026ldquo;insufficient compute budget\u0026rdquo; dan errornya nggak ngasih tahu instruksi mana yang meledakkan budget.\nKejutan kedua: CPI dominan di budget kebanyakan program. Kamu bisa ngoptimasi deserialisasi dan account loading seharian dan hemat 10,000 CU. Atau lu bisa ngilangin satu CPI yang nggak perlu dan hemat seluruh biaya eksekusinya. Satu perubahan, dampak gede.\nHal ketiga: angka CU di log cargo test-sbf itu data nyata dari kode kamu yang sebenernya. Jangan ng_estimator. Profil.\nMengikat semuanya Bayangin semuanya bergerak. Sebuah transaksi masuk. Runtime ngasih dia 1.4M CU. Setiap instruksi di dalamnya dapet 200K. Setiap operasi, dari pembacaan memori sampai recovery secp256k1, nggerogoti budget itu. Saat nyapai nol, transaksi mati. Nggak ada pengembalian.\nLangkah yang bisa diandalkan adalah profil sebelum hal lain. cargo test-sbf ngasih angka nyata. Pake. Cari tau ke mana CU sebenernya pergi. Biasanya itu CPI: satu cross-program invoke yang nggak perlu bisa lebih mahal dari semua yang lain digabungin. Hilangin itu, dan budget kebuka lagi.\nPriority fees nolong melewatin kemacetan. Tapi dia nggak bakal nyelamatin kode yang bakar 1.2M CU karena numpuk CPI yang malas. Transaksi yang cuma pake 80K CU karena hot paths-nya ketat jarang butuh priority fees sama sekali.\nBagian 3 bahas gimana program Solana beneran hidup di onchain: model dua akun yang bikin upgrade bisa dilakuin tanpa merusak semua yang bergantung ke program tersebut.\n","permalink":"https://widnyana.web.id/id/posts/solana/solana-compute-units/","summary":"\u003cp\u003eIni Bagian 2 dari seri \u003ca href=\"/id/series/solana-program-lifecycle/\"\u003eSolana Program Lifecycle\u003c/a\u003e. Di Bagian 1 kita udah bahas cara kerja akun. Sekarang gue mau bahas hal yang bakal bikin kode lu hancur kalau dibiariin: compute units.\u003c/p\u003e\n\u003cp\u003eTransaksi Solana nggak ngasih peringatan saat kita mendekatin batas compute. Dia cuma gagal aja. User kehilangan fees. Nggak ada pengembalian. Banyak developer baru nyadar masalah ini pas mainnet mulai nolak transaksi yang sebelumnya lancar jaya di devnet.\u003c/p\u003e\n\u003cp\u003ePost ini about cara nghindarin pelajaran mahal itu.\u003c/p\u003e","title":"Compute Units Solana: Apa yang Kamu Bayar untuk Menjalankan Kode"},{"content":"Ini Bagian 1 dari seri Solana Program Lifecycle. Kalau kamu baru mulai belajar develop di Solana, mulai dari sini aja. Konsep-konsep di sini bakal muncul terus di semua yang kamu bikin nanti.\n1. Model Akun Di Solana, semuanya itu akun. Program, data user, token, bahkan state ledger, semuanya disimpen di akun. Nggak ada database engine, nggak ada tabel relasional. Cuma akun doang, tersebar di peta key-value datar di mana setiap key itu alamat 32-byte dan setiap value itu sebuah akun.\nSetiap akun di Solana punya field-field ini:\nField Isinya lamports Saldo akun dalam lamports (1 SOL = 1 miliar lamports) data Array byte mentah. Di sinilah data kamu beneran disimpen owner Program ID yang ngendalikan akun ini executable true kalau akun ini berisi kode program yang bisa dijalanin rent_epoch Field warisan. Nyimpen state rent (detailnya bahas di bawah) Buat kamu yang pernah develop di Ethereum, ini bedanya yang paling gede: Solana misahin kode dari data. Sebuah program akun nyimpen bytecode yang bisa dieksekusi (mirip contract). Sebuah data akun nyimpen state yang dibaca dan ditulis sama program. Itu dua akun yang beda, ya.\nKenapa sih penting? Karena pas transaksi nyentuh data akun yang beda-beda, runtime bisa proses semuanya secara paralel. Nggak ada bottleneck global state tunggal.\n2. Siapa Punya Apa Setiap akun punya owner (sebuah program ID). Runtime Solana ngejalanin aturan simpel soal siapa yang boleh nyentuh apa:\nCuma program owner yang bisa ubah data sebuah akun Cuma program owner yang bisa kurangi lamports dari akun itu Program mana aja bisa nambah lamports ke akun mana aja yang writable Owner itu satu-satunya program yang bisa alihin kepemilikan ke program lain (dan cuma kalau akunnya nggak executable) Dua program nggak bisa pinjem akun yang sama buat ditulis bareng-bareng Pas kamu bikin akun, System Program (11111111111111111111111111111111) jadi ownernya. Buat nyerahin ke program kamu, System Program transfer kepemilikannya. Setelah program kamu jadi ownernya, cuma program kamu doang yang bisa tulis ke akun itu.\nSiklus hidup akun: 1. System Program bikin akun → owner = System Program 2. System Program alihin kepemilikan ke program kamu 3. Program kamu sekarang jadi satu-satunya yang bisa nyentuh data akun ini Buat akun program (yang punya executable: true), ownernya itu loader program, biasanya BPF Loader. Itu bahasannya di Bagian 3.\n3. Bagaimana Penyimpanan Bekerja Data akun itu cuma array byte mentah. Nggak ada schema engine, nggak ada ORM, nggak ada sihir. Program kamu yang mutusin cara pack dan unpack byte-nya.\nBorsh: Serializer Pilihan Utama Mayoritas program Solana pakai Borsh, format serialisasi biner yang ringkas dan deterministik. Contoh struct data:\n1 2 3 4 5 6 7 8 9 use borsh::{BorshSerialize, BorshDeserialize}; #[derive(BorshSerialize, BorshDeserialize)] pub struct UserProfile { pub authority: Pubkey, // 32 byte pub username: String, // 4 byte (prefix panjang) + byte string aktual pub karma: u64, // 8 byte pub is_active: bool, // 1 byte } Ngitung ukuran itu penting banget karena kamu harus alokasi byte sejak awal:\nUserProfile = 32 (Pubkey) + (4 + max_username_len) + 8 + 1 Kalau kamu pakai Anchor, tambahin 8 byte buat discriminator yang otomatis ditambahin di awal.\nAturan Ukuran Ukuran data akun maksimal itu 10 MB. Kamu alokasi ruang pas pembuatan dan nggak bisa dibesarin lagi nanti. Nutup dan bikin ulang itu satu-satunya cara buat ngubah ukuran. Saran gue sih: selalu alokasi lebih gede dari yang kamu butuhin. Nambah beberapa byte sekarang itu jauh lebih murah daripada migrasi data kemudian harinya.\nTips Layout Taruh field berukuran tetap di awal, field berukuran variabel (string, vec) di akhir. Ini bikin semuanya ada di offset yang bisa diprediksi:\n+-------------------+ | 8 byte: Anchor | \u0026lt;- Anchor discriminator (kalau pakai Anchor) | discriminator | +-------------------+ | Field ukuran tetap | \u0026lt;- Pubkey, u64, bool, selalu di offset yang diketahui +-------------------+ | Field ukuran | \u0026lt;- String, Vec, taruh di bagian akhir | variabel | +-------------------+ 4. Rent: Membayar untuk Penyimpanan Nyimpen data di Solana butuh SOL. Jaringan sebutnya rent, tapi kerjanya lebih kayak deposit. Kamu bisa klaim balik seluruh saldonya pas nutup akun.\nBegini deal-nya: setiap akun harus pegang saldo lamport minimum yang proporsional sama ukuran datanya. Saldo ini bikin data tetap hidup on-chain.\nRent-Exempt: Kondisi Normal Pendekatan praktisnya gampang aja: danai setiap akun dengan cukup lamports buat nutup dua tahun rent di muka. Setelah itu, akun jadi rent-exempt. Runtime nggak pernah potong apa-apa dari akun itu.\nIni bukan opsional di praktiknya. Setiap akun yang kamu bikin harus rent-exempt sejak hari pertama.\n1 2 3 4 5 6 7 8 9 10 11 12 # CLI: cek minimum rent-exempt buat ukuran data tertentu solana rent 150 # Output: Rent-exempt minimum: 0.001934880 SOL # Atau query langsung via RPC curl https://api.devnet.solana.com -s -X POST \\ -H \u0026#34;Content-Type: application/json\u0026#34; -d \u0026#39;{ \u0026#34;jsonrpc\u0026#34;: \u0026#34;2.0\u0026#34;, \u0026#34;id\u0026#34;: 1, \u0026#34;method\u0026#34;: \u0026#34;getMinimumBalanceForRentExemption\u0026#34;, \u0026#34;params\u0026#34;: [150] }\u0026#39; Field rent_epoch yang kamu liat di struktur akun? Itu warisan dari zaman dulu waktu rent emang dipotong per epoch. Di praktiknya sekarang, setiap akun baru harus didanai sampai minimum rent-exempt pas pembuatan. Runtime nggak bakal ngizinin kamu bikin akun yang dananya kurang.\nMenutup Akun Kalau kamu udah nggak butuh sebuah akun lagi, program kamu bisa nutup itu akun buat klaim balik SOL-nya:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Anchor: tutup akun dan klaim balik SOL pub fn close_account(ctx: Context\u0026lt;CloseAccount\u0026gt;) -\u0026gt; Result\u0026lt;()\u0026gt; { let dest = \u0026amp;mut ctx.accounts.destination; let source = \u0026amp;mut ctx.accounts.account_to_close; // Pindahin semua lamports keluar dest.lamports = dest.lamports .checked_add(source.lamports) .ok_or(ErrorCode::Overflow)?; source.lamports = 0; // Bersihin datanya source.data.zero_fill(); Ok(()) } Akunnya masih ada di alamatnya, tapi saldo nol dan data kosong. Garbage collection yang ngurus sisanya.\n5. Program Derived Addresses (PDA) PDA ngasih program buat punya dan ngelola akun tanpa private key sama sekali. Ini mungkin bagian Solana yang paling bikin bingung orang, jadi kita bahas pelan-pelan ya.\nApa yang Membedakan PDA Alamat Solana normal itu public key dari keypair Ed25519. Mereka punya private key yang cocok buat nandatangan transaksi. PDA beda: ini alamat yang sengaja diturunkan supaya jatuh dari kurva Ed25519. Nggak ada private key buat PDA. Selamanya.\nBayangin gini aja: PDA itu kayak alamat kotak surat yang cuma satu program tertentu punya kuncinya. Nggak ada manusia, nggak ada program lain, yang bisa nandatangani buat PDA itu.\nKamu nurunin PDA dari dua hal:\nSebuah program ID: program mana yang \u0026ldquo;punya\u0026rdquo; alamat ini Seeds: string byte sembarang yang kamu pilih (misalnya b\u0026quot;profile\u0026quot; atau public key user) 1 2 3 4 5 // TypeScript (web3.js) const [pdaAddress, bump] = PublicKey.findProgramAddressSync( [Buffer.from(\u0026#34;user_profile\u0026#34;), userPublicKey.toBuffer()], programId ); 1 2 3 4 5 6 7 8 // Rust let (pda_address, bump_seed) = Pubkey::find_program_address( \u0026amp;[ b\u0026#34;user_profile\u0026#34;, user_authority.key.as_ref(), ], \u0026amp;program_id, ); Proses derivasinya nyoba nilai bump mulai dari 255, hitung mundur, sampai nemu alamat yang ada di luar kurva Ed25519. Byte bump terakhir itulah yang dikembaliin bareng alamatnya.\nYang penting diingat: nurunin PDA itu cuma matematika doang. Itu nggak bikin apa-apa jadi on-chain. Kaya yang dijelasin di dokumentasi Solana, PDA itu kayak alamat di peta. Karena alamatnya ada bukan berarti ada bangunan di situ. Kamu tetap harus bikin akunnya secara eksplisit.\nKenapa PDA Berguna Alamat yang bisa diprediksi. Seeds yang sama + program yang sama = alamat yang sama, kapan aja. Frontend kamu bisa hitung di mana akun berada tanpa nanya ke chain.\nKontrol cuma oleh program. Cuma program owner yang bisa nandatangani buat PDA-nya via invoke_signed. Nggak ada key eksternal yang bisa campur tangan. Ini yang bikin PDA aman. Nggak ada manusia, nggak ada program lain, yang bisa bajak.\nDesain state yang fleksibel. Kombinasi seed yang beda ngasih alamat yang beda. Satu akun per user? Seeds [b\u0026quot;profile\u0026quot;, user_pubkey]. Config global? Seeds [b\u0026quot;config\u0026quot;]. Petakan state kamu sesuka hati deh.\nPola Seed yang Umum 1 2 3 4 5 6 7 8 // Satu akun per user seeds = [b\u0026#34;profile\u0026#34;, user_pubkey] // Banyak item per user seeds = [b\u0026#34;item\u0026#34;, user_pubkey, item_index.to_le_bytes()] // Config global tunggal seeds = [b\u0026#34;config\u0026#34;] Menandatangani dengan PDA Pas program kamu perlu nandatangani transaksi atas nama PDA (misalnya, transfer SOL dari PDA), kamu pakai invoke_signed:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 use solana_program::program::invoke_signed; // Runtime ngecek: apakah seeds ini + program ID gue = PDA ini? invoke_signed( \u0026amp;transfer_instruction, \u0026amp;accounts, \u0026amp;[ \u0026amp;[ b\u0026#34;user_profile\u0026#34;, authority.key.as_ref(), \u0026amp;[bump_seed], // disimpen pas akun dibikin ], ], )?; Runtime nurunin ulang alamat dari seeds dan program ID. Kalau cocok sama akun yang lagi dioperasiin, tanda tangannya valid. Ini satu-satunya cara PDA bisa nandatangani. Nggak ada private key cadangan.\n6. Mengikat Semuanya Yuk taruh semuanya ke praktik. Seorang user kirim transaksi. Runtime muat setiap akun yang nyangkut di instruksi. Program kamu cek field owner: bener nih program ini yang punya data akun tersebut? Kalau iya, dia baca dan tulis byte mentah (serialisasi Borsh).\nSetiap akun yang nyangkut butuh saldo lamport-nya di atau di atas minimum rent-exempt. Kalau PDA jadi bagian dari alur, program kamu nandatangani pakai invoke_signed dengan seeds. Runtime cek ulang semua aturan kepemilikan dan penandatanganan. Nggak ada pengecualian.\nAkun, penyimpanan, rent, dan PDA. Kuasai keempatnya, dan sisa pengembangan Solana bakal jauh lebih gampang. Bagian 2 bahas compute unit: gimana Solana ngukur dan mbatasin kerjaan yang bisa dikerjain transaksi kamu.\nReferensi Accounts, Solana Docs Program Derived Addresses, Solana Docs Cross-Program Invocations, Solana Docs getMinimumBalanceForRentExemption, Solana RPC Borsh Specification ","permalink":"https://widnyana.web.id/id/posts/solana/solana-accounts-storage-rent/","summary":"\u003cp\u003eIni Bagian 1 dari seri \u003ca href=\"/id/series/solana-program-lifecycle/\"\u003eSolana Program Lifecycle\u003c/a\u003e. Kalau kamu baru mulai belajar develop di Solana, mulai dari sini aja. Konsep-konsep di sini bakal muncul terus di semua yang kamu bikin nanti.\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"1-model-akun\"\u003e1. Model Akun\u003c/h3\u003e\n\u003cp\u003eDi Solana, semuanya itu akun. Program, data user, token, bahkan state ledger, semuanya disimpen di akun. Nggak ada database engine, nggak ada tabel relasional. Cuma akun doang, tersebar di peta key-value datar di mana setiap key itu alamat 32-byte dan setiap value itu sebuah akun.\u003c/p\u003e","title":"Akun Solana, Penyimpanan, dan Rent: Bagaimana Solana Mengingat Sesuatu"},{"content":"Berikut beberapa proyek open source yang pernah saya kontribusi atau buat.\nKontribusi Open Source tenable/pyTenable - Python Library for interfacing into Tenable\u0026rsquo;s platform APIs. bsoyka/gravify - A simple Python package to generate a Gravatar URL. har07/PySastrawi - Indonesian stemmer. Python port of PHP Sastrawi project. cotterapp/python-sdk - Cotter Python SDK for logging in using python scripts and CLI. cmfcmf/docusaurus-search-local - Offline / local search for Docusaurus v2+ that works behind your firewall. sartography/spiff-arena - software development platform for building, running, and monitoring executable diagrams. Proyek Pribadi solana-onchain-mcp - MCP server for Solana on-chain activity, built in Rust. idrx-go - Unofficial Go SDK for IDRX stablecoin. Docker Intro - Tips and tricks to build docker container image for various stack. kubectl-ports-rs - A kubectl krew plugin to provide a list of exposed ports on Kubernetes Pod / Service resources. Monitoring Stack - Partial part of my self-hosted monitoring stack. DevOps Toolkits - Various tools for DevOps daily routine. gallang - Golang HTTP service that provide oEmbed metadata. boilerplate-rs - Rust Web App boilerplate using Axum and SeaORM. nvltr - Telegram Bot Boilerplate using Golang. Obsoleted malesgan - Ansible Role for lazy coders. Lumen Dingo Route List - Route:list command support at Lumen framework application with Dingo API framework. jne - python script to check JNE courier for price and track your airwaybill code. rajaongkir-python - Python Client for RajaOngkir.com. ","permalink":"https://widnyana.web.id/id/projects/","summary":"\u003cp\u003eBerikut beberapa proyek open source yang pernah saya kontribusi atau buat.\u003c/p\u003e\n\u003ch2 id=\"kontribusi-open-source\"\u003eKontribusi Open Source\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/tenable/pyTenable\"\u003etenable/pyTenable\u003c/a\u003e - Python Library for interfacing into Tenable\u0026rsquo;s platform APIs.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/bsoyka/gravify\"\u003ebsoyka/gravify\u003c/a\u003e - A simple Python package to generate a Gravatar URL.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/har07/PySastrawi\"\u003ehar07/PySastrawi\u003c/a\u003e - Indonesian stemmer. Python port of PHP Sastrawi project.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/cotterapp/python-sdk\"\u003ecotterapp/python-sdk\u003c/a\u003e - Cotter Python SDK for logging in using python scripts and CLI.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/cmfcmf/docusaurus-search-local\"\u003ecmfcmf/docusaurus-search-local\u003c/a\u003e - Offline / local search for Docusaurus v2+ that works behind your firewall.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/sartography/spiff-arena\"\u003esartography/spiff-arena\u003c/a\u003e - software development platform for building, running, and monitoring executable diagrams.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"proyek-pribadi\"\u003eProyek Pribadi\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/solana-onchain-mcp\"\u003esolana-onchain-mcp\u003c/a\u003e - MCP server for Solana on-chain activity, built in Rust.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/idrx-go\"\u003eidrx-go\u003c/a\u003e - Unofficial Go SDK for IDRX stablecoin.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/docker-intro\"\u003eDocker Intro\u003c/a\u003e - Tips and tricks to build docker container image for various stack.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/kubectl-ports-rs\"\u003ekubectl-ports-rs\u003c/a\u003e - A kubectl krew plugin to provide a list of exposed ports on Kubernetes Pod / Service resources.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/monitoring-stack\"\u003eMonitoring Stack\u003c/a\u003e - Partial part of my self-hosted monitoring stack.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/devop-toolkit\"\u003eDevOps Toolkits\u003c/a\u003e - Various tools for DevOps daily routine.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/gallang\"\u003egallang\u003c/a\u003e - Golang HTTP service that provide oEmbed metadata.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/boilerplate-rs\"\u003eboilerplate-rs\u003c/a\u003e - Rust Web App boilerplate using Axum and SeaORM.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/nvltr\"\u003envltr\u003c/a\u003e - Telegram Bot Boilerplate using Golang.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"obsoleted\"\u003eObsoleted\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/malesgan\"\u003emalesgan\u003c/a\u003e - Ansible Role for lazy coders.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/lumen-dingo-route-list\"\u003eLumen Dingo Route List\u003c/a\u003e - Route:list command support at Lumen framework application with Dingo API framework.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/widnyana/jne\"\u003ejne\u003c/a\u003e - python script to check JNE courier for price and track your airwaybill code.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/redite/rajaongkir-python\"\u003erajaongkir-python\u003c/a\u003e - Python Client for RajaOngkir.com.\u003c/li\u003e\n\u003c/ul\u003e","title":"Proyek"}]