189 lines
7.4 KiB
Plaintext

#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