Skip to content

Commit 1a2082a

Browse files
authored
Gnolinker subscribe mvp (#67)
* feat: integrate indexer tooling * chore: cleanups * fix: cleanup code * fix: fomratting for graphql * chore: wip fly.toml * feat: adding autosync and indexing to discord bot * fix: testing
1 parent 04395c7 commit 1a2082a

File tree

31 files changed

+4089
-68
lines changed

31 files changed

+4089
-68
lines changed

projects/gnoland/gno.land/r/linker000/discord/user/v0/user.gno

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ func UnlinkByCaller(cur realm) {
4545
std.Emit("UserUnlinked",
4646
"address", caller.String(),
4747
"discordID", claim.discordID,
48-
"triggeredBy", "UnlinkByCaller",
4948
)
5049
}
5150

@@ -69,7 +68,6 @@ func UnlinkByClaim(cur realm, encodedClaim string) {
6968
std.Emit("UserUnlinked",
7069
"address", existingClaim.addr.String(),
7170
"discordID", claim.discordID,
72-
"triggeredBy", "UnlinkByClaim",
7371
)
7472
}
7573

projects/gnoland/tx-indexer/fly.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# fly.toml app configuration file generated for labsnet-txindexer-3342 on 2025-07-16T15:21:46-06:00
1+
# fly.toml app configuration file generated for labsnet-txindexer-3342 on 2025-07-17T09:09:24-06:00
22
#
33
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
44
#

projects/gnolinker/Makefile

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: dev clean down test security-scan
1+
.PHONY: dev clean down test security-scan pull-data
22

33
clean: down
44
docker-compose down -v --remove-orphans
@@ -16,3 +16,19 @@ test:
1616
security-scan:
1717
@which gosec > /dev/null || (echo "Installing gosec..." && go install github.com/securego/gosec/v2/cmd/gosec@latest)
1818
gosec ./...
19+
20+
pull-data:
21+
@echo "Checking AWS CLI availability..."
22+
@which aws > /dev/null || (echo "Error: AWS CLI not found. Please install it first." && exit 1)
23+
@echo "Exporting data from MinIO..."
24+
@mkdir -p data/export
25+
@AWS_ACCESS_KEY_ID=minioadmin \
26+
AWS_SECRET_ACCESS_KEY=minioadmin123 \
27+
AWS_REGION=us-east-1 \
28+
aws s3 sync s3://gnolinker-data data/export/ \
29+
--endpoint-url http://localhost:9000 \
30+
--no-verify-ssl \
31+
--delete || (echo "Error: Failed to sync data. Make sure MinIO is running (make dev)" && exit 1)
32+
@echo "✅ Data exported to data/export/"
33+
@echo "📁 Contents:"
34+
@find data/export -type f 2>/dev/null | head -20 || echo " (no files found)"

projects/gnolinker/cmd/discord/discord.go

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"encoding/hex"
66
"flag"
77
"os"
8+
"strconv"
9+
"strings"
810

911
"github.com/allinbits/labs/projects/gnolinker/core"
1012
"github.com/allinbits/labs/projects/gnolinker/core/config"
@@ -16,14 +18,16 @@ import (
1618
func Run() {
1719
// Command line flags
1820
var (
19-
tokenFlag = flag.String("token", "", "Discord bot token")
20-
signingKeyFlag = flag.String("signing-key", "", "Hex encoded signing key")
21-
rpcURLFlag = flag.String("rpc-url", "https://rpc.gno.land:443", "Gno RPC URL")
22-
baseURLFlag = flag.String("base-url", "https://gno.land", "Base URL for claim links")
23-
userContractFlag = flag.String("user-contract", "r/linker000/discord/user/v0", "User contract path")
24-
roleContractFlag = flag.String("role-contract", "r/linker000/discord/role/v0", "Role contract path")
25-
logLevelFlag = flag.String("log-level", "info", "Log level (debug, info, warn, error)")
26-
cleanupFlag = flag.Bool("cleanup-commands", false, "Remove all existing slash commands on startup")
21+
tokenFlag = flag.String("token", "", "Discord bot token")
22+
signingKeyFlag = flag.String("signing-key", "", "Hex encoded signing key")
23+
rpcURLFlag = flag.String("rpc-url", "https://rpc.gno.land:443", "Gno RPC URL")
24+
baseURLFlag = flag.String("base-url", "https://gno.land", "Base URL for claim links")
25+
userContractFlag = flag.String("user-contract", "r/linker000/discord/user/v0", "User contract path")
26+
roleContractFlag = flag.String("role-contract", "r/linker000/discord/role/v0", "Role contract path")
27+
logLevelFlag = flag.String("log-level", "info", "Log level (debug, info, warn, error)")
28+
cleanupFlag = flag.Bool("cleanup-commands", false, "Remove all existing slash commands on startup")
29+
graphqlEndpointFlag = flag.String("graphql-endpoint", "", "GraphQL HTTP endpoint for event monitoring")
30+
enableEventMonitorFlag = flag.Bool("enable-event-monitoring", false, "Enable real-time event monitoring")
2731
)
2832
flag.Parse()
2933

@@ -49,6 +53,8 @@ func Run() {
4953
baseURL := getEnvOrFlag("GNOLINKER__BASE_URL", *baseURLFlag)
5054
userContract := getEnvOrFlag("GNOLINKER__USER_CONTRACT", *userContractFlag)
5155
roleContract := getEnvOrFlag("GNOLINKER__ROLE_CONTRACT", *roleContractFlag)
56+
graphqlEndpoint := getEnvOrFlag("GNOLINKER__GRAPHQL_ENDPOINT", *graphqlEndpointFlag)
57+
enableEventMonitoring := getEnvOrBool("GNOLINKER__ENABLE_EVENT_MONITORING", *enableEventMonitorFlag)
5258

5359
// Validate required parameters
5460
if token == "" {
@@ -63,6 +69,15 @@ func Run() {
6369
// Roles are now managed per-guild by ConfigManager
6470
storageConfig := configManager.GetStorageConfig()
6571
logger.Info("Roles will be managed per-guild", "auto_create_roles", storageConfig.AutoCreateRoles, "default_verified_role_name", storageConfig.DefaultVerifiedRoleName)
72+
73+
// Log GraphQL event monitoring configuration
74+
if enableEventMonitoring && graphqlEndpoint != "" {
75+
logger.Info("GraphQL event monitoring enabled with polling", "endpoint", graphqlEndpoint)
76+
} else if enableEventMonitoring && graphqlEndpoint == "" {
77+
logger.Warn("Event monitoring enabled but no GraphQL endpoint specified")
78+
} else {
79+
logger.Info("GraphQL event monitoring disabled")
80+
}
6681

6782
// Decode signing key
6883
signingKeyBytes, err := hex.DecodeString(signingKeyStr)
@@ -80,8 +95,10 @@ func Run() {
8095

8196
// Create Discord config - roles are now managed by ConfigManager
8297
discordConfig := discord.Config{
83-
Token: token,
84-
CleanupOldCommands: *cleanupFlag,
98+
Token: token,
99+
CleanupOldCommands: *cleanupFlag,
100+
GraphQLEndpoint: graphqlEndpoint,
101+
EnableEventMonitoring: enableEventMonitoring,
85102
// Remove hard-coded roles - these will be managed dynamically per guild
86103
}
87104

@@ -131,3 +148,21 @@ func getEnvOrFlag(envVar, flagValue string) string {
131148
}
132149
return flagValue
133150
}
151+
152+
func getEnvOrBool(envVar string, flagValue bool) bool {
153+
if envValue := os.Getenv(envVar); envValue != "" {
154+
// Parse boolean from environment variable
155+
switch strings.ToLower(envValue) {
156+
case "true", "1", "yes", "on":
157+
return true
158+
case "false", "0", "no", "off":
159+
return false
160+
default:
161+
// If invalid value, try to parse as boolean
162+
if parsed, err := strconv.ParseBool(envValue); err == nil {
163+
return parsed
164+
}
165+
}
166+
}
167+
return flagValue
168+
}

projects/gnolinker/cmd/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ Environment variables:
7474
GNOLINKER__DISCORD_TOKEN, GNOLINKER__SIGNING_KEY
7575
GNOLINKER__GNOLAND_RPC_ENDPOINT, GNOLINKER__BASE_URL
7676
GNOLINKER__LOG_LEVEL (debug, info, warn, error)
77+
GNOLINKER__GRAPHQL_ENDPOINT, GNOLINKER__ENABLE_EVENT_MONITORING
7778
7879
Storage configuration (GNOLINKER__ prefix):
7980
GNOLINKER__STORAGE_TYPE (memory, s3)

projects/gnolinker/core/config/manager.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ func (m *ConfigManager) GetLockManager() lock.LockManager {
9292
return m.lockManager
9393
}
9494

95+
// GetStore returns the storage store
96+
func (m *ConfigManager) GetStore() storage.ConfigStore {
97+
return m.store
98+
}
99+
95100
// UpdateGuildConfig updates a guild configuration
96101
func (m *ConfigManager) UpdateGuildConfig(guildID string, config *storage.GuildConfig) error {
97102
return m.store.Set(guildID, config)

projects/gnolinker/core/contracts/client.go

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
type GnoClient struct {
1616
client gnoclient.Client
1717
config ClientConfig
18+
logger core.Logger
1819
}
1920

2021
// ClientConfig holds the contract configuration
@@ -34,22 +35,35 @@ func NewGnoClient(config ClientConfig) (*GnoClient, error) {
3435
client := gnoclient.Client{
3536
RPCClient: rpcClient,
3637
}
38+
39+
// Create a logger for the client
40+
logger := core.NewSlogLogger(core.ParseLogLevel("info"))
3741

3842
return &GnoClient{
3943
client: client,
4044
config: config,
45+
logger: logger,
4146
}, nil
4247
}
4348

4449
// GetLinkedAddress returns the Gno address linked to a platform user ID
4550
func (c *GnoClient) GetLinkedAddress(platformID string) (string, error) {
4651
query := fmt.Sprintf(`GetLinkedAddress("%v")`, platformID)
47-
result, _, err := c.client.QEval("gno.land/"+c.config.UserContract, query)
52+
contractPath := "gno.land/" + c.config.UserContract
53+
54+
c.logger.Debug("Querying GetLinkedAddress", "platform_id", platformID, "contract", contractPath, "query", query)
55+
56+
result, _, err := c.client.QEval(contractPath, query)
4857
if err != nil {
58+
c.logger.Error("GetLinkedAddress query failed", "error", err, "platform_id", platformID, "contract", contractPath)
4959
return "", fmt.Errorf("failed to get linked address: %w", err)
5060
}
51-
fmt.Println("GetLinkedAddress result:", result)
52-
return parseGnoAddress(result), nil
61+
62+
c.logger.Info("GetLinkedAddress result", "platform_id", platformID, "raw_result", result)
63+
address := parseGnoAddress(result)
64+
c.logger.Info("GetLinkedAddress parsed", "platform_id", platformID, "address", address)
65+
66+
return address, nil
5367
}
5468

5569
// GetLinkedRole returns the role mapping for a specific realm role
@@ -65,22 +79,47 @@ func (c *GnoClient) GetLinkedRole(realmPath, roleName, platformGuildID string) (
6579
// ListLinkedRoles returns all role mappings for a realm
6680
func (c *GnoClient) ListLinkedRoles(realmPath, platformGuildID string) ([]*core.RoleMapping, error) {
6781
query := fmt.Sprintf(`ListLinkedRolesJSON("%v", "%v")`, realmPath, platformGuildID)
68-
result, _, err := c.client.QEval("gno.land/"+c.config.RoleContract, query)
82+
contractPath := "gno.land/" + c.config.RoleContract
83+
84+
c.logger.Debug("Querying ListLinkedRoles", "realm_path", realmPath, "guild_id", platformGuildID, "contract", contractPath, "query", query)
85+
86+
result, _, err := c.client.QEval(contractPath, query)
6987
if err != nil {
88+
c.logger.Error("ListLinkedRoles query failed", "error", err, "realm_path", realmPath, "guild_id", platformGuildID, "contract", contractPath)
7089
return nil, fmt.Errorf("failed to list linked roles: %w", err)
7190
}
72-
return parseLinkedRoles(result)
91+
92+
c.logger.Info("ListLinkedRoles result", "realm_path", realmPath, "guild_id", platformGuildID, "raw_result", result)
93+
94+
roles, err := parseLinkedRoles(result)
95+
if err != nil {
96+
c.logger.Error("Failed to parse linked roles", "error", err, "raw_result", result)
97+
return nil, err
98+
}
99+
100+
c.logger.Info("ListLinkedRoles parsed", "realm_path", realmPath, "guild_id", platformGuildID, "role_count", len(roles))
101+
102+
return roles, nil
73103
}
74104

75105
// HasRole checks if an address has a specific role in the realm
76106
func (c *GnoClient) HasRole(realmPath, roleName, address string) (bool, error) {
77107
query := fmt.Sprintf(`HasRole("%v", "%v")`, roleName, address)
108+
109+
c.logger.Debug("Querying HasRole", "realm_path", realmPath, "role_name", roleName, "address", address, "query", query)
110+
78111
result, _, err := c.client.QEval(realmPath, query)
79112
if err != nil {
113+
c.logger.Error("HasRole query failed", "error", err, "realm_path", realmPath, "role_name", roleName, "address", address)
80114
return false, fmt.Errorf("failed to check role membership: %w", err)
81115
}
82-
fmt.Println("HasRole result:", result)
83-
return result == "(true bool)", nil
116+
117+
c.logger.Info("HasRole result", "realm_path", realmPath, "role_name", roleName, "address", address, "raw_result", result)
118+
119+
isMember := result == "(true bool)"
120+
c.logger.Info("HasRole parsed", "realm_path", realmPath, "role_name", roleName, "address", address, "is_member", isMember)
121+
122+
return isMember, nil
84123
}
85124

86125
// Parsing functions (same as before)

0 commit comments

Comments
 (0)