# HubClient SDK (Go)

The HubClient SDK is a Go client library for interacting with x402-hub. It provides type-safe access to protocol endpoints, authentication, and transaction history.

## Installation

```bash
go get github.com/sigweihq/x402pay
```

## Quick Start

```go
import (
    "fmt"
    "github.com/sigweihq/x402pay/pkg/hubclient"
    x402paytypes "github.com/sigweihq/x402pay/pkg/types"
)

func main() {
    // Create client (uses https://hub.sigwei.com by default)
    client := hubclient.NewHubClient(nil)

    // Get authentication message
    msg, err := client.Auth.GetAuthMessage("0xYourWalletAddress")
    if err != nil {
        panic(err)
    }

    // Sign message with wallet (user does this)
    signature := signWithWallet(msg.Message)

    // Login
    auth, err := client.Auth.Login(msg.Message, signature)
    if err != nil {
        panic(err)
    }

    fmt.Println("Logged in:", auth.User.WalletAddress)

    // Get transaction history
    history, err := client.History.GetHistory(&x402paytypes.HistoryParams{
        Network: "base",
        Limit:   50,
    })

    for _, tx := range history.Transactions {
        fmt.Printf("Transaction: %s (%s)\n", tx.Status, tx.Network)
    }
}
```

## Architecture

HubClient is organized into three main components:

```
HubClient
├─ FacilitatorClient (embedded)  → Standard x402 protocol
│  ├─ Verify()                   → Verify payment
│  └─ Settle()                   → Settle payment
├─ Auth                          → Wallet authentication
│  ├─ GetAuthMessage()           → Get SIWE message
│  ├─ Login()                    → Login with signature
│  ├─ RefreshToken()             → Refresh access token
│  ├─ GetMe()                    → Get current user
│  └─ Logout()                   → Logout
└─ History                       → Transaction history
   ├─ GetHistory()               → Query transactions
   └─ GetHistoryWithAutoRefresh() → Auto-refresh tokens
```

### Hub-Specific Extensions

In addition to standard x402 protocol endpoints, HubClient provides:

* **SettleWithOptions()** - Enhanced settle with `confirm` and `useDbId` parameters
* **Transfer()** - Convenient endpoint for EVM transfers
* **Supported()** - Query supported networks and schemes

## Authentication

### Basic Authentication Flow

```go
// 1. Get auth message
msgResp, err := client.Auth.GetAuthMessage("0x1234...")
if err != nil {
    log.Fatal(err)
}

// 2. Sign message with wallet
signature := "0x..." // From wallet software

// 3. Login
auth, err := client.Auth.Login(msgResp.Message, signature)
if err != nil {
    log.Fatal(err)
}

// Tokens are automatically stored in client.Auth
fmt.Println("Access Token:", auth.AccessToken)
fmt.Println("User:", auth.User.WalletAddress)
```

### Check Authentication Status

```go
if client.Auth.IsAuthenticated() {
    fmt.Println("User is authenticated")
}
```

### Get Current User

```go
user, err := client.Auth.GetMe()
if err != nil {
    log.Fatal(err)
}
fmt.Println("Current user:", user.WalletAddress)
```

### Token Refresh

```go
// When access token expires
newTokens, err := client.Auth.RefreshToken()
if err != nil {
    log.Fatal(err)
}
```

### Manual Token Management

```go
// Set tokens manually (e.g., from storage)
client.Auth.SetTokens("access-token", "refresh-token")

// Get tokens (e.g., to persist)
accessToken := client.Auth.GetAccessToken()
refreshToken := client.Auth.GetRefreshToken()

// Clear tokens
client.Auth.ClearTokens()
```

### Logout

```go
if err := client.Auth.Logout(); err != nil {
    log.Fatal(err)
}
// Tokens are cleared
```

## Transaction History

### Basic Query

```go
history, err := client.History.GetHistory(&x402paytypes.HistoryParams{
    Network: "base",    // Optional: filter by network
    Limit:   50,        // 1-100, defaults to 50
    Offset:  0,         // For pagination
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Total: %d, Returned: %d\n",
    history.Pagination.TotalCount,
    len(history.History))

for _, tx := range history.History {
    fmt.Printf("Transaction %d: %s (%s)\n",
        tx.ID, tx.Status, tx.Network)
    if tx.TransactionHash != nil {
        fmt.Printf("  Hash: %s\n", *tx.TransactionHash)
    }
    fmt.Printf("  Amount: %s wei\n", tx.Amount)
}
```

### Auto-Refresh

Use `GetHistoryWithAutoRefresh()` to automatically refresh expired tokens:

```go
history, err := client.History.GetHistoryWithAutoRefresh(&x402paytypes.HistoryParams{
    Limit: 50,
})
// If token is expired, it will automatically refresh and retry
```

### Pagination

```go
// Page 1
page1, err := client.History.GetHistory(&x402paytypes.HistoryParams{
    Limit:  20,
    Offset: 0,
})

// Page 2
page2, err := client.History.GetHistory(&x402paytypes.HistoryParams{
    Limit:  20,
    Offset: 20,
})
```

## x402 Protocol Operations

### Check Supported Networks

```go
supported, err := client.Supported()
if err != nil {
    log.Fatal(err)
}

for _, kind := range supported.Kinds {
    fmt.Printf("%s on %s\n", kind.Scheme, kind.Network)
}
```

### Verify Payment

```go
resp, err := client.Verify(paymentPayload, paymentRequirements)
if err != nil {
    log.Fatal(err)
}

if resp.IsValid {
    fmt.Println("Payment is valid")
} else {
    fmt.Println("Invalid:", resp.InvalidReason)
}
```

### Settle Payment (Standard)

```go
// Standard settle (from FacilitatorClient)
resp, err := client.Settle(paymentPayload, paymentRequirements)
if err != nil {
    log.Fatal(err)
}

fmt.Println("Transaction:", resp.Transaction)
```

### Settle Payment with Options

```go
// Hub-enhanced settle with options
resp, err := client.SettleWithOptions(
    paymentPayload,
    paymentRequirements,
    true,  // confirm: enable on-chain verification
    false, // useDbId: return DB ID instead of tx hash
)
```

### Transfer (Hub-Specific)

```go
import "github.com/coinbase/x402/go/pkg/types"

payload := &types.ExactEvmPayload{
    Signature: "0x...",
    Authorization: types.Authorization{
        From:        "0xSender",
        To:          "0xRecipient",
        Value:       "1000000", // 1 USDC in wei (6 decimals)
        ValidAfter:  "0",
        ValidBefore: "9999999999",
        Nonce:       "0x...",
    },
}

resp, err := client.Transfer(
    payload,
    "base",                                    // network
    "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC contract
    true,                                      // confirm
)
```

## Configuration

### Default Configuration

```go
// Uses default hub URL: https://hub.sigwei.com
client := hubclient.NewHubClient(nil)
```

### Custom Configuration

```go
import (
    "time"
    "github.com/coinbase/x402/go/pkg/types"
)

config := &types.FacilitatorConfig{
    URL: "https://custom-hub.example.com",
    Timeout: func() time.Duration {
        return 30 * time.Second
    },
}

client := hubclient.NewHubClient(config)
```

## Error Handling

### HTTP Errors

```go
_, err := client.Auth.Login("invalid", "invalid")
if err != nil {
    if httpErr, ok := err.(*hubclient.HTTPError); ok {
        fmt.Printf("HTTP %d: %s\n", httpErr.StatusCode, httpErr.Error())

        if httpErr.IsUnauthorized() {
            fmt.Println("Authentication failed")
        }
    }
}
```

### Common Errors

* **401 Unauthorized**: Invalid credentials or expired token
  * Solution: Refresh token or re-authenticate
* **403 Forbidden**: Insufficient permissions
* **400 Bad Request**: Invalid parameters
* **500 Internal Server Error**: Server error

## Type Reference

### Auth Types

```go
type MessageResponse struct {
    Message    string `json:"message"`
    APIVersion string `json:"apiVersion"`
    Timestamp  string `json:"timestamp"`
}

type AuthResponse struct {
    User         *User  `json:"user"`
    AccessToken  string `json:"accessToken"`
    RefreshToken string `json:"refreshToken"`
    APIVersion   string `json:"apiVersion"`
    Timestamp    string `json:"timestamp"`
}

type User struct {
    ID            uint64    `json:"id"`
    WalletAddress string    `json:"walletAddress"`
    CreatedAt     time.Time `json:"createdAt"`
    UpdatedAt     time.Time `json:"updatedAt"`
}

type TokenPair struct {
    AccessToken  string `json:"accessToken"`
    RefreshToken string `json:"refreshToken"`
    APIVersion   string `json:"apiVersion"`
    Timestamp    string `json:"timestamp"`
}
```

### History Types

```go
type HistoryParams struct {
    Network string `json:"network,omitempty"` // Optional: "base" or "base-sepolia"
    Limit   int    `json:"limit"`             // 1-100
    Offset  int    `json:"offset"`            // For pagination
}

type HistoryResponse struct {
    Success    bool                       `json:"success"`
    History    []*TransactionHistoryItem  `json:"history"`
    Pagination *PaginationInfo            `json:"pagination"`
    APIVersion string                     `json:"apiVersion"`
    Timestamp  string                     `json:"timestamp"`
}

type TransactionHistoryItem struct {
    ID              int64            `json:"id"`
    CreatedAt       time.Time        `json:"createdAt"`
    UpdatedAt       time.Time        `json:"updatedAt"`
    SignerAddress   string           `json:"signerAddress"`
    Amount          string           `json:"amount"` // Wei units
    Network         string           `json:"network"`
    ChainID         int              `json:"chainId"`
    TransactionHash *string          `json:"transactionHash,omitempty"`
    Status          string           `json:"status"`
    Error           *string          `json:"error,omitempty"`
    Type            string           `json:"type"`
    X402Data        *X402DataHistory `json:"x402Data,omitempty"`
    PurchaseData    *PurchaseData    `json:"purchaseData,omitempty"`
}

type PaginationInfo struct {
    TotalCount int  `json:"totalCount"`
    Limit      int  `json:"limit"`
    Offset     int  `json:"offset"`
    HasNext    bool `json:"hasNext"`
    HasPrev    bool `json:"hasPrev"`
}
```

## Thread Safety

* **Auth token management** is thread-safe (uses `sync.RWMutex`)
* **HTTP client** is thread-safe (from `net/http`)
* Multiple goroutines can safely share a single `HubClient` instance

## Testing with HubClient

HubClient significantly simplifies E2E and integration tests by eliminating manual HTTP calls and JSON parsing.

### Before: Manual HTTP Calls (\~70 lines)

```go
func authenticateUser(account *TestAccount) (string, error) {
    // Manual HTTP call to /api/v1/auth/message
    client := &http.Client{Timeout: 10 * time.Second}
    messageUrl := fmt.Sprintf("%s/api/v1/auth/message?walletAddress=%s",
        baseURL, account.Address)
    resp, err := client.Get(messageUrl)
    // ... JSON parsing
    var messageResponse map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&messageResponse)
    message := messageResponse["message"].(string)

    // Sign message
    signature, _ := account.SignPersonalMessage(t, message)

    // Manual HTTP call to /api/v1/auth/login
    authBody, _ := json.Marshal(map[string]interface{}{
        "message": message,
        "signature": signature,
    })
    authResp, _ := client.Post(
        fmt.Sprintf("%s/api/v1/auth/login", baseURL), ...)
    // ... more JSON parsing
    // ~70 lines total
}
```

### After: Using HubClient (\~15 lines)

```go
func authenticateUser(account *TestAccount) (*hubclient.HubClient, error) {
    hubClient := hubclient.NewHubClient(&types.FacilitatorConfig{
        URL: baseURL,
    })

    msgResp, _ := hubClient.Auth.GetAuthMessage(account.Address)
    signature, _ := account.SignPersonalMessage(t, msgResp.Message)
    _, err := hubClient.Auth.Login(msgResp.Message, signature)
    // Tokens auto-stored in hubClient.Auth
    return hubClient, err
}
```

### Benefits in Tests

1. **63% less code** - 70 lines → 26 lines for auth
2. **Type safety** - `map[string]interface{}` → `*TransactionHistoryItem`
3. **Automatic token management** - No manual extraction or storage
4. **Better error handling** - Structured errors with context
5. **Auto token refresh** - Use `GetHistoryWithAutoRefresh()`
6. **Single source of truth** - All auth/history logic in hubclient

## Complete Examples

See the [HubClient README](https://github.com/sigweihq/x402pay/blob/main/pkg/hubclient/README.md) for comprehensive examples including:

* Full authentication workflow
* Transaction history queries
* Error handling
* Token refresh
* Protocol endpoints

## Best Practices

1. **Reuse client instances** - Create once, use throughout your application
2. **Handle token refresh** - Use `GetHistoryWithAutoRefresh()` or implement refresh logic
3. **Store tokens securely** - Persist tokens in secure storage between sessions
4. **Validate parameters** - Check limits and offsets before making requests
5. **Handle errors properly** - Check for `HTTPError` type for detailed error info
6. **Use in tests** - Replace manual HTTP calls with hubclient for cleaner, type-safe tests

## Related Documentation

* [Getting Started with x402-hub](https://sigwei.gitbook.io/sigwei/x402-hub-documentation/getting-started-x402-hub)
* [x402-hub API Reference](https://sigwei.gitbook.io/sigwei/x402-hub-documentation/api-reference-x402-hub)
* [x402 Protocol Reference](https://sigwei.gitbook.io/sigwei/shared-resources/x402-protocol)

## Source Code

GitHub: [sigweihq/x402pay](https://github.com/sigweihq/x402pay) Package: `github.com/sigweihq/x402pay/pkg/hubclient`
