-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7c15e6a
Showing
6 changed files
with
392 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<p align="center" style="color: #444"> | ||
<h1 align="center">🍭 Jetton Mass Sender</h1> | ||
</p> | ||
<p align="center" style="font-size: 1.2rem;">Send TON Jettons Easily!</p> | ||
|
||
1. Prepare json file with the following structure: | ||
|
||
```json | ||
[ | ||
{ | ||
"amount": "65089.333268244", | ||
"address": "UQAZF-cErbXnXbSTDJCFM3k5GI4dqFh5NSzgIT7tIMK5rSOX" | ||
}, | ||
{ | ||
"amount": "1731376.26493529", | ||
"address": "UQBCSonWNi0mVwHdxord0JnUfjzDCAJlCIsas2fBmR1p00tk" | ||
}, | ||
{ | ||
"amount": "2169644.44227224", | ||
"address": "UQDXOSJeAPITOr7NrdmqXRALMzskzKo087qCb-0V3ZrKFrjs" | ||
} | ||
] | ||
``` | ||
|
||
2. Clone Repository | ||
|
||
```bash | ||
git clone [email protected]:qpwedev/jetton-mass-sender.git; cd jetton-mass-sender; | ||
``` | ||
|
||
3. [Install golang](https://go.dev/doc/install) | ||
4. Setup Jetton Sender | ||
|
||
```bash | ||
go run src/main.go src/cli.go src/highload.go setup | ||
``` | ||
|
||
5. Run Jetton Sender | ||
|
||
```bash | ||
go run src/main.go src/cli.go src/highload.go | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
module github.com/tonft-app/highload-wallet-server | ||
|
||
go 1.20 | ||
|
||
require ( | ||
github.com/joho/godotenv v1.5.1 | ||
github.com/xssnick/tonutils-go v1.8.8 | ||
) | ||
|
||
require ( | ||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||
github.com/charmbracelet/lipgloss v0.10.0 // indirect | ||
github.com/charmbracelet/log v0.4.0 // indirect | ||
github.com/go-logfmt/logfmt v0.6.0 // indirect | ||
github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | ||
github.com/mattn/go-isatty v0.0.18 // indirect | ||
github.com/mattn/go-runewidth v0.0.15 // indirect | ||
github.com/muesli/reflow v0.3.0 // indirect | ||
github.com/muesli/termenv v0.15.2 // indirect | ||
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect | ||
github.com/rivo/uniseg v0.4.7 // indirect | ||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect | ||
github.com/spf13/cobra v1.8.0 // indirect | ||
github.com/spf13/pflag v1.0.5 // indirect | ||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 // indirect | ||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect | ||
golang.org/x/sys v0.13.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= | ||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= | ||
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= | ||
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= | ||
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= | ||
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= | ||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= | ||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= | ||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | ||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | ||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= | ||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= | ||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | ||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= | ||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= | ||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= | ||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= | ||
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY= | ||
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= | ||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= | ||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= | ||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= | ||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= | ||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||
github.com/xssnick/tonutils-go v1.8.8 h1:D9LauvmIY6HZAXNMfSL+d9xTgZCBDPBwm1FF6aH+k+4= | ||
github.com/xssnick/tonutils-go v1.8.8/go.mod h1:rqfQ4jsLaFhUUvouz2hTTC02nQGszOhSps7tGAKRC8g= | ||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s= | ||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= | ||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= | ||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= | ||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= | ||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"github.com/charmbracelet/log" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
// Configuration stores the user's settings | ||
type Configuration struct { | ||
SeedPhrase string `json:"seedPhrase"` | ||
JettonMasterAddress string `json:"jettonMasterAddress"` | ||
Commentary string `json:"commentary"` | ||
MessageEntryFilename string `json:"messageEntryFilename"` | ||
} | ||
|
||
const configFilePath = "config.json" | ||
|
||
var rootCmd = &cobra.Command{ | ||
Use: "toncli", | ||
Short: "TON CLI application for managing transactions", | ||
Run: runMainLogic, | ||
} | ||
|
||
var setupCmd = &cobra.Command{ | ||
Use: "setup", | ||
Short: "Set up or update the application configuration", | ||
Run: func(cmd *cobra.Command, args []string) { setupConfiguration() }, | ||
} | ||
|
||
|
||
func runMainLogic(cmd *cobra.Command, args []string) { | ||
config, err := loadConfiguration(configFilePath) | ||
if err != nil { | ||
fmt.Println("Configuration not found or invalid. Please run `setup` command.") | ||
return | ||
} | ||
fmt.Println("Running main logic with current configuration...") | ||
|
||
massSender(config.SeedPhrase, config.JettonMasterAddress, config.Commentary, config.MessageEntryFilename) | ||
} | ||
|
||
func setupConfiguration() { | ||
reader := bufio.NewReader(os.Stdin) | ||
|
||
fmt.Println("Enter Seed Phrase (From Any Wallet):") | ||
seedPhrase, _ := reader.ReadString('\n') | ||
|
||
fmt.Println("Enter Token Address:") | ||
jettonMasterAddress, _ := reader.ReadString('\n') | ||
|
||
fmt.Println("Enter Commentary:") | ||
commentary, _ := reader.ReadString('\n') | ||
|
||
fmt.Println("Enter Message Entry Filename:") | ||
messageEntryFilename, _ := reader.ReadString('\n') | ||
|
||
config := Configuration{ | ||
SeedPhrase: seedPhrase, | ||
JettonMasterAddress: jettonMasterAddress, | ||
Commentary: commentary, | ||
MessageEntryFilename: messageEntryFilename, | ||
} | ||
if err := saveConfiguration(config, configFilePath); err != nil { | ||
log.Fatalf("Failed to save configuration: %v", err) | ||
} | ||
|
||
fmt.Println("Configuration saved successfully.") | ||
} | ||
|
||
|
||
func saveConfiguration(config Configuration, filePath string) error { | ||
// Trim whitespace from the configuration values | ||
config.SeedPhrase = strings.TrimSpace(config.SeedPhrase) | ||
config.JettonMasterAddress = strings.TrimSpace(config.JettonMasterAddress) | ||
config.Commentary = strings.TrimSpace(config.Commentary) | ||
config.MessageEntryFilename = strings.TrimSpace(config.MessageEntryFilename) | ||
|
||
file, err := os.Create(filePath) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
encoder := json.NewEncoder(file) | ||
return encoder.Encode(config) | ||
} | ||
|
||
func loadConfiguration(filePath string) (Configuration, error) { | ||
var config Configuration | ||
file, err := os.Open(filePath) | ||
if err != nil { | ||
return config, err | ||
} | ||
defer file.Close() | ||
|
||
decoder := json.NewDecoder(file) | ||
err = decoder.Decode(&config) | ||
return config, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"encoding/json" | ||
"os" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/charmbracelet/log" | ||
|
||
"github.com/xssnick/tonutils-go/address" | ||
"github.com/xssnick/tonutils-go/liteclient" | ||
"github.com/xssnick/tonutils-go/tlb" | ||
"github.com/xssnick/tonutils-go/ton" | ||
"github.com/xssnick/tonutils-go/ton/jetton" | ||
"github.com/xssnick/tonutils-go/ton/wallet" | ||
) | ||
|
||
type MessageEntry struct { | ||
Amount string `json:"amount"` | ||
Address string `json:"address"` | ||
} | ||
|
||
func massSender(seedPhrase string, jettonMasterAddress string, commentary string, messageEntryFilename string) { | ||
client := liteclient.NewConnectionPool() | ||
|
||
// Connect to mainnet lite server | ||
err := client.AddConnection(context.Background(), "135.181.140.212:13206", "K0t3+IWLOXHYMvMcrGZDPs+pn58a17LFbnXoQkKc2xw=") | ||
if err != nil { | ||
log.Error("connection err:", err.Error()) | ||
return | ||
} | ||
|
||
ctx := client.StickyContext(context.Background()) | ||
api := ton.NewAPIClient(client, ton.ProofCheckPolicyFast).WithRetry() | ||
|
||
words := strings.Split(seedPhrase, " ") | ||
|
||
log.Infof("Seed words: %s", words) | ||
|
||
// Initialize highload wallet | ||
w, err := wallet.FromSeed(api, words, wallet.HighloadV2R2) | ||
if err != nil { | ||
log.Error("FromSeed err:", err.Error()) | ||
return | ||
} | ||
|
||
log.Infof("Wallet address: %s", w.WalletAddress()) | ||
|
||
|
||
block, err := api.CurrentMasterchainInfo(context.Background()) | ||
if err != nil { | ||
log.Error("CurrentMasterchainInfo err:", err.Error()) | ||
return | ||
} | ||
|
||
balance, err := w.GetBalance(context.Background(), block) | ||
if err != nil { | ||
log.Error("GetBalance err:", err.Error()) | ||
return | ||
} | ||
|
||
// Read message entries from file | ||
data, err := os.ReadFile(messageEntryFilename) | ||
if err != nil { | ||
log.Error("Error reading file:", err.Error()) | ||
return | ||
} | ||
|
||
var messages []MessageEntry | ||
err = json.Unmarshal(data, &messages) | ||
if err != nil { | ||
log.Error("Error unmarshalling JSON:", err.Error()) | ||
return | ||
} | ||
|
||
// if balance < len(messages) * 0.05 + their amount then exit | ||
sum := 0.0 | ||
for _, msg := range messages { | ||
// from string to float | ||
amount, err := strconv.ParseFloat(msg.Amount, 64) | ||
if err != nil { | ||
log.Error("Error parsing amount:", err.Error()) | ||
return | ||
} | ||
|
||
sum += amount | ||
} | ||
|
||
if float64(balance.Nano().Int64()) < (float64(len(messages)) * 5e7) + sum { | ||
log.Error("Not enough balance to send all messages") | ||
return | ||
} | ||
|
||
// Initialize token wallet | ||
token := jetton.NewJettonMasterClient(api, address.MustParseAddr(jettonMasterAddress)) | ||
jettonWallet, err := token.GetJettonWallet(ctx, w.WalletAddress()) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
jettonBalance, err := jettonWallet.GetBalance(ctx) | ||
if err != nil { | ||
log.Error("GetBalance err:", err.Error()) | ||
return | ||
} | ||
|
||
log.Infof("Balance: %s jettons", jettonBalance) | ||
|
||
|
||
// Start sending messages in batches | ||
const batchSize = 100 | ||
for i := 0; i < len(messages); i += batchSize { | ||
end := i + batchSize | ||
if end > len(messages) { | ||
end = len(messages) | ||
} | ||
|
||
batch := messages[i:end] | ||
var walletMessages []*wallet.Message | ||
for _, msg := range batch { | ||
log.Printf(msg.Address, msg.Amount) | ||
amountTokens := tlb.MustFromDecimal(msg.Amount, 9) | ||
comment, err := wallet.CreateCommentCell(commentary) | ||
if err != nil { | ||
log.Error("Error creating comment cell:", err.Error()) | ||
log.Fatal(err) | ||
} | ||
to := address.MustParseAddr(msg.Address) | ||
transferPayload, err := jettonWallet.BuildTransferPayload(to, amountTokens, tlb.ZeroCoins, comment) | ||
if err != nil { | ||
log.Error("Error building transfer payload:", err.Error()) | ||
log.Fatal(err) | ||
} | ||
walletMsg := wallet.SimpleMessage(jettonWallet.Address(), tlb.MustFromTON("0.05"), transferPayload) | ||
walletMessages = append(walletMessages, walletMsg) | ||
} | ||
|
||
log.Infof("Sending transaction and waiting for confirmation for batch starting from index %s...") | ||
|
||
txHash, err := w.SendManyWaitTxHash(ctx, walletMessages) | ||
if err != nil { | ||
log.Error("Transfer err:", err.Error()) | ||
return | ||
} | ||
|
||
log.Infof("Batch transaction sent, hash: %s", base64.StdEncoding.EncodeToString(txHash)) | ||
log.Infof("Explorer link: https://tonscan.org/tx/ %s", base64.URLEncoding.EncodeToString(txHash)) | ||
|
||
log.Info("Sleeping for 30 seconds...") | ||
time.Sleep(30 * time.Second) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package main | ||
|
||
import "github.com/charmbracelet/log" | ||
|
||
func main() { | ||
rootCmd.AddCommand(setupCmd) | ||
if err := rootCmd.Execute(); err != nil { | ||
log.Fatalf("Error executing rootCmd: %v", err) | ||
} | ||
} |