Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
138 changes: 88 additions & 50 deletions api/v1_create_reward_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ import (
"go.uber.org/zap"
)

// rewardPoolDeadlineWindow is the number of blocks ahead of the current
// height at which we set the deadline_block_height on cometbft tx
// envelopes that this server originates (CreateRewardPool, CreateReward).
// Cheap to keep generous: the deadline only bounds how stale a single
// signed envelope can sit before the validator rejects it.
const rewardPoolDeadlineWindow = 100
Comment thread
rickyrombo marked this conversation as resolved.
Outdated

const (
signedAuthMessage = "code"
codeLength = 10
Expand Down Expand Up @@ -212,8 +219,27 @@ func (app *ApiServer) createAndInsertRewardCode(ctx context.Context, code, mint
return rewardAddress, nil
}

// createRewardCode creates or reuses a reward pool and returns the reward address.
// This is shared business logic used by both v1CreateRewardCode and prize claim flow.
// createRewardCode creates a cometbft reward bound to the launchpad mint's
// pool and returns the reward address. Idempotent on the pool (a pool that
// already exists is reused; only the very first reward for a brand-new
// mint triggers CreateRewardPool).
//
// Three keys are involved:
// - The per-mint claim authority eth key (secp256k1, from
// DeriveEthAddressForMint). Signs the cometbft envelope and is the
// pool's sole initial authority.
// - The RM ed25519 keypair (from DeriveRewardManagerKeypair). Same
// keypair the solana-relay used to init the Solana reward manager
// state account; its public key IS the rewards_manager_pubkey.
// Signs the CreateRewardPool envelope's rm_owner_signature, which
// proves possession of the RM keypair and prevents pool-creation
// frontrunning.
//
// Both are derived from app.config.LaunchpadDeterministicSecret +
// the mint, so they're available everywhere the secret is configured.
// When the secret is empty, this function is a no-op and returns ""
// (matches existing behavior for dev environments without launchpad
// configuration).
func (app *ApiServer) createRewardCode(ctx context.Context, code, mint string, amount int64, rewardName string) (string, error) {
app.logger.Info("createRewardCode: Starting",
zap.String("code", code),
Expand All @@ -223,65 +249,77 @@ func (app *ApiServer) createRewardCode(ctx context.Context, code, mint string, a
zap.Bool("has_deterministic_secret", app.config.LaunchpadDeterministicSecret != ""),
zap.String("audiusd_url", app.config.AudiusdURL))

var rewardAddress string
if app.config.LaunchpadDeterministicSecret == "" {
app.logger.Info("createRewardCode: Completed (no launchpad secret configured; reward pool skipped)",
zap.String("code", code))
return "", nil
}

// Only create reward pool if deterministic secret is configured
if app.config.LaunchpadDeterministicSecret != "" {
mintPubKey, err := solana.PublicKeyFromBase58(mint)
if err != nil {
return "", fmt.Errorf("invalid mint address: %w", err)
}
mintPubKey, err := solana.PublicKeyFromBase58(mint)
if err != nil {
return "", fmt.Errorf("invalid mint address: %w", err)
}

claimAuthority, claimAuthorityPrivateKey, err := utils.DeriveEthAddressForMint(
[]byte("claimAuthority"),
app.config.LaunchpadDeterministicSecret,
mintPubKey,
)
if err != nil {
return "", fmt.Errorf("failed to derive Ethereum key: %w", err)
}
claimAuthority, claimAuthorityPrivateKey, err := utils.DeriveEthAddressForMint(
[]byte("claimAuthority"),
app.config.LaunchpadDeterministicSecret,
mintPubKey,
)
if err != nil {
return "", fmt.Errorf("failed to derive eth claim-authority key: %w", err)
}
envelopeKey, err := common.EthToEthKey(claimAuthorityPrivateKey)
if err != nil {
return "", fmt.Errorf("failed to convert eth claim-authority key: %w", err)
}

// Convert the private key to the format expected by the SDK
privateKey, err := common.EthToEthKey(claimAuthorityPrivateKey)
if err != nil {
return "", fmt.Errorf("failed to convert private key: %w", err)
}
// Derive the RM ed25519 keypair matching what the solana-relay used
// to init the Solana reward manager state account. The base58-encoded
// public key IS the rewards_manager_pubkey cometbft carries for this
// mint's pool.
rmKey := utils.DeriveRewardManagerKeypair(app.config.LaunchpadDeterministicSecret, mintPubKey)
rewardsManagerPubkey := base58.Encode(rmKey.Public().(ed25519.PublicKey))

// Create OpenAudio SDK instance and set the private key
oap := sdk.NewOpenAudioSDK(app.config.AudiusdURL)
oap.SetPrivKey(privateKey)
oap := sdk.NewOpenAudioSDK(app.config.AudiusdURL)
oap.SetPrivKey(envelopeKey)

// Get current chain status to calculate deadline
statusResp, err := oap.Core.GetStatus(context.Background(), connect.NewRequest(&v1.GetStatusRequest{}))
if err != nil {
return "", fmt.Errorf("failed to get chain status: %w", err)
}
statusResp, err := oap.Core.GetStatus(ctx, connect.NewRequest(&v1.GetStatusRequest{}))
if err != nil {
return "", fmt.Errorf("failed to get chain status: %w", err)
}
deadline := statusResp.Msg.ChainInfo.CurrentHeight + rewardPoolDeadlineWindow

currentHeight := statusResp.Msg.ChainInfo.CurrentHeight
deadline := currentHeight + 100
rewardID := fmt.Sprintf("%s", code)

reward, err := oap.Rewards.CreateReward(context.Background(), &v1.CreateReward{
RewardId: rewardID,
Name: fmt.Sprintf("Launchpad Reward %s", code),
Amount: uint64(amount),
ClaimAuthorities: []*v1.ClaimAuthority{
{Address: claimAuthority, Name: "Launchpad"},
},
DeadlineBlockHeight: deadline,
})
if err != nil {
// First reward against this mint? Create the pool. Pre-existing pool
// is the common case (every subsequent reward for the same mint).
if _, err := oap.Rewards.GetRewardPool(ctx, rewardsManagerPubkey); err != nil {
if connect.CodeOf(err) != connect.CodeNotFound {
return "", fmt.Errorf("failed to look up reward pool for RM %s: %w", rewardsManagerPubkey, err)
}
app.logger.Info("createRewardCode: Creating reward pool",
zap.String("mint", mint),
zap.String("rewards_manager_pubkey", rewardsManagerPubkey),
zap.String("claim_authority", claimAuthority))
if _, err := oap.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{
RewardsManagerPubkey: rewardsManagerPubkey,
Authorities: []string{claimAuthority},
}, rmKey, deadline); err != nil {
return "", fmt.Errorf("failed to create reward pool: %w", err)
Comment thread
rickyrombo marked this conversation as resolved.
Outdated
}
}

rewardAddress = reward.Address
} else {
rewardAddress = ""
reward, err := oap.Rewards.CreateReward(ctx, &v1.CreateReward{
RewardId: code,
Name: fmt.Sprintf("Launchpad Reward %s", code),
Amount: uint64(amount),
RewardsManagerPubkey: rewardsManagerPubkey,
}, deadline)
if err != nil {
return "", fmt.Errorf("failed to create reward: %w", err)
}

app.logger.Info("createRewardCode: Completed",
zap.String("code", code),
zap.String("reward_address", rewardAddress),
zap.Bool("has_reward_address", rewardAddress != ""))
return rewardAddress, nil
zap.String("reward_address", reward.Address),
zap.String("rewards_manager_pubkey", rewardsManagerPubkey))
return reward.Address, nil
}
90 changes: 55 additions & 35 deletions cmd/create_reward_codes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"crypto/ed25519"
"encoding/csv"
"errors"
"flag"
Expand All @@ -19,9 +20,15 @@ import (
"github.com/OpenAudio/go-openaudio/pkg/sdk"
"github.com/gagliardetto/solana-go"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/mr-tron/base58"
"go.uber.org/zap"
)

// rewardPoolDeadlineWindow is the number of blocks ahead of the current
// height at which the CLI sets the deadline_block_height on cometbft tx
// envelopes (CreateRewardPool, CreateReward).
const rewardPoolDeadlineWindow = 100

const (
maxRetries = 3
initialRetryDelay = 100 * time.Millisecond
Expand Down Expand Up @@ -268,8 +275,15 @@ func processCode(ctx context.Context, logger *zap.Logger, pool *pgxpool.Pool, cf
oap := sdk.NewOpenAudioSDK(cfg.AudiusdURL)
oap.SetPrivKey(privateKey)

// Create reward pool (with retry and idempotency check)
rewardAddress, err := createRewardPool(ctx, logger, pool, oap, code, amount, claimAuthority)
// Derive the RM ed25519 keypair matching what the solana-relay used to
// init the Solana reward manager state account. The base58-encoded
// public key IS the rewards_manager_pubkey cometbft carries for this
// mint's pool.
rmKey := utils.DeriveRewardManagerKeypair(cfg.LaunchpadDeterministicSecret, mintPubKey)
rewardsManagerPubkey := base58.Encode(rmKey.Public().(ed25519.PublicKey))

// Ensure pool exists for this mint, then create the reward.
rewardAddress, err := ensurePoolAndCreateReward(ctx, logger, pool, oap, code, amount, claimAuthority, rewardsManagerPubkey, rmKey)
if err != nil {
return CodeResult{
Code: code,
Expand Down Expand Up @@ -303,61 +317,67 @@ func checkCodeExists(ctx context.Context, pool *pgxpool.Pool, code string) (bool
return exists, err
}

func createRewardPool(ctx context.Context, logger *zap.Logger, pool *pgxpool.Pool, oap *sdk.OpenAudioSDK, code string, amount int64, claimAuthority string) (string, error) {
// Get current chain status to calculate deadline
// ensurePoolAndCreateReward looks up (and if missing, creates) the reward
// pool for the mint, then submits the CreateReward tx and returns the
// reward address. The "reward already exists in pool" case is detected
// via the cometbft error string and resolved by reading the previously
// stored reward_address from the local DB — the idempotency guarantee
// the prior implementation provided is preserved.
func ensurePoolAndCreateReward(ctx context.Context, logger *zap.Logger, pool *pgxpool.Pool, oap *sdk.OpenAudioSDK, code string, amount int64, claimAuthority, rewardsManagerPubkey string, rmKey ed25519.PrivateKey) (string, error) {
var statusResp *connect.Response[v1.GetStatusResponse]
err := retryOperation(func() error {
if err := retryOperation(func() error {
var err error
statusResp, err = oap.Core.GetStatus(ctx, connect.NewRequest(&v1.GetStatusRequest{}))
return err
})
if err != nil {
}); err != nil {
return "", fmt.Errorf("failed to get chain status: %w", err)
}
deadline := statusResp.Msg.ChainInfo.CurrentHeight + rewardPoolDeadlineWindow

// Pool existence check. The common case (any non-first reward for the
// mint) is "pool exists, skip the create." Brand-new mints fall into
// the create branch exactly once.
if err := retryOperation(func() error {
_, err := oap.Rewards.GetRewardPool(ctx, rewardsManagerPubkey)
if err != nil && connect.CodeOf(err) == connect.CodeNotFound {
logger.Info("Creating reward pool", zap.String("rewards_manager_pubkey", rewardsManagerPubkey), zap.String("claim_authority", claimAuthority))
if _, createErr := oap.Rewards.CreateRewardPool(ctx, &v1.CreateRewardPool{
RewardsManagerPubkey: rewardsManagerPubkey,
Authorities: []string{claimAuthority},
}, rmKey, deadline); createErr != nil && !strings.Contains(createErr.Error(), "already exists") {
return createErr
Comment thread
rickyrombo marked this conversation as resolved.
Outdated
}
return nil
}
return err
}); err != nil {
return "", fmt.Errorf("failed to ensure reward pool: %w", err)
}

currentHeight := statusResp.Msg.ChainInfo.CurrentHeight
deadline := currentHeight + 100
rewardID := code

// Try to create reward pool
var reward *v1.GetRewardResponse
err = retryOperation(func() error {
if err := retryOperation(func() error {
var err error
reward, err = oap.Rewards.CreateReward(ctx, &v1.CreateReward{
RewardId: rewardID,
Name: fmt.Sprintf("Launchpad Reward %s", code),
Amount: uint64(amount),
ClaimAuthorities: []*v1.ClaimAuthority{
{Address: claimAuthority, Name: "Launchpad"},
},
DeadlineBlockHeight: deadline,
})

// If error indicates reward already exists, return special error
RewardId: code,
Name: fmt.Sprintf("Launchpad Reward %s", code),
Amount: uint64(amount),
RewardsManagerPubkey: rewardsManagerPubkey,
}, deadline)
if err != nil && strings.Contains(err.Error(), "already exists") {
logger.Info("Reward pool already exists", zap.String("code", code))
logger.Info("Reward already exists", zap.String("code", code))
return &RewardExistsError{Code: code}
}

return err
})

// Handle reward already exists case
if err != nil {
}); err != nil {
if existsErr, ok := err.(*RewardExistsError); ok {
// Reward pool already exists - check if we have it in the DB
// We need to pass pool to the closure, so we'll query it here
var rewardAddress string
dbErr := retryOperation(func() error {
return pool.QueryRow(ctx, "SELECT reward_address FROM reward_codes WHERE code = $1", existsErr.Code).Scan(&rewardAddress)
})
if dbErr == nil && rewardAddress != "" {
// We have it in DB, use that
return rewardAddress, nil
}
// If not in DB, we can't proceed - this shouldn't happen in normal flow
// but if it does, we'll return an error
return "", fmt.Errorf("reward pool exists but address not found in database")
return "", fmt.Errorf("reward already exists on chain but address not found in local DB")
}
return "", err
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
connectrpc.com/connect v1.18.1
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Doist/unfurlist v0.0.0-20250409100812-515f2735f8e5
github.com/OpenAudio/go-openaudio v1.2.11
github.com/OpenAudio/go-openaudio v1.2.13-0.20260512190210-e499df4ad4d8
github.com/aquasecurity/esquery v0.2.0
github.com/axiomhq/axiom-go v0.23.0
github.com/axiomhq/hyperloglog v0.2.5
Expand Down
20 changes: 2 additions & 18 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OpenAudio/go-openaudio v1.2.0 h1:jnvc7nWpPEZlDTABVrp9uLuLh0BcchwpHkg8nTQ/Zqs=
github.com/OpenAudio/go-openaudio v1.2.0/go.mod h1:tI/qfjYymj8TmVWEMl4WnciLxB//I2eZZyIkuUUCSnM=
github.com/OpenAudio/go-openaudio v1.2.9 h1:dhUTfzNAq4jVhSRmu1XDpVs8wMen3rPlSuVW1qqwZdo=
github.com/OpenAudio/go-openaudio v1.2.9/go.mod h1:+xl3SeIY7pc6CfwO1qmYjWELLSseQaulqSQefE6i2FA=
github.com/OpenAudio/go-openaudio v1.2.11 h1:s30csv/5g2bi8j4RcI6op04AmazX3qFuOw1o+FAq7UE=
github.com/OpenAudio/go-openaudio v1.2.11/go.mod h1:+xl3SeIY7pc6CfwO1qmYjWELLSseQaulqSQefE6i2FA=
github.com/OpenAudio/go-openaudio v1.2.13-0.20260512190210-e499df4ad4d8 h1:3CSruoJu4vumWtBn8jSiEabMt7E74zdPm+DtKSrXAk0=
github.com/OpenAudio/go-openaudio v1.2.13-0.20260512190210-e499df4ad4d8/go.mod h1:+xl3SeIY7pc6CfwO1qmYjWELLSseQaulqSQefE6i2FA=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
Expand Down Expand Up @@ -206,8 +202,6 @@ github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBj
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
Expand Down Expand Up @@ -636,30 +630,22 @@ go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
Expand Down Expand Up @@ -812,8 +798,6 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
Loading
Loading