-
Notifications
You must be signed in to change notification settings - Fork 0
feat(turnkey): Integrate Turnkey support for remote signer functionality #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
817d14a
098ebce
866df77
7a12dc0
528e6bf
cbd29d8
ab7a91b
2279566
5985fac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
| "strings" | ||
|
|
||
| "github.com/ethereum/go-ethereum/log" | ||
| ) | ||
|
|
||
| func (w *wizard) turnkeyBase() string { | ||
| return fmt.Sprintf("http://%s:%s", w.host, w.httpPort) | ||
| } | ||
|
|
||
| func (w *wizard) turnkeyListWallets() { | ||
| resp, err := http.Get(w.turnkeyBase() + "/turnkey/wallets") | ||
| if err != nil { | ||
| log.Error("Turnkey list wallets failed", "err", err) | ||
| return | ||
| } | ||
| defer resp.Body.Close() | ||
| body, _ := io.ReadAll(resp.Body) | ||
| if resp.StatusCode != http.StatusOK { | ||
| log.Error("Turnkey list wallets", "status", resp.StatusCode, "body", string(body)) | ||
| return | ||
| } | ||
| fmt.Println(string(body)) | ||
|
Comment on lines
+18
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In Go's net/http package, http.Get and http.Post use http.DefaultClient by default when no custom http.Client is provided. The DefaultClient is defined as var DefaultClient = &Client{}, which is the zero value of Client. The Client struct has a Timeout field of type time.Duration, and the documentation explicitly states: "A Timeout of zero means no timeout." The client.do method checks if c.Timeout > 0 before applying any deadline; since it is zero, no timeout is set. This behavior is consistent across Go versions, including recent ones like go1.25.1, and is confirmed by multiple secondary sources warning that the default client has no timeout, allowing requests to hang indefinitely. Citations:
🏁 Script executed: # Find the file and check its size
fd wizard_turnkey.goRepository: eliteprox/go-livepeer Length of output: 99 🏁 Script executed: # Read the entire file to see all HTTP calls
cat -n cmd/livepeer_cli/wizard_turnkey.goRepository: eliteprox/go-livepeer Length of output: 3139 Add a bounded HTTP client for Turnkey wizard calls. All four actions use the default 🔧 Minimal fix sketch import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
+ "time"
"github.com/ethereum/go-ethereum/log"
)
+var turnkeyHTTPClient = &http.Client{Timeout: 10 * time.Second}
+
func (w *wizard) turnkeyListWallets() {
- resp, err := http.Get(w.turnkeyBase() + "/turnkey/wallets")
+ resp, err := turnkeyHTTPClient.Get(w.turnkeyBase() + "/turnkey/wallets")
if err != nil {
log.Error("Turnkey list wallets failed", "err", err)
return
}
@@
- resp, err := http.Post(w.turnkeyBase()+"/turnkey/create-wallet", "application/json", bytes.NewReader(payload))
+ resp, err := turnkeyHTTPClient.Post(w.turnkeyBase()+"/turnkey/create-wallet", "application/json", bytes.NewReader(payload))
@@
- resp, err := http.Post(w.turnkeyBase()+"/turnkey/create-account", "application/json", bytes.NewReader(payload))
+ resp, err := turnkeyHTTPClient.Post(w.turnkeyBase()+"/turnkey/create-account", "application/json", bytes.NewReader(payload))
@@
- resp, err := http.Post(w.turnkeyBase()+"/turnkey/select-address", "application/json", bytes.NewReader(payload))
+ resp, err := turnkeyHTTPClient.Post(w.turnkeyBase()+"/turnkey/select-address", "application/json", bytes.NewReader(payload))🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| func (w *wizard) turnkeyCreateWallet() { | ||
| fmt.Println("Wallet name?") | ||
| name := w.readString() | ||
| payload, _ := json.Marshal(map[string]string{"walletName": name}) | ||
| resp, err := http.Post(w.turnkeyBase()+"/turnkey/create-wallet", "application/json", bytes.NewReader(payload)) | ||
| if err != nil { | ||
| log.Error("Turnkey create wallet failed", "err", err) | ||
| return | ||
| } | ||
| defer resp.Body.Close() | ||
| body, _ := io.ReadAll(resp.Body) | ||
| if resp.StatusCode != http.StatusOK { | ||
| log.Error("Turnkey create wallet", "status", resp.StatusCode, "body", string(body)) | ||
| return | ||
| } | ||
| fmt.Println(string(body)) | ||
| } | ||
|
|
||
| func (w *wizard) turnkeyCreateAccount() { | ||
| fmt.Println("Wallet ID?") | ||
| wid := w.readString() | ||
| payload, _ := json.Marshal(map[string]string{"walletId": wid}) | ||
| resp, err := http.Post(w.turnkeyBase()+"/turnkey/create-account", "application/json", bytes.NewReader(payload)) | ||
| if err != nil { | ||
| log.Error("Turnkey create account failed", "err", err) | ||
| return | ||
| } | ||
| defer resp.Body.Close() | ||
| body, _ := io.ReadAll(resp.Body) | ||
| if resp.StatusCode != http.StatusOK { | ||
| log.Error("Turnkey create account", "status", resp.StatusCode, "body", string(body)) | ||
| return | ||
| } | ||
| fmt.Println(string(body)) | ||
| } | ||
|
|
||
| func (w *wizard) turnkeySelectAddress() { | ||
| fmt.Println("Ethereum address (0x…)?") | ||
| addr := w.readString() | ||
| if !strings.HasPrefix(addr, "0x") { | ||
| addr = "0x" + addr | ||
| } | ||
| payload, _ := json.Marshal(map[string]string{"address": addr}) | ||
| resp, err := http.Post(w.turnkeyBase()+"/turnkey/select-address", "application/json", bytes.NewReader(payload)) | ||
| if err != nil { | ||
| log.Error("Turnkey select address failed", "err", err) | ||
| return | ||
| } | ||
| defer resp.Body.Close() | ||
| body, _ := io.ReadAll(resp.Body) | ||
| if resp.StatusCode != http.StatusOK { | ||
| log.Error("Turnkey select address", "status", resp.StatusCode, "body", string(body)) | ||
| return | ||
| } | ||
| fmt.Println(string(body)) | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -151,8 +151,17 @@ type LivepeerNode struct { | |||||||||||||||||||||||||||||||||||||||||||||||
| // Gateway fields for remote signers | ||||||||||||||||||||||||||||||||||||||||||||||||
| RemoteSignerUrl *url.URL | ||||||||||||||||||||||||||||||||||||||||||||||||
| RemoteEthAddr ethcommon.Address // eth address of the remote signer | ||||||||||||||||||||||||||||||||||||||||||||||||
| InfoSig []byte // sig over eth address for the OrchestratorInfo request | ||||||||||||||||||||||||||||||||||||||||||||||||
| RemoteDiscovery bool // expose remote discovery endpoint when enabled | ||||||||||||||||||||||||||||||||||||||||||||||||
| // GatewayRemoteSignerAddress optionally pins the gateway to a specific remote signer ETH identity (Turnkey multi-address). | ||||||||||||||||||||||||||||||||||||||||||||||||
| GatewayRemoteSignerAddress ethcommon.Address | ||||||||||||||||||||||||||||||||||||||||||||||||
| InfoSig []byte // sig over eth address for the OrchestratorInfo request | ||||||||||||||||||||||||||||||||||||||||||||||||
| RemoteDiscovery bool // expose remote discovery endpoint when enabled | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Turnkey (remote signer only): org id, address book, and the concrete account manager when using Turnkey signing. | ||||||||||||||||||||||||||||||||||||||||||||||||
| TurnkeyMode bool | ||||||||||||||||||||||||||||||||||||||||||||||||
| TurnkeyOrgID string | ||||||||||||||||||||||||||||||||||||||||||||||||
| TurnkeyAccount *eth.TurnkeyAccountManager | ||||||||||||||||||||||||||||||||||||||||||||||||
| turnkeyMu sync.RWMutex | ||||||||||||||||||||||||||||||||||||||||||||||||
| turnkeyAddrs map[ethcommon.Address]struct{} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Thread safety for config fields | ||||||||||||||||||||||||||||||||||||||||||||||||
| mu sync.RWMutex | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -396,3 +405,44 @@ func (n *LivepeerNode) GetPriceForJob(senderEthAddress string, extCapability str | |||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return jobPrice | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // ReplaceTurnkeyAddressBook sets the set of Ethereum addresses this remote signer may use for Turnkey signing. | ||||||||||||||||||||||||||||||||||||||||||||||||
| func (n *LivepeerNode) ReplaceTurnkeyAddressBook(addrs []ethcommon.Address) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if n == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| n.turnkeyMu.Lock() | ||||||||||||||||||||||||||||||||||||||||||||||||
| defer n.turnkeyMu.Unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||
| n.turnkeyAddrs = make(map[ethcommon.Address]struct{}) | ||||||||||||||||||||||||||||||||||||||||||||||||
| for _, a := range addrs { | ||||||||||||||||||||||||||||||||||||||||||||||||
| n.turnkeyAddrs[a] = struct{}{} | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // TurnkeySigningAddressAllowed returns true if addr is in the Turnkey address book (or if the book is unset / Turnkey off). | ||||||||||||||||||||||||||||||||||||||||||||||||
| func (n *LivepeerNode) TurnkeySigningAddressAllowed(addr ethcommon.Address) bool { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if n == nil || !n.TurnkeyMode || n.TurnkeyAccount == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| n.turnkeyMu.RLock() | ||||||||||||||||||||||||||||||||||||||||||||||||
| defer n.turnkeyMu.RUnlock() | ||||||||||||||||||||||||||||||||||||||||||||||||
| if len(n.turnkeyAddrs) == 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| _, ok := n.turnkeyAddrs[addr] | ||||||||||||||||||||||||||||||||||||||||||||||||
| return ok | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+427
to
+437
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fail closed when the Turnkey address book is empty. Once Turnkey mode is active, returning 🔒 Suggested change func (n *LivepeerNode) TurnkeySigningAddressAllowed(addr ethcommon.Address) bool {
if n == nil || !n.TurnkeyMode || n.TurnkeyAccount == nil {
return true
}
n.turnkeyMu.RLock()
defer n.turnkeyMu.RUnlock()
if len(n.turnkeyAddrs) == 0 {
- return true
+ return false
}
_, ok := n.turnkeyAddrs[addr]
return ok
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // TurnkeyAddressList returns a snapshot of known Turnkey Ethereum addresses (for status / CLI). | ||||||||||||||||||||||||||||||||||||||||||||||||
| func (n *LivepeerNode) TurnkeyAddressList() []ethcommon.Address { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if n == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| n.turnkeyMu.RLock() | ||||||||||||||||||||||||||||||||||||||||||||||||
| defer n.turnkeyMu.RUnlock() | ||||||||||||||||||||||||||||||||||||||||||||||||
| out := make([]ethcommon.Address, 0, len(n.turnkeyAddrs)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| for a := range n.turnkeyAddrs { | ||||||||||||||||||||||||||||||||||||||||||||||||
| out = append(out, a) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| return out | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't silently pick
accts[0]when multiple Turnkey accounts exist.This makes the signer identity restart-dependent and also loses any address selected at runtime via the Turnkey API. If more than one Ethereum account exists, require
-ethAcctAddror load a persisted default instead of relying on API order.🔧 Safer startup behavior
📝 Committable suggestion
🤖 Prompt for AI Agents