packages feed

solana-haskell-sdk-1.3.0.0: app/SplTransfer.hs

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Monad.IO.Class (liftIO)
import Data.Maybe (fromJust)
import Network.Solana.Core.Crypto
import Network.Solana.NativePrograms.SystemProgram qualified as SystemProgram
import Network.Solana.RPC.HTTP.Tokenomics (getMinimumBalanceForRentExemption)
import Network.Solana.RPC.HTTP.Transaction (requestAirdrop)
import Network.Solana.SolanaWeb3
import Network.Solana.SplPrograms.AssociatedTokenAccount qualified as Ata
import Network.Solana.SplPrograms.Token qualified as Token
import Network.Web3.Provider

main :: IO ()
main = do
  (myPublicKey, myPrivateKey) <- createSolanaKeyPair
  (mint, mintPrivateKey) <- createSolanaKeyPair -- the new token's mint account

  result <- runWeb3' (HttpProvider "http://127.0.0.1:8899") $ do
    requestAirdrop myPublicKey 10_000_000_000 >>= confirmFinalized

    let recipient = "A988FuUtUVk8jMUuVc1ccaoTA3VS9CB4dkEf9XUAUqV4" -- recipient wallet

        -- ATAs are PDAs of (wallet, token program, mint): derived, not generated.
        sourceAta = fromJust (Ata.getAssociatedTokenAddress myPublicKey mint)
        destinationAta = fromJust (Ata.getAssociatedTokenAddress recipient mint)

    -- Setup: create a mint with 6 decimals (we are its mint authority), our own
    -- token account, and mint 10 tokens into it. The mint keypair co-signs
    -- because createAccount requires the new account's signature.
    mintRent <- getMinimumBalanceForRentExemption 82 -- a mint account is 82 bytes
    setupTx <-
      newTransaction
        [myPrivateKey, mintPrivateKey]
        [ SystemProgram.createAccount myPublicKey mint mintRent 82 Token.tokenProgramId,
          Token.initializeMint2 mint 6 myPublicKey Nothing,
          Ata.createAssociatedTokenAccount myPublicKey myPublicKey mint,
          Token.mintTo mint sourceAta myPublicKey [] 10_000_000
        ]
    confirmFinalized setupTx

    -- Transfer 1 token to the recipient
    transferTx <-
      newTransaction
        [myPrivateKey]
        [ -- Create the recipient's token account if it does not exist yet (no-op otherwise).
          Ata.createAssociatedTokenAccountIdempotent
            myPublicKey -- funder (pays rent)
            recipient -- wallet that will own the ATA
            mint,
          -- Transfer 1 token (6 decimals); mint and decimals are verified on-chain.
          Token.transferChecked
            sourceAta -- source token account
            mint -- token mint
            destinationAta -- destination token account
            myPublicKey -- owner of the source account
            [] -- extra multisig signers (none)
            1_000_000 -- amount in base units
            6 -- decimals of the mint
        ]
    confirmFinalized transferTx

    -- Read back the recipient's token account (decoded on-chain state)
    account <- getTokenAccount destinationAta
    liftIO $ putStrLn ("Recipient token balance: " <> show (Token.taAmount <$> account))

  either (\e -> putStrLn ("RPC error: " <> show e)) pure result