The SignedCall functionality provides authenticated JSON-RPC calls to the Steem blockchain. It uses cryptographic signatures to prove account ownership and access private or restricted data.
This implementation is fully compatible with the steem-js library's signedCall method, ensuring interoperability between JavaScript and Go applications.
- Cryptographic Authentication: Proves account ownership without transmitting private keys
- Replay Protection: Uses unique nonces and timestamps to prevent replay attacks
- Time-based Expiration: Signatures expire after 60 seconds for security
- Multiple Key Support: Can sign with multiple private keys simultaneously
- HTTP Only: Requires HTTP/HTTPS transport for security reasons
package main
import (
"fmt"
"log"
"github.com/steemit/steemgosdk"
"github.com/steemit/steemgosdk/consts"
)
func main() {
// Create client
client := steemgosdk.GetClient("https://api.steemit.com")
client.AccountName = "your-account"
// Import your private key
err := client.ImportWif(consts.ACTIVE_KEY, "your-private-key-wif")
if err != nil {
log.Fatal(err)
}
// Make a signed call
var accounts []map[string]interface{}
err = client.SignedCallWithResult(
"condenser_api.get_accounts",
[]interface{}{[]string{"your-account"}},
consts.ACTIVE_KEY,
&accounts,
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Account: %v\n", accounts[0]["name"])
}Makes a signed RPC call using stored credentials.
Parameters:
method(string): The RPC method to call (e.g., "condenser_api.get_accounts")params([]interface{}): Parameters for the RPC methodkeyType(string): Which private key to use ("active", "posting", "owner", "memo")
Returns:
*RpcResultData: Raw RPC response dataerror: Error if the call fails
Makes a signed RPC call and unmarshals the result into the provided object.
Parameters:
method(string): The RPC method to callparams([]interface{}): Parameters for the RPC methodkeyType(string): Which private key to useresult(interface{}): Pointer to object to unmarshal result into
Returns:
error: Error if the call fails or unmarshaling fails
Low-level signed call method.
Parameters:
method(string): The RPC method to callparams([]interface{}): Parameters for the RPC methodaccount(string): Account name to sign asprivateKey(string): Private key in WIF format
Returns:
*RpcResultData: Raw RPC response dataerror: Error if the call fails
Low-level signed call with result unmarshaling.
var accounts []map[string]interface{}
err := client.SignedCallWithResult(
"condenser_api.get_accounts",
[]interface{}{[]string{"username"}},
consts.ACTIVE_KEY,
&accounts,
)var history []interface{}
err := client.SignedCallWithResult(
"condenser_api.get_account_history",
[]interface{}{"username", -1, 100},
consts.ACTIVE_KEY,
&history,
)var followers []interface{}
err := client.SignedCallWithResult(
"condenser_api.get_followers",
[]interface{}{"username", "", "blog", 100},
consts.POSTING_KEY,
&followers,
)// Multiple signed calls
operations := []struct {
method string
params []interface{}
}{
{"condenser_api.get_accounts", []interface{}{[]string{"username"}}},
{"condenser_api.get_account_history", []interface{}{"username", -1, 10}},
{"condenser_api.get_followers", []interface{}{"username", "", "blog", 10}},
}
for _, op := range operations {
var result interface{}
err := client.SignedCallWithResult(op.method, op.params, consts.ACTIVE_KEY, &result)
if err != nil {
log.Printf("Operation %s failed: %v", op.method, err)
continue
}
// Process result...
}// Error: "signed calls can only be made when using HTTP transport"
client := steemgosdk.GetClient("wss://api.steemit.com") // WebSocket not supported// Error: "invalid key type: invalid_key"
err := client.SignedCall(method, params, "invalid_key")// Error: "account name not set"
client := steemgosdk.GetClient(url)
// client.AccountName not set
err := client.SignedCall(method, params, consts.ACTIVE_KEY)// Error: "signature expired"
// Occurs when request takes longer than 60 seconds to reach serverfunc makeSignedCall(client *steemgosdk.Client, method string, params []interface{}) {
var result interface{}
err := client.SignedCallWithResult(method, params, consts.ACTIVE_KEY, &result)
switch {
case err == nil:
// Success
fmt.Printf("Call succeeded: %v\n", result)
case strings.Contains(err.Error(), "transport"):
// Transport error - check URL scheme
log.Printf("Transport error: %v", err)
case strings.Contains(err.Error(), "expired"):
// Signature expired - retry
log.Printf("Signature expired, retrying: %v", err)
// Implement retry logic
case strings.Contains(err.Error(), "key"):
// Key-related error - check credentials
log.Printf("Authentication error: %v", err)
default:
// Other error
log.Printf("Unexpected error: %v", err)
}
}- Use HTTPS Only: Always use HTTPS endpoints for signed calls
- Validate Private Keys: Verify key format before making calls
- Handle Expiration: Implement retry logic for expired signatures
- Secure Key Storage: Never log or expose private keys
- Environment Variables: Use environment variables for sensitive data
- No Key Transmission: Private keys never leave your application
- Unique Nonces: Each request uses a unique 8-byte random nonce
- Timestamp Validation: Signatures expire after 60 seconds
- Cross-Protocol Protection: Uses protocol-specific signing constant
package main
import (
"os"
"log"
"github.com/steemit/steemgosdk"
"github.com/steemit/steemgosdk/consts"
)
func main() {
// Get credentials from environment
account := os.Getenv("STEEM_ACCOUNT")
password := os.Getenv("STEEM_PASSWORD")
if account == "" || password == "" {
log.Fatal("STEEM_ACCOUNT and STEEM_PASSWORD must be set")
}
// Create client with HTTPS endpoint
client := steemgosdk.GetClient("https://api.steemit.com")
client.AccountName = account
// Generate keys from password (more secure than storing WIF)
auth := client.GetAuth()
keys, err := auth.GetPrivateKeys(account, password)
if err != nil {
log.Fatalf("Failed to generate keys: %v", err)
}
// Import only the keys you need
if err := client.ImportWif(consts.ACTIVE_KEY, keys.Active); err != nil {
log.Fatalf("Failed to import active key: %v", err)
}
// Make signed calls...
}import "github.com/steemit/steemutil/rpc"
// Custom verification function
func customVerifyFunc(message []byte, signatures []string, account string) error {
// Implement custom signature verification logic
// This could involve checking against blockchain account keys
return nil
}
// Use with low-level RPC signing
request := &rpc.RpcRequest{
Method: "condenser_api.get_accounts",
Params: []interface{}{[]string{"username"}},
ID: 1,
}
signedRequest, err := rpc.Sign(request, "username", []string{"private-key"})
if err != nil {
return err
}
params, err := rpc.Validate(signedRequest, customVerifyFunc)
if err != nil {
return err
}// Sign with multiple keys
privateKeys := []string{
"active-key-wif",
"posting-key-wif",
}
signedRequest, err := rpc.Sign(request, account, privateKeys)
// signedRequest will contain multiple signaturesfunc TestSignedCall(t *testing.T) {
client := steemgosdk.GetClient("https://api.steemit.com")
client.AccountName = "testaccount"
// Import test key
err := client.ImportWif(consts.ACTIVE_KEY, "test-private-key")
if err != nil {
t.Fatal(err)
}
// Test signed call
var result interface{}
err = client.SignedCallWithResult(
"condenser_api.get_dynamic_global_properties",
[]interface{}{},
consts.ACTIVE_KEY,
&result,
)
// In test environment, expect network error, not validation error
if err != nil && !strings.Contains(err.Error(), "network") {
t.Errorf("Unexpected error: %v", err)
}
}See test-gosdk/examples/signed_call/main.go for a complete integration test example.
This implementation is fully compatible with steem-js signed calls:
- Uses the same signing algorithm and message format
- Generates identical signature structures
- Supports the same security features (nonces, timestamps, expiration)
- Can validate signatures created by
steem-jsand vice versa
- Signing Overhead: ~1-5ms per signature
- Network Latency: Depends on node response time
- Memory Usage: Minimal additional overhead over regular RPC calls
- Concurrent Calls: Fully thread-safe, supports concurrent signed calls
- "Transport Error": Ensure you're using HTTP/HTTPS, not WebSocket
- "Invalid Key": Verify private key format and permissions
- "Account Not Set": Set
client.AccountNamebefore making calls - "Signature Expired": Implement retry logic for slow networks
- "Network Error": Check node availability and network connectivity
Set the DEBUG environment variable to enable detailed logging:
DEBUG=1 go run your-program.goThis will show request/response details and signing information.