#load "/home/joe/Development/DegenzGame/.paket/load/net6.0/main.group.fsx";; open System open System.IO open Npgsql.FSharp open dotenv.net open Solnet.Rpc open Solnet.Rpc.Models open Solnet.Rpc.Builders open Solnet.Wallet open Solnet.Programs open Solnet.KeyStore let devEnv = DotEnv.Read(DotEnvOptions(envFilePaths = [ "./.dev.env" ])) let ( _ , devConnStr )= devEnv.TryGetValue("DATABASE_URL") let ( _ , rpcClientUrl )= devEnv.TryGetValue("RPC_CLIENT") let ( _ , wssClientUrl )= devEnv.TryGetValue("WSS_CLIENT") let keystore = SolanaKeyStoreService() //let file = File.ReadAllText("/home/joe/.config/solana/devnet.json") let file = File.ReadAllText("/home/joe/.config/solana/DegenzW7kWzac5zEdTWEuqVasoVMPKd16T3za2j6S3qR.json") let authority = keystore.RestoreKeystore(file) //let rpcClient = ClientFactory.GetClient(Cluster.DevNet) let rpcClient = ClientFactory.GetClient(rpcClientUrl) //let mintAccount = PublicKey("2iS6gcoB5VhiLC4eNB7NdcaLgEHjLrXHYpz7T2JMGBDw") let mintAccount = PublicKey("4gN1u9LNBnvFERpDU3SrMusMJ1gKLbE9XB6EYraQkWTf") //let ataAccount = PublicKey("CRa7GCMUaB4np32XoTD2sEyCXFVfYKKF4JRPkQTwV3EY") let sourceAtaAccount = PublicKey("7CB459UJC3qtTYJzfC8bFNvdycQGXTPzKoEHxys8Ytfs") //let associatedTokenAccountOwner = PublicKey("GutKESfJw8PDMbFVqByxTr4f5TUSHUVmkf5gtsWWWqrU") type AirdropStatus = | Hold = 0 | Ready = 1 | Pending = 2 | Error = 3 | PendingError = 4 | Completed = 5 | Verified = 6 let updateError wallet msg = devConnStr |> Sql.connect |> Sql.parameters [ "wallet" , Sql.string wallet ; "msg" , Sql.string msg ] |> Sql.query """ UPDATE crypto SET error_msg = @msg, status = 'Error' WHERE crypto.wallet_address = @wallet; """ |> Sql.executeNonQueryAsync let updatePendingError wallet msg txId = devConnStr |> Sql.connect |> Sql.parameters [ "wallet" , Sql.string wallet ; "msg" , Sql.string msg ; "txId" , Sql.string txId ] |> Sql.query """ UPDATE crypto SET error_msg = @msg, pending_tx = @txId, status = 'PendingError' WHERE crypto.wallet_address = @wallet; """ |> Sql.executeNonQueryAsync let updatePending wallet txId = devConnStr |> Sql.connect |> Sql.parameters [ "wallet" , Sql.string wallet ; "txId" , Sql.string txId ] |> Sql.query """ UPDATE crypto SET pending_tx = @txId, status = 'Pending' WHERE crypto.wallet_address = @wallet; """ |> Sql.executeNonQueryAsync let updateCompleted wallet txId = devConnStr |> Sql.connect |> Sql.parameters [ "wallet" , Sql.string wallet ; "txId" , Sql.string txId ] |> Sql.query """ UPDATE crypto SET successful_tx = pending_tx, status = 'Completed' WHERE crypto.wallet_address = @wallet; """ |> Sql.executeNonQueryAsync let updateVerified wallet txId amount = devConnStr |> Sql.connect |> Sql.parameters [ "wallet" , Sql.string wallet ; "txId" , Sql.string txId ; "amount" , Sql.int amount ] |> Sql.query """ UPDATE crypto SET pending_tx = @txId, status = 'Verified', total_dropped = @amount WHERE crypto.wallet_address = @wallet; """ |> Sql.executeNonQueryAsync let getTokenCountToDrop wallet = task { let! wl = devConnStr |> Sql.connect |> Sql.parameters [ "wallet" , Sql.string wallet ] |> Sql.query """ SELECT has_wl, has_og FROM crypto WHERE wallet_address = @wallet; """ |> Sql.executeRowAsync (fun reader -> {| HasWhitelist = reader.bool "has_wl" ; HasOg = reader.bool "has_og" |}) return (if wl.HasWhitelist then 1uL else 0uL) + (if wl.HasOg then 2uL else 0uL) } let executeDrop (wallet : string) = let associatedTokenAccountOwner = PublicKey(wallet) let associatedTokenAccount = AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(associatedTokenAccountOwner, mintAccount) let log msg = printfn $"{wallet} || {msg}" log $"ATA: {associatedTokenAccount.Key}" let buildTransaction (block : LatestBlockHash) amount (targetWallet : string) = TransactionBuilder() .SetRecentBlockHash(block.Blockhash) .SetFeePayer(authority.Account) .AddInstruction(AssociatedTokenAccountProgram.CreateAssociatedTokenAccount( authority.Account, PublicKey(targetWallet), mintAccount)) .AddInstruction(TokenProgram.Transfer( sourceAtaAccount, associatedTokenAccount, amount, authority.Account)) .Build(authority.Account); async { // let streamingClient = ClientFactory.GetStreamingClient(Cluster.DevNet) let streamingClient = ClientFactory.GetStreamingClient(wssClientUrl) do! streamingClient.ConnectAsync() |> Async.AwaitTask let blockHash = rpcClient.GetLatestBlockHash() let! amount = getTokenCountToDrop wallet |> Async.AwaitTask log $"Dropping {amount} tokens" let tx = buildTransaction blockHash.Result.Value amount wallet let! tx = rpcClient.SendTransactionAsync(tx) |> Async.AwaitTask if tx.ErrorData <> null then let! _ = updateError wallet (tx.ErrorData.Logs |> String.concat "\n") |> Async.AwaitTask () log $"Transaction error: {wallet}" return () elif String.IsNullOrWhiteSpace(tx.Result) then let msg = $"Transaction did not have an ID but the ErrorData was null: {tx.Reason}" let! _ = updateError wallet msg |> Async.AwaitTask log $"Odd Transaction Error" return () else log $"Successful, now waiting for RPC" let! _ = updatePending wallet tx.Result |> Async.AwaitTask let! _ = streamingClient.SubscribeSignatureAsync(tx.Result, fun sub result -> if result.Value.Error = null then log "RPC Finished" task { let! _ = updateCompleted wallet tx.Result log "Getting Transaction and Token Balance" let! txInfo = rpcClient.GetTransactionAsync(tx.Result) let! tokenBalance = rpcClient.GetTokenAccountBalanceAsync(associatedTokenAccount) log $"Transaction Successful? {txInfo.WasSuccessful} - Token Balance {tokenBalance.Result.Value.AmountUlong}" if txInfo.WasSuccessful = true && tokenBalance.Result.Value.AmountUlong = amount then let! _ = updateVerified wallet tx.Result (int amount) () return () } |> Async.AwaitTask |> Async.Start else let msg = $"Got an error, check the tx id" updatePendingError wallet msg tx.Result |> Async.AwaitTask |> Async.Ignore |> Async.Start) |> Async.AwaitTask return () } let targetWallets = devConnStr |> Sql.connect |> Sql.query """ SELECT wallet_address FROM crypto WHERE status = 'Ready' AND total_dropped = 0; """ |> Sql.execute (fun reader -> reader.string "wallet_address") printfn $"Got target wallets: {targetWallets.Length}" let asyncs = targetWallets |> List.map executeDrop |> List.chunkBySize 5 async { for a in asyncs do let! _ = Async.Parallel a do! Async.Sleep 500 } |> Async.Start Console.ReadLine() |> ignore