|
8 | 8 |
|
9 | 9 | "github.com/spf13/cobra" |
10 | 10 |
|
| 11 | + "github.com/duneanalytics/duneapi-client-go/config" |
| 12 | + "github.com/duneanalytics/duneapi-client-go/dune" |
| 13 | + |
11 | 14 | "github.com/duneanalytics/cli/authconfig" |
12 | 15 | "github.com/duneanalytics/cli/cmd/sim/evm" |
13 | 16 | "github.com/duneanalytics/cli/cmd/sim/svm" |
@@ -50,6 +53,17 @@ func simPreRun(cmd *cobra.Command, _ []string) error { |
50 | 53 | // correct duration for telemetry. |
51 | 54 | cmdutil.SetStartTime(cmd, time.Now()) |
52 | 55 |
|
| 56 | + // Resolve customer identity for analytics (best-effort, never blocks the CLI). |
| 57 | + // We try to resolve the Dune API key (not the Sim API key) to identify the user. |
| 58 | + if tr := cmdutil.TrackerFromCmd(cmd); tr != nil { |
| 59 | + if env := resolveDuneEnv(); env != nil { |
| 60 | + client := dune.NewDuneClient(env) |
| 61 | + if customerID := resolveCustomerIDForSim(client, env.APIKey); customerID != "" { |
| 62 | + tr.SetUserID(customerID) |
| 63 | + } |
| 64 | + } |
| 65 | + } |
| 66 | + |
53 | 67 | // Commands like `sim evm supported-chains` that hit public endpoints |
54 | 68 | // don't require an API key. Provide a bare (unauthenticated) client so |
55 | 69 | // they can still use the shared HTTP infrastructure and error handling. |
@@ -103,3 +117,54 @@ func SimClientFromCmd(cmd *cobra.Command) *SimClient { |
103 | 117 | } |
104 | 118 | return v.(*SimClient) |
105 | 119 | } |
| 120 | + |
| 121 | +// resolveDuneEnv attempts to resolve the Dune API key from environment variables |
| 122 | +// or the config file, returning a Dune environment configuration. |
| 123 | +// Returns nil if no Dune API key is available (this is not an error for sim commands). |
| 124 | +func resolveDuneEnv() *config.Env { |
| 125 | + // Try environment variable first. |
| 126 | + env, err := config.FromEnvVars() |
| 127 | + if err == nil { |
| 128 | + return env |
| 129 | + } |
| 130 | + |
| 131 | + // Try config file. |
| 132 | + cfg, err := authconfig.Load() |
| 133 | + if err != nil || cfg == nil { |
| 134 | + return nil |
| 135 | + } |
| 136 | + |
| 137 | + key := strings.TrimSpace(cfg.APIKey) |
| 138 | + if key == "" { |
| 139 | + return nil |
| 140 | + } |
| 141 | + |
| 142 | + return config.FromAPIKey(key) |
| 143 | +} |
| 144 | + |
| 145 | +// resolveCustomerIDForSim is a wrapper around cli.ResolveCustomerID that |
| 146 | +// can be called from the sim package. It must be declared here to avoid |
| 147 | +// a circular import between cli and cmd/sim. |
| 148 | +func resolveCustomerIDForSim(client dune.DuneClient, apiKey string) string { |
| 149 | + keyHash := authconfig.HashAPIKey(apiKey) |
| 150 | + |
| 151 | + // Try the cache first. |
| 152 | + cached, err := authconfig.LoadIdentity() |
| 153 | + if err == nil && cached != nil && cached.APIKeyHash == keyHash && cached.CustomerID != "" { |
| 154 | + return cached.CustomerID |
| 155 | + } |
| 156 | + |
| 157 | + // Cache miss or stale — call the API. |
| 158 | + resp, err := client.WhoAmI() |
| 159 | + if err != nil || resp == nil || resp.CustomerID == "" { |
| 160 | + return "" |
| 161 | + } |
| 162 | + |
| 163 | + // Persist for next time (best-effort). |
| 164 | + _ = authconfig.SaveIdentity(&authconfig.UserIdentity{ |
| 165 | + CustomerID: resp.CustomerID, |
| 166 | + APIKeyHash: keyHash, |
| 167 | + }) |
| 168 | + |
| 169 | + return resp.CustomerID |
| 170 | +} |
0 commit comments