diff --git a/Bot/InviteTracker.fs b/Bot/InviteTracker.fs index 2f44d41..0e9e26d 100644 --- a/Bot/InviteTracker.fs +++ b/Bot/InviteTracker.fs @@ -507,7 +507,8 @@ Keep an eye on <#{GuildEnvironment.channelAnnouncements}> for updates.""" else do! Messaging.sendFollowUpMessage ctx "⚠️ That's not a valid Solana address, please try again" do! Analytics.invalidWalletSubmit (ctx.GetDiscordMember()) - with _ -> + with ex -> + printfn $"{ex.Message}" do! Messaging.sendFollowUpMessage ctx "⚠️ That's not a valid Solana address, please try again" do! Analytics.invalidWalletSubmit (ctx.GetDiscordMember()) }) diff --git a/Bot/Scripts/Airdrop.fsx b/Bot/Scripts/Airdrop.fsx index 62617ab..96b8969 100644 --- a/Bot/Scripts/Airdrop.fsx +++ b/Bot/Scripts/Airdrop.fsx @@ -14,15 +14,21 @@ 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/devnet.json") +let file = File.ReadAllText("/home/joe/.config/solana/DegenzW7kWzac5zEdTWEuqVasoVMPKd16T3za2j6S3qR.json") let authority = keystore.RestoreKeystore(file) -//let rpcClient = ClientFactory.GetClient("https://still-empty-field.solana-mainnet.quiknode.pro/5b1b6b5c913ec79a20bef19d5ba5f63023e470d6/") -let rpcClient = ClientFactory.GetClient(Cluster.DevNet) +//let rpcClient = ClientFactory.GetClient(Cluster.DevNet) +let rpcClient = ClientFactory.GetClient(rpcClientUrl) -let mintAccount = PublicKey("2iS6gcoB5VhiLC4eNB7NdcaLgEHjLrXHYpz7T2JMGBDw") +//let mintAccount = PublicKey("2iS6gcoB5VhiLC4eNB7NdcaLgEHjLrXHYpz7T2JMGBDw") +let mintAccount = PublicKey("4gN1u9LNBnvFERpDU3SrMusMJ1gKLbE9XB6EYraQkWTf") +//let ataAccount = PublicKey("CRa7GCMUaB4np32XoTD2sEyCXFVfYKKF4JRPkQTwV3EY") +let sourceAtaAccount = PublicKey("7CB459UJC3qtTYJzfC8bFNvdycQGXTPzKoEHxys8Ytfs") //let associatedTokenAccountOwner = PublicKey("GutKESfJw8PDMbFVqByxTr4f5TUSHUVmkf5gtsWWWqrU") type AirdropStatus = @@ -93,7 +99,6 @@ let getTokenCountToDrop wallet = return (if wl.HasWhitelist then 1uL else 0uL) + (if wl.HasOg then 2uL else 0uL) } -// TODO: Change was_successful to status and check if attempted, pending, errored, or completed let executeDrop (wallet : string) = let associatedTokenAccountOwner = PublicKey(wallet) let associatedTokenAccount = AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(associatedTokenAccountOwner, mintAccount) @@ -109,33 +114,33 @@ let executeDrop (wallet : string) = PublicKey(targetWallet), mintAccount)) .AddInstruction(TokenProgram.Transfer( - PublicKey("CRa7GCMUaB4np32XoTD2sEyCXFVfYKKF4JRPkQTwV3EY"), + sourceAtaAccount, associatedTokenAccount, amount, authority.Account)) .Build(authority.Account); - task { -// let streamingClient = ClientFactory.GetStreamingClient("wss://still-empty-field.solana-mainnet.quiknode.pro/5b1b6b5c913ec79a20bef19d5ba5f63023e470d6/") - let streamingClient = ClientFactory.GetStreamingClient(Cluster.DevNet) - do! streamingClient.ConnectAsync() + 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 + let! amount = getTokenCountToDrop wallet |> Async.AwaitTask log $"Dropping {amount} tokens" let tx = buildTransaction blockHash.Result.Value amount wallet - let! tx = rpcClient.SendTransactionAsync(tx) + let! tx = rpcClient.SendTransactionAsync(tx) |> Async.AwaitTask if tx.ErrorData <> null then - let! _ = updateError wallet (tx.ErrorData.Logs |> String.concat "\n") + 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 + let! _ = updateError wallet msg |> Async.AwaitTask log $"Odd Transaction Error" return () else log $"Successful, now waiting for RPC" - let! _ = updatePending wallet tx.Result + let! _ = updatePending wallet tx.Result |> Async.AwaitTask let! _ = streamingClient.SubscribeSignatureAsync(tx.Result, fun sub result -> if result.Value.Error = null then log "RPC Finished" @@ -152,8 +157,8 @@ let executeDrop (wallet : string) = return () } |> Async.AwaitTask |> Async.Start else - let msg = $"Got an error, let's check it out {result.Value.Error}" - updatePendingError wallet msg tx.Result |> Async.AwaitTask |> Async.Ignore |> Async.Start) + let msg = $"Got an error, check the tx id" + updatePendingError wallet msg tx.Result |> Async.AwaitTask |> Async.Ignore |> Async.Start) |> Async.AwaitTask return () } @@ -161,19 +166,22 @@ let targetWallets = devConnStr |> Sql.connect |> Sql.query """ - SELECT wallet_address FROM crypto WHERE status = 'Ready'; + 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}" -// "GK7rkZYrdAEpTm9n9TkHWK1T5nDXeRfVUVfcHQwSDyuJ" let asyncs = targetWallets |> List.map executeDrop - |> List.map Async.AwaitTask - -Async.Parallel ( asyncs , 16 ) |> Async.StartChild + |> List.chunkBySize 5 + +async { + for a in asyncs do + let! _ = Async.Parallel a + do! Async.Sleep 500 +} |> Async.Start Console.ReadLine() |> ignore diff --git a/Bot/Scripts/Whitelist.fsx b/Bot/Scripts/Whitelist.fsx index 8d7fe72..fb947cb 100644 --- a/Bot/Scripts/Whitelist.fsx +++ b/Bot/Scripts/Whitelist.fsx @@ -2,6 +2,7 @@ open Npgsql.FSharp open DSharpPlus +open DSharpPlus.Entities open dotenv.net let prodEnv = DotEnv.Read(DotEnvOptions(envFilePaths = [ "./.prod.env" ])) @@ -27,6 +28,26 @@ printfn "Getting Members" let users = guild.GetAllMembersAsync() |> Async.AwaitTask |> Async.RunSynchronously printfn $"Total Members: {users.Count}" +let firstDrop = + users + |> Seq.filter (fun u -> u.Roles |> Seq.exists (fun role -> + role.Id = 978229149569286174uL)) + |> Seq.distinct + |> Seq.toList + +printfn $"{firstDrop.Length}" + +let changeStatus ids = + devConnStr + |> Sql.connect + |> Sql.parameters [ "ids" , Sql.stringArray ids ] + |> Sql.query """ + UPDATE crypto SET status = 'Ready' WHERE discord_id = ANY (ARRAY[@ids]::varchar[]) AND status = 'Nothing' + """ + |> Sql.executeNonQuery + +changeStatus (firstDrop |> List.map (fun m -> string m.Id) |> List.toArray) + let whitelisted = users |> Seq.filter (fun u -> u.Roles |> Seq.exists (fun role -> role.Name.Contains("Confirmed"))) @@ -52,22 +73,75 @@ let insert did name wallet wl og = "wl" , Sql.bool wl ; "og" , Sql.bool og ] |> Sql.query """ INSERT INTO crypto(discord_id, display_name, wallet_address, has_wl, has_og) VALUES (@did, @name, @wallet, @wl, @og) - ON CONFLICT (wallet_address) DO - UPDATE SET display_name = @name, discord_id = @did , has_wl = @wl , has_og = @og WHERE crypto.wallet_address = @wallet; + ON CONFLICT (discord_id) DO + UPDATE SET wallet_address = @wallet, has_wl = @wl , has_og = @og WHERE crypto.discord_id = @did AND crypto.status = 'Nothing'; """ |> Sql.executeNonQuery +let updateUserWithWallet wallet wl og = + devConnStr + |> Sql.connect + |> Sql.parameters [ "wallet" , Sql.string wallet ; "wl" , Sql.bool wl ; "og" , Sql.bool og ] + |> Sql.query """ + UPDATE crypto SET has_wl = @wl, has_og = @og WHERE crypto.wallet_address = @wallet AND crypto.status = 'Nothing'; + """ + |> Sql.executeNonQuery + +let prune ids = + devConnStr + |> Sql.connect + |> Sql.parameters [ "ids" , Sql.stringArray ids ] + |> Sql.query """ + DELETE FROM crypto WHERE discord_id = ANY (ARRAY[@ids]::varchar[]) AND status = 'Nothing' + """ + |> Sql.executeNonQuery + +let getExisting () = + devConnStr + |> Sql.connect + |> Sql.query """ + SELECT discord_id, display_name, wallet_address FROM crypto + """ + |> Sql.execute (fun reader -> {| Id = reader.string "discord_id" |> uint64 ; Name = reader.string "display_name" ; Wallet = reader.string "wallet_address" |}) + +let walletExists wallet = + let list = + devConnStr + |> Sql.connect + |> Sql.parameters [ "wallet" , Sql.string wallet ] + |> Sql.query """ + SELECT wallet_address FROM crypto WHERE wallet_address = @wallet + """ + |> Sql.execute (fun reader -> reader.string "wallet_address") + not list.IsEmpty + printfn "Inserting" async { for user in whitelisted do - for wa in walletAddresses do - if wa.Id = user.Id then - let hasWL = user.Roles |> Seq.exists (fun role -> role.Name = "Whitelist Confirmed") - let hasOG = user.Roles |> Seq.exists (fun role -> role.Name = "OG Whitelist Confirmed") - try - let _ = insert wa.Id wa.Name wa.Wallet hasWL hasOG - () - with ex -> printfn $"{ex.Message}" + for wa in walletAddresses do + if wa.Id = user.Id then + let hasWL = user.Roles |> Seq.exists (fun role -> role.Name = "Whitelist Confirmed") + let hasOG = user.Roles |> Seq.exists (fun role -> role.Name = "OG Whitelist Confirmed") + try + if walletExists wa.Wallet then + let _ = updateUserWithWallet wa.Wallet hasWL hasOG + () + else + let _ = insert wa.Id wa.Name wa.Wallet hasWL hasOG + () + with ex -> printfn $"Huh? {ex.Message}" + () + () + let existing = getExisting () + let usersToFilter = + existing |> List.filter (fun wa -> (whitelisted |> List.exists (fun wl -> wl.Id = wa.Id)) |> not) + let toPrune = usersToFilter |> List.map (fun u -> u.Id |> string) |> List.toArray + let _ = prune toPrune + if toPrune.Length > 0 then + printfn $"Pruning {toPrune.Length} users: {toPrune}" + let existing = getExisting () + printfn $"Total Users: {existing.Length}" + } |> Async.RunSynchronously bot.DisconnectAsync() |> Async.AwaitTask |> Async.RunSynchronously \ No newline at end of file