Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3b9f42b
Add GraphQL API for wallet indexer (#239)
aditya1702 Jul 21, 2025
c462e85
Move `/accounts/` REST APIs to GraphQL (#253)
akcays Jul 25, 2025
86b808a
Merge remote-tracking branch 'upstream/main' into graphql-main
aditya1702 Jul 30, 2025
4f38d87
Build dynamic sql query based on columns requested in GraphQL (#261)
aditya1702 Aug 4, 2025
16e9873
Merge remote-tracking branch 'origin/main' into graphql-main
aditya1702 Aug 8, 2025
daa299e
Update resolvers to use state change's `toID` and `order` (#274)
aditya1702 Aug 11, 2025
61f019c
Move `/transactions/build/` from REST to GraphQL (#260)
akcays Aug 12, 2025
0767bbc
[Test Optimization] Use a common db setup for testing resolvers (#277)
aditya1702 Aug 12, 2025
8674e4a
Remove `tx/create-sponsored-account` endpoint (#267)
akcays Aug 13, 2025
39482f8
Remove audience claim from JWT authentication (#281)
JakeUrban Aug 14, 2025
1a97fda
Remove /payments/ endpoint, models, and ingestion logic (#284)
akcays Aug 18, 2025
b5f73b4
Make JWT authentication optional (#291)
JakeUrban Aug 18, 2025
e31c712
Paginate through an account's transactions and operations (#279)
aditya1702 Aug 18, 2025
042d542
Paginate through an account's state changes (#286)
aditya1702 Aug 19, 2025
a44d6b7
Paginate through all transactions and operations (#293)
aditya1702 Aug 19, 2025
9617171
update docker compose to remove warning logs (#298)
JakeUrban Aug 20, 2025
f333831
Add relay pagination to remaining queries (#297)
aditya1702 Aug 26, 2025
0141f8d
Merge branch 'main' into graphql-main
aditya1702 Aug 29, 2025
3353015
Merge branch 'main' into graphql-main
aditya1702 Sep 16, 2025
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
12 changes: 7 additions & 5 deletions .github/workflows/go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
push:
branches:
- main
paths-ignore:
- 'internal/serve/graphql/generated/**'
workflow_call: # allows this workflow to be called from another workflow

jobs:
Expand All @@ -17,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.23.2"
go-version: "1.24.2"
cache: true
cache-dependency-path: go.sum

Expand All @@ -32,7 +34,7 @@ jobs:
- name: Run `[email protected]`
run: |
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/[email protected]
shadow ./...
shadow ./... | { grep -v "generated.go" || true; }

- name: Run `[email protected]`
run: |
Expand All @@ -42,7 +44,7 @@ jobs:
- name: Run `[email protected]`
run: |
go install golang.org/x/tools/cmd/[email protected]
output=$(deadcode -test ./...)
output=$(deadcode -test ./... | { grep -v "UnmarshalUInt32" || true; })
if [[ -n "$output" ]]; then
echo "🚨 Deadcode found:"
echo "$output"
Expand Down Expand Up @@ -74,7 +76,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.23.2"
go-version: "1.24.2"
cache: true
cache-dependency-path: go.sum

Expand Down Expand Up @@ -113,7 +115,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.23.2"
go-version: "1.24.2"
cache: true
cache-dependency-path: go.sum

Expand Down
21 changes: 18 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ shadow: ## Run shadow analysis to find shadowed variables
echo "Installing shadow..."; \
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/[email protected]; \
fi
$(shell go env GOPATH)/bin/shadow ./...
@$(shell go env GOPATH)/bin/shadow ./... | { grep -v "generated.go" || true; }

exhaustive: ## Check exhaustiveness of switch statements
@echo "==> Running exhaustive..."
Expand All @@ -63,7 +63,7 @@ deadcode: ## Find unused code
echo "Installing deadcode..."; \
go install golang.org/x/tools/cmd/[email protected]; \
fi
@output=$$($(shell go env GOPATH)/bin/deadcode -test ./...); \
@output=$$($(shell go env GOPATH)/bin/deadcode -test ./... | grep -v "UnmarshalUInt32"); \
if [ -n "$$output" ]; then \
echo "🚨 Deadcode found:"; \
echo "$$output"; \
Expand Down Expand Up @@ -95,9 +95,24 @@ govulncheck: ## Check for known vulnerabilities
@command -v govulncheck >/dev/null 2>&1 || { go install golang.org/x/vuln/cmd/govulncheck@latest; }
$(shell go env GOPATH)/bin/govulncheck ./...

check: tidy fmt vet lint generate shadow exhaustive deadcode goimports govulncheck ## Run all checks
check: tidy fmt vet lint generate shadow exhaustive deadcode goimports govulncheck gql-validate ## Run all checks
@echo "✅ All checks completed successfully"

# ==================================================================================== #
# GRAPHQL
# ==================================================================================== #
gql-generate: ## Generate GraphQL code using gqlgen
@echo "==> Generating GraphQL code..."
@command -v $(shell go env GOPATH)/bin/gqlgen >/dev/null 2>&1 || { go install github.com/99designs/[email protected]; }
$(shell go env GOPATH)/bin/gqlgen generate
@echo "✅ GraphQL code generated successfully"

gql-validate: ## Validate GraphQL schema
@echo "==> Validating GraphQL schema..."
@command -v $(shell go env GOPATH)/bin/gqlgen >/dev/null 2>&1 || { go install github.com/99designs/[email protected]; }
$(shell go env GOPATH)/bin/gqlgen validate
@echo "✅ GraphQL schema is valid"

# ==================================================================================== #
# TESTING
# ==================================================================================== #
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,6 @@ The JWT payload field should contain the following fields:

- (default) `exp` – The expiration time on and after which the JWT must not be accepted for processing, in seconds since Epoch. (Must be less than `iat`+15sec.)
- (default) `iat` - The time at which the JWT was issued, in seconds since Epoch.
- (default) `aud` – The audience for the JWT. This is the server's hostname.
- (default) `sub` – The subject of the JWT, which is the public key of the Stellar account that is being authenticated.
- (custom) `methodAndPath` – The HTTP method and path of the request (e.g., `GET /transactions/b9d0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9c9020`).
- (custom) `bodyHash`, a hex-encoded SHA-256 hash of the raw HTTP request body, present even when the body is empty:
Expand Down
16 changes: 10 additions & 6 deletions cmd/integrationtests.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ func (c *integrationTestsCmd) Command() *cobra.Command {
utils.RPCURLOption(&cfg.RPCURL),
{
Name: "client-auth-private-key",
Usage: "The private key used to authenticate the client when making HTTP requests to the wallet-backend.",
Usage: "The private key used to authenticate the client when making HTTP requests to the wallet-backend. If not provided, authentication is disabled.",
OptType: types.String,
CustomSetValue: utils.SetConfigOptionStellarPrivateKey,
ConfigKey: &cfg.ClientAuthPrivateKey,
Required: true,
Required: false,
},
{
Name: "primary-source-account-private-key",
Expand Down Expand Up @@ -114,11 +114,15 @@ func (c *integrationTestsCmd) Command() *cobra.Command {
return fmt.Errorf("instantiating rpc service: %w", err)
}

jwtTokenGenerator, err := auth.NewJWTTokenGenerator(cfg.ClientAuthPrivateKey)
if err != nil {
return fmt.Errorf("instantiating jwt token generator: %w", err)
var requestSigner auth.HTTPRequestSigner
if cfg.ClientAuthPrivateKey != "" {
jwtTokenGenerator, err := auth.NewJWTTokenGenerator(cfg.ClientAuthPrivateKey)
if err != nil {
return fmt.Errorf("instantiating jwt token generator: %w", err)
}
requestSigner = auth.NewHTTPRequestSigner(jwtTokenGenerator)
}
wbClient := wbclient.NewClient(cfg.ServerBaseURL, auth.NewHTTPRequestSigner(jwtTokenGenerator))
wbClient := wbclient.NewClient(cfg.ServerBaseURL, requestSigner)

primaryKP, err := keypair.ParseFull(cfg.PrimarySourceAccountPrivateKey)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ func (c *serveCmd) Command() *cobra.Command {
},
{
Name: "client-auth-public-keys",
Usage: "A comma-separated list of public keys whose private keys are authorized to sign the payloads when making HTTP requests to this server.",
Usage: "A comma-separated list of public keys whose private keys are authorized to sign the payloads when making HTTP requests to this server. If not provided or empty, authentication is disabled.",
OptType: types.String,
CustomSetValue: utils.SetConfigOptionStellarPublicKeyList,
ConfigKey: &cfg.ClientAuthPublicKeys,
Required: true,
Required: false,
},
{
Name: "client-auth-max-timeout-seconds",
Expand Down
11 changes: 10 additions & 1 deletion cmd/utils/custom_set_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,16 @@ func SetConfigOptionStellarPublicKeyList(co *config.ConfigOption) error {
publicKeysStr := viper.GetString(co.Name)
publicKeysStr = strings.TrimSpace(publicKeysStr)
if publicKeysStr == "" {
return fmt.Errorf("no public keys provided in %s", co.Name)
if co.Required {
return fmt.Errorf("no public keys provided in %s", co.Name)
}
// If not required and empty, set to empty slice
key, ok := co.ConfigKey.(*[]string)
if !ok {
return unexpectedTypeError(key, co)
}
*key = []string{}
return nil
}
publicKeysStr = strings.ReplaceAll(publicKeysStr, " ", "")
publicKeys := strings.Split(publicKeysStr, ",")
Expand Down
4 changes: 2 additions & 2 deletions cmd/utils/custom_set_value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ func TestSetConfigOptionStellarPublicKeyList(t *testing.T) {

testCases := []customSetterTestCase[[]string]{
{
name: "🔴returns_an_error_if_the_public_keys_are_empty",
wantErrContains: "no public keys provided in client-auth-public-keys",
name: "🟢allows_empty_public_keys_when_not_required",
wantResult: []string{},
},
{
name: "🔴returns_an_error_if_the_public_key_is_invalid",
Expand Down
41 changes: 21 additions & 20 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ services:
stellar-rpc:
container_name: stellar-rpc
image: stellar/stellar-rpc:stable
platform: linux/amd64
healthcheck:
test: "curl --location 'http://localhost:8000/' -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"id\":8675309,\"method\":\"getHealth\"}' | grep -q '\"status\":\"healthy\"'"
interval: 10s
Expand Down Expand Up @@ -49,7 +50,7 @@ services:
db:
condition: service_healthy
stellar-rpc:
condition: service_started
condition: service_healthy
ingest:
condition: service_healthy
ports:
Expand All @@ -67,31 +68,31 @@ services:
PORT: 8001
SERVER_BASE_URL: http://api:8001
LOG_LEVEL: TRACE
CLIENT_AUTH_PUBLIC_KEYS: ${CLIENT_AUTH_PUBLIC_KEYS}
DISTRIBUTION_ACCOUNT_PUBLIC_KEY: ${DISTRIBUTION_ACCOUNT_PUBLIC_KEY}
DISTRIBUTION_ACCOUNT_SIGNATURE_PROVIDER: ${DISTRIBUTION_ACCOUNT_SIGNATURE_PROVIDER}
CLIENT_AUTH_PUBLIC_KEYS: ${CLIENT_AUTH_PUBLIC_KEYS:-}
DISTRIBUTION_ACCOUNT_PUBLIC_KEY: ${DISTRIBUTION_ACCOUNT_PUBLIC_KEY:-}
DISTRIBUTION_ACCOUNT_SIGNATURE_PROVIDER: ${DISTRIBUTION_ACCOUNT_SIGNATURE_PROVIDER:-ENV}
NUMBER_CHANNEL_ACCOUNTS: ${NUMBER_CHANNEL_ACCOUNTS:-2}

# Env Signature Client
DISTRIBUTION_ACCOUNT_PRIVATE_KEY: ${DISTRIBUTION_ACCOUNT_PRIVATE_KEY}
DISTRIBUTION_ACCOUNT_PRIVATE_KEY: ${DISTRIBUTION_ACCOUNT_PRIVATE_KEY:-}

# (optional) KMS Signature Client
KMS_KEY_ARN: ${KMS_KEY_ARN}
AWS_REGIONG: ${AWS_REGION}
KMS_KEY_ARN: ${KMS_KEY_ARN:-}
AWS_REGION: ${AWS_REGION:-}
# (optional) Using KMS locally is necessary to inject the AWS credentials envs.
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
AWS_SESSION_TOKEN: ${AWS_SESSION_TOKEN}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
AWS_SESSION_TOKEN: ${AWS_SESSION_TOKEN:-}

# Channel Account
CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE: ${CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE}
TRACKER_DSN: ${TRACKER_DSN}
STELLAR_ENVIRONMENT: ${STELLAR_ENVIRONMENT}
CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE: ${CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE:-}
TRACKER_DSN: ${TRACKER_DSN:-}
STELLAR_ENVIRONMENT: ${STELLAR_ENVIRONMENT:-}

# (optional) Integration Tests
CLIENT_AUTH_PRIVATE_KEY: ${CLIENT_AUTH_PRIVATE_KEY}
PRIMARY_SOURCE_ACCOUNT_PRIVATE_KEY: ${PRIMARY_SOURCE_ACCOUNT_PRIVATE_KEY}
SECONDARY_SOURCE_ACCOUNT_PRIVATE_KEY: ${SECONDARY_SOURCE_ACCOUNT_PRIVATE_KEY}
CLIENT_AUTH_PRIVATE_KEY: ${CLIENT_AUTH_PRIVATE_KEY:-}
PRIMARY_SOURCE_ACCOUNT_PRIVATE_KEY: ${PRIMARY_SOURCE_ACCOUNT_PRIVATE_KEY:-}
SECONDARY_SOURCE_ACCOUNT_PRIVATE_KEY: ${SECONDARY_SOURCE_ACCOUNT_PRIVATE_KEY:-}

ingest:
container_name: ingest
Expand All @@ -108,15 +109,15 @@ services:
db:
condition: service_healthy
stellar-rpc:
condition: service_started
condition: service_healthy
entrypoint: ""
command:
- sh
- -c
- |
./wallet-backend migrate up
if [ "$STELLAR_ENVIRONMENT" = "GITHUB_WORKFLOW" ]; then
HEALTH_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}' "${RPC_URL}")
HEALTH_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}' "$$RPC_URL")
LATEST_LEDGER=$(echo "$$HEALTH_RESPONSE" | grep -oE '"latestLedger":[0-9]+' | grep -oE '[0-9]+' || true)
if [ -z "$$LATEST_LEDGER" ] || [ "$$LATEST_LEDGER" = "" ]; then
./wallet-backend ingest
Expand All @@ -129,8 +130,8 @@ services:
environment:
RPC_URL: ${RPC_URL:-http://stellar-rpc:8000}
DATABASE_URL: postgres://postgres@db:5432/wallet-backend?sslmode=disable
TRACKER_DSN: ${TRACKER_DSN}
STELLAR_ENVIRONMENT: ${STELLAR_ENVIRONMENT}
TRACKER_DSN: ${TRACKER_DSN:-}
STELLAR_ENVIRONMENT: ${STELLAR_ENVIRONMENT:-}
volumes:
postgres-db:
driver: local
19 changes: 14 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
module github.com/stellar/wallet-backend

go 1.23.2
go 1.24

toolchain go1.24.2

require (
github.com/99designs/gqlgen v0.17.76
github.com/alitto/pond v1.9.2
github.com/avast/retry-go/v4 v4.6.1
github.com/aws/aws-sdk-go v1.55.7
Expand All @@ -12,7 +15,6 @@ require (
github.com/go-chi/chi v4.1.2+incompatible
github.com/go-playground/validator/v10 v10.27.0
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/google/uuid v1.6.0
github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.28
Expand All @@ -24,8 +26,10 @@ require (
github.com/spf13/viper v1.20.1
github.com/stellar/go v0.0.0-20250903085211-00c0b06cd7cc
github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2
github.com/stellar/stellar-rpc v0.9.6-0.20250523162628-6bb9d7a387d5
github.com/stellar/stellar-rpc v0.9.6-0.20250618231249-2d3e8ff69365
github.com/stretchr/testify v1.10.0
github.com/vektah/gqlparser/v2 v2.5.30
github.com/vikstrous/dataloadgen v0.0.9
golang.org/x/term v0.33.0
golang.org/x/text v0.27.0
)
Expand All @@ -39,12 +43,13 @@ require (
cloud.google.com/go/iam v1.2.2 // indirect
cloud.google.com/go/monitoring v1.21.2 // indirect
cloud.google.com/go/storage v1.49.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
Expand Down Expand Up @@ -90,11 +95,14 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gorilla/schema v1.4.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/guregu/null v4.0.0+incompatible // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
Expand All @@ -113,6 +121,7 @@ require (
github.com/rs/cors v1.11.0 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
Expand Down Expand Up @@ -143,7 +152,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
google.golang.org/grpc v1.67.3 // indirect
google.golang.org/protobuf v1.36.5 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/djherbis/atime.v1 v1.0.0 // indirect
gopkg.in/djherbis/stream.v1 v1.3.1 // indirect
gopkg.in/tylerb/graceful.v1 v1.2.15 // indirect
Expand Down
Loading
Loading