Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
868825f
chore: Split business logic into service layer
jus1d Nov 1, 2025
6a3cd5d
chore: replace int32 to int
jus1d Nov 1, 2025
89aa391
chore: cleanup
jus1d Nov 1, 2025
a317e9a
chore: use params struct to services
jus1d Nov 1, 2025
69fa08f
chore: remove select * from sql queries
jus1d Nov 2, 2025
6a3eea7
chore: add migrations back, add ton package
jus1d Nov 2, 2025
f0cbec8
chore: Implement types for contests with prizes
jus1d Nov 4, 2025
d6d17d0
chore: Introduce tx manager
jus1d Nov 4, 2025
d2151a3
chore: add problem repo to txrepo
jus1d Nov 4, 2025
0dbe2d6
chore: improve fixes after review
jus1d Nov 4, 2025
1d151c4
chore: ton transfer: add comment optional
jus1d Nov 4, 2025
1645577
fix: incorrect participants count
jus1d Nov 4, 2025
b791a0f
chore: add creator_id and title filters for contest querying
jus1d Nov 4, 2025
832e29e
chore: add leaderboard view
jus1d Nov 5, 2025
de6ebeb
chore: factor out charcode calculation from repo layer
jus1d Nov 5, 2025
e4d7f18
chore: add some views
jus1d Nov 5, 2025
be0ba77
chore: cleanup views, prizes
jus1d Nov 5, 2025
33a5bce
migrations: split creating view out of tables
jus1d Nov 5, 2025
9016c27
chore: Add submission_details view
jus1d Nov 5, 2025
f8d891a
chore: add `award_type` to contest responses
jus1d Nov 5, 2025
330fff4
chore: add enum difficulty
jus1d Nov 5, 2025
7c132a0
chore: check txs to pay the entry
jus1d Nov 5, 2025
fe87802
chore: add tx hash, and entry price for the contest
jus1d Nov 5, 2025
b3b768d
chore: add entry price to create contest params
jus1d Nov 5, 2025
dc16613
chore: add `is_admitted` field to entry response
jus1d Nov 5, 2025
47d9b14
chore: add address to contest detailed response
jus1d Nov 5, 2025
efe166d
chore: cleanup omit empties
jus1d Nov 5, 2025
bf08f48
chore: forbid to view contest problem if entry is not paid
jus1d Nov 6, 2025
cd96f85
chore: add awards distributions
jus1d Nov 7, 2025
89299dd
chore: rename migration files
jus1d Nov 7, 2025
243703a
chore: Introduce payments table
jus1d Nov 7, 2025
66a60ba
chore: drop indecies
jus1d Nov 7, 2025
283c4f8
chore: Improve migrations, add indecies, make views materialized
jus1d Nov 7, 2025
bb6bc91
chore: Save wallet adresses as testnet, if config setted as testnet
jus1d Nov 7, 2025
2866e5b
chore: remove materialized views
jus1d Nov 7, 2025
5055f26
chore: remove GET /entry
jus1d Nov 8, 2025
3507533
chore: Improve contest response structure
jus1d Nov 8, 2025
376e4bd
chore: check for entry paid on get contest
jus1d Nov 8, 2025
e895509
chore: rename leaderboard to scores
jus1d Nov 8, 2025
27cacfb
chore: ignore .diff files
jus1d Nov 9, 2025
de382b3
fix: proper ignore .diff files
jus1d Nov 9, 2025
db1ff41
chore: add address to account/user response
jus1d Nov 9, 2025
9c8f0ad
chore: Introduce tonproof logic. again
jus1d Nov 11, 2025
4edd6bc
chore: replace liteapi with tonutils
jus1d Nov 11, 2025
9084169
chore: move implementation of abi.Executor to ton.Client
jus1d Nov 11, 2025
a4de79b
chore: hide tonconnect.Server inside of ton.Client
jus1d Nov 11, 2025
e746327
chore: Move tonproof logic into service
jus1d Nov 11, 2025
498b5c8
chore: add caching balance
jus1d Nov 11, 2025
9e7bb60
chore: remove logging from auth mw
jus1d Nov 11, 2025
2b846ca
chore: Implement mnemonic encryption
jus1d Nov 11, 2025
0a68ad6
test: cover ./pkg/ with tests
jus1d Nov 11, 2025
f5e231d
migrations: sucked into one master migration
jus1d Nov 12, 2025
c7b277e
chore: Add request_id to every error response
jus1d Nov 14, 2025
2a0722c
fix: correctly return is_admitted after payment
jus1d Nov 15, 2025
d670b3d
chore: Improve validation, error handling, and consistency across con…
jus1d Nov 23, 2025
d36a5cb
deps: Update `crypto` to 0.45.0
jus1d Nov 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ build
# Ignore TODO
TODO
buffer

migrations/data.up.sql

*.diff
19 changes: 14 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ require (
github.com/joho/godotenv v1.5.1
github.com/labstack/echo/v4 v4.13.3
github.com/redis/go-redis/v9 v9.12.1
github.com/stretchr/testify v1.10.0
github.com/tonkeeper/tongo v1.16.54
github.com/xssnick/tonutils-go v1.15.5
)

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
Expand All @@ -24,14 +29,18 @@ require (
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/snksoft/crc v1.1.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20230116083435-1de6713980de // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
Expand Down
32 changes: 22 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
Expand Down Expand Up @@ -43,33 +45,43 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/snksoft/crc v1.1.0 h1:HkLdI4taFlgGGG1KvsWMpz78PkOC9TkPVpTV/cuWn48=
github.com/snksoft/crc v1.1.0/go.mod h1:5/gUOsgAm7OmIhb6WJzw7w5g2zfJi4FrHYgGPdshE+A=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tonkeeper/tongo v1.16.54 h1:X8+VnC/gR/0+S1jlcY1hmvkL0wUrn5TEOyWL6uTHmd8=
github.com/tonkeeper/tongo v1.16.54/go.mod h1:MjgIgAytFarjCoVjMLjYEtpZNN1f2G/pnZhKjr28cWs=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
github.com/xssnick/tonutils-go v1.15.5 h1:yAcHnDaY5QW0aIQE47lT0PuDhhHYE+N+NyZssdPKR0s=
github.com/xssnick/tonutils-go v1.15.5/go.mod h1:3/B8mS5IWLTd1xbGbFbzRem55oz/Q86HG884bVsTqZ8=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20230116083435-1de6713980de h1:DBWn//IJw30uYCgERoxCg84hWtA97F4wMiKOIh00Uf0=
golang.org/x/exp v0.0.0-20230116083435-1de6713980de/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
101 changes: 101 additions & 0 deletions internal/app/distributor/distributor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package distributor

import (
"context"
"errors"
"fmt"
"log/slog"
"math/big"

"github.com/jackc/pgx/v5"
"github.com/voidcontests/api/internal/lib/crypto"
"github.com/voidcontests/api/internal/storage/models"
"github.com/voidcontests/api/internal/storage/repository"
"github.com/voidcontests/api/pkg/ton"
"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/tlb"
)

func New(r *repository.Repository, tc *ton.Client, cipher crypto.Cipher) func(ctx context.Context) error {
return func(ctx context.Context) error {
contests, err := r.Contest.GetWithUndistributedAwards(ctx)
if err != nil {
return err
}

for _, c := range contests {
err := distributeAwardForContest(ctx, r, tc, cipher, c)
if err != nil {
return err
}
}
return nil
}
}

func distributeAwardForContest(ctx context.Context, r *repository.Repository, tc *ton.Client, cipher crypto.Cipher, c models.Contest) error {
w, err := r.Contest.GetWallet(ctx, *c.WalletID)
if err != nil {
return err
}

decryptedMnemonic, err := cipher.Decrypt(w.MnemonicEncrypted)
if err != nil {
return fmt.Errorf("failed to decrypt mnemonic: %w", err)
}

wallet, err := tc.WalletWithSeed(decryptedMnemonic)
if err != nil {
return err
}

winnerID, err := r.Contest.GetWinnerID(ctx, c.ID)
if errors.Is(err, pgx.ErrNoRows) {
slog.Info("no users that submitted at least one ok solution", slog.Int("contest_id", c.ID))
return nil
}
if err != nil {
return err
}

winner, err := r.User.GetByID(ctx, winnerID)
if err != nil {
return err
}

if winner.Address == "" {
return fmt.Errorf("user has no wallet")
}

recepient, err := address.ParseAddr(winner.Address)
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name "recepient" is misspelled. It should be "recipient" (note the 'i' before 'e').

Copilot uses AI. Check for mistakes.
if err != nil {
return err
}

nanos, err := tc.GetBalance(ctx, wallet.Address())
if err != nil {
return err
}

// keep 2% for paying gas
factor := 1 - 0.02
amount := tlb.FromNanoTON(big.NewInt(int64(float64(nanos) * factor)))
tx, err := wallet.TransferTo(ctx, recepient, amount, fmt.Sprintf("contests.fckn.engineer: Prize for winning contest #%d", c.ID))
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name "recepient" is misspelled. It should be "recipient" (note the 'i' before 'e').

Copilot uses AI. Check for mistakes.
if err != nil {
return err
}

slog.Info("award distributed", slog.Int("contest_id", c.ID), slog.String("tx", tx))

paymentID, err := r.Payment.Create(ctx, tx, tc.GetAddress(wallet.Address()), tc.GetAddress(recepient), amount.Nano().Uint64(), false)
if err != nil {
return err
}

err = r.Contest.SetDistributionPaymentID(ctx, c.ID, paymentID)
if err != nil {
return err
}

return nil
}
Loading