// Package aerecloud is the official Go client for the Aere Cloud API (Aere Network, chain 2800). // // Same routes and semantics as @aere/cloud (JS), aere-cloud (Python) and aere-cloud (Java) 1.5.2. Zero dependencies // beyond the Go standard library. JSON routes return the server's JSON as json.RawMessage, so the integrator decodes it // into whatever types they already use; the two routes that return FILES (the audit export and the check of a kept // export) return typed results with the digest computed locally from the bytes received. // // Non-2xx answers are returned as *Error with the HTTP status, the server's body and the path. package aerecloud // Nota interna: comentariile de implementare sunt in romana fara diacritice, ca in celelalte SDK-uri ale casei; // documentatia publica a API-ului (comentariile exportate) e in engleza. import ( "bytes" "context" "crypto/hmac" "crypto/sha256" "crypto/subtle" "encoding/hex" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strconv" "strings" "time" ) // Version of this client, sent in the User-Agent (the edge refuses generic library agents on every route). const Version = "1.5.2" // DefaultBaseURL is the production gateway. const DefaultBaseURL = "https://cloud.aere.network/v1" // Error is a non-2xx answer from Aere Cloud (Status 0 means the request never got an answer). type Error struct { Status int Body []byte Path string } func (e *Error) Error() string { b := string(e.Body) if b == "" { b = "request failed" } return fmt.Sprintf("Aere Cloud %d on %s: %s", e.Status, e.Path, b) } // Client talks to one Aere Cloud gateway with one API key (ak2800.
.). type Client struct { apiKey string baseURL string http *http.Client } // New returns a client for the production gateway. The API key is required. func New(apiKey string) (*Client, error) { return NewWithBase(apiKey, DefaultBaseURL, nil) } // NewWithBase returns a client for another base URL (a test server) and an optional *http.Client. func NewWithBase(apiKey, baseURL string, hc *http.Client) (*Client, error) { if apiKey == "" { return nil, errors.New("aerecloud: apiKey is required (ak2800.
.)") } if hc == nil { // fara redirectari urmate: un 3xx e un raspuns, nu un drum spre alta gazda care ar primi cheia hc = &http.Client{Timeout: 30 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }} } return &Client{apiKey: apiKey, baseURL: strings.TrimRight(baseURL, "/"), http: hc}, nil } type raspuns struct { status int body []byte headers http.Header } func (c *Client) do(ctx context.Context, method, path string, body any, auth bool) (*raspuns, error) { var rd io.Reader if body != nil { b, err := json.Marshal(body) if err != nil { return nil, err } rd = bytes.NewReader(b) } req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, rd) if err != nil { return nil, err } req.Header.Set("user-agent", "aere-cloud-go/"+Version) if body != nil { req.Header.Set("content-type", "application/json") } if auth { req.Header.Set("x-api-key", c.apiKey) } r, err := c.http.Do(req) if err != nil { // mesajul de retea nu poarta cheia (e in antet, nu in URL) return nil, &Error{Status: 0, Body: []byte(err.Error()), Path: path} } defer r.Body.Close() b, err := io.ReadAll(r.Body) if err != nil { return nil, &Error{Status: 0, Body: []byte(err.Error()), Path: path} } return &raspuns{status: r.StatusCode, body: b, headers: r.Header}, nil } func (c *Client) jsonReq(ctx context.Context, method, path string, body any, auth bool) (json.RawMessage, error) { r, err := c.do(ctx, method, path, body, auth) if err != nil { return nil, err } if r.status < 200 || r.status >= 300 { return nil, &Error{Status: r.status, Body: r.body, Path: path} } return json.RawMessage(r.body), nil } func esc(s string) string { return url.PathEscape(s) } // ---- health, account, JSON-RPC ---- func (c *Client) Health(ctx context.Context) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/health", nil, false) } func (c *Client) Account(ctx context.Context) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/account", nil, true) } // RPC sends one JSON-RPC call and returns the whole JSON-RPC envelope. func (c *Client) RPC(ctx context.Context, method string, params []any) (json.RawMessage, error) { if params == nil { params = []any{} } return c.jsonReq(ctx, "POST", "/rpc", map[string]any{"jsonrpc": "2.0", "id": 1, "method": method, "params": params}, true) } // BlockNumber is eth_blockNumber as an integer. func (c *Client) BlockNumber(ctx context.Context) (uint64, error) { b, err := c.RPC(ctx, "eth_blockNumber", nil) if err != nil { return 0, err } var env struct { Result string `json:"result"` } if err := json.Unmarshal(b, &env); err != nil || !strings.HasPrefix(env.Result, "0x") { return 0, &Error{Status: 200, Body: b, Path: "/rpc"} } return strconv.ParseUint(env.Result[2:], 16, 64) } // ---- post-quantum verification, chain data, notarization, proofs ---- // PQVerify: payload like {"scheme":"ml-dsa-44","publicKey":"0x..","signature":"0x..","message":"0x.."}. func (c *Client) PQVerify(ctx context.Context, payload any) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/pq/verify", payload, true) } func (c *Client) DataHead(ctx context.Context) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/data/head", nil, true) } func (c *Client) Validators(ctx context.Context) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/data/validators", nil, true) } func (c *Client) Anchors(ctx context.Context, limit int) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/data/anchors?limit="+strconv.Itoa(limit), nil, true) } func (c *Client) Anchor(ctx context.Context, height uint64) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/data/anchors/"+strconv.FormatUint(height, 10), nil, true) } // Finality returns the post-quantum finality point (finalized/safe = the parent of the last certified anchor), 2026-09-26. func (c *Client) Finality(ctx context.Context) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/data/finality", nil, true) } func (c *Client) Transfers(ctx context.Context, address string, limit int) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/data/transfers?address="+url.QueryEscape(address)+"&limit="+strconv.Itoa(limit), nil, true) } func (c *Client) Notarize(ctx context.Context, hash string) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/notarize", map[string]string{"hash": hash}, true) } func (c *Client) ProofOf(ctx context.Context, hash string) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/notarize/"+esc(hash), nil, true) } // Proof: the proof with its post-quantum finality (first appearance, covering anchor, verdict, verification steps). func (c *Client) Proof(ctx context.Context, hash string) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/proof/"+esc(hash), nil, true) } // ---- quantum readiness ---- // Readiness scans a domain's public TLS edge. Free without a key; attest=true notarizes the report digest and sends the key. func (c *Client) Readiness(ctx context.Context, domain string, fresh, attest bool) (json.RawMessage, error) { b := map[string]any{"domain": domain} if fresh { b["fresh"] = true } if attest { b["attest"] = true } return c.jsonReq(ctx, "POST", "/pq/readiness", b, attest) } func (c *Client) ReadinessReport(ctx context.Context, domain string) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/pq/readiness/"+esc(domain), nil, false) } // PerimeterOptions: Hosts may be nil; Discover nil keeps the server default; Attest notarizes only a COMPLETE report. type PerimeterOptions struct { Hosts []string Discover *bool Attest bool } // ReadinessPerimeter: the whole public perimeter of a domain in one keyed report; call again while summary.complete is false. func (c *Client) ReadinessPerimeter(ctx context.Context, domain string, o PerimeterOptions) (json.RawMessage, error) { b := map[string]any{"domain": domain} if o.Hosts != nil { b["hosts"] = o.Hosts } if o.Discover != nil && !*o.Discover { b["discover"] = false } if o.Attest { b["attest"] = true } return c.jsonReq(ctx, "POST", "/pq/readiness/perimeter", b, true) } // PQFinality: AIP-21 per-block post-quantum finality. client: "besu", "nethermind" or "both" (with `agreement`). No key. func (c *Client) PQFinality(ctx context.Context, block, network, client string) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/pq/finality/"+esc(block)+"?network="+url.QueryEscape(network)+"&client="+url.QueryEscape(client), nil, false) } // ---- audit log ---- // AuditLog: the paged JSON view of one UTC day (day "" = today), with the digest of the day file. func (c *Client) AuditLog(ctx context.Context, day string, offset, limit int) (json.RawMessage, error) { q := "?offset=" + strconv.Itoa(offset) + "&limit=" + strconv.Itoa(limit) if day != "" { q += "&day=" + url.QueryEscape(day) } return c.jsonReq(ctx, "GET", "/account/audit"+q, nil, true) } // AuditExport is a day as a file. For jsonl, Verified reports whether the sha256 of the bytes RECEIVED equals the stated // digest (computed here, not taken on trust); for cef it is nil, because the digest is the one of the source file. type AuditExport struct { Day, Format, Digest string Bytes []byte Entries int Verified *bool } func sha256Hex(b []byte) string { h := sha256.Sum256(b); return "0x" + hex.EncodeToString(h[:]) } func (c *Client) AuditExport(ctx context.Context, day, format string) (*AuditExport, error) { if format == "" { format = "jsonl" } p := "/account/audit?format=" + url.QueryEscape(format) if day != "" { p += "&day=" + url.QueryEscape(day) } r, err := c.do(ctx, "GET", p, nil, true) if err != nil { return nil, err } if r.status < 200 || r.status >= 300 { return nil, &Error{Status: r.status, Body: r.body, Path: "/account/audit"} } e := &AuditExport{Day: r.headers.Get("x-aere-day"), Format: format, Digest: r.headers.Get("x-aere-digest"), Bytes: r.body} e.Entries, _ = strconv.Atoi(r.headers.Get("x-aere-entries")) if format == "jsonl" { ok := e.Digest != "" && e.Digest == sha256Hex(r.body) e.Verified = &ok } return e, nil } // AuditRetention reads the retention of the audit log with the API key (read-only). Retention is SET with a wallet // signature in the console, never with the key: a stolen key must not be able to shorten the trail of its own use. func (c *Client) AuditRetention(ctx context.Context) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/account/audit/retention", nil, true) } // KeptCheck: does an export you KEPT still match the day we hold ("held"), or the tombstone a deleted day left ("expired")? type KeptCheck struct { Day, State, Digest, Local, DeletedAt string Matches bool } // AuditVerifyKept hashes YOUR bytes locally and compares them with the day we hold or with the tombstone of a deleted day // (410). Any other failure is returned as an error, never as "does not match". func (c *Client) AuditVerifyKept(ctx context.Context, day string, kept []byte) (*KeptCheck, error) { local := sha256Hex(kept) r, err := c.do(ctx, "GET", "/account/audit?format=jsonl&day="+url.QueryEscape(day), nil, true) if err != nil { return nil, err } if r.status == 410 { var t struct { Expired *struct { Digest string `json:"digest"` DeletedAt string `json:"deletedAt"` } `json:"expired"` } if json.Unmarshal(r.body, &t) == nil && t.Expired != nil && t.Expired.Digest != "" { return &KeptCheck{Day: day, State: "expired", Digest: t.Expired.Digest, Local: local, Matches: local == t.Expired.Digest, DeletedAt: t.Expired.DeletedAt}, nil } } if r.status < 200 || r.status >= 300 { return nil, &Error{Status: r.status, Body: r.body, Path: "/account/audit"} } d := sha256Hex(r.body) return &KeptCheck{Day: day, State: "held", Digest: d, Local: local, Matches: local == d}, nil } // ---- webhooks and gas sponsorship ---- func (c *Client) ListWebhooks(ctx context.Context) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/webhooks", nil, true) } func (c *Client) CreateWebhook(ctx context.Context, spec any) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/webhooks", spec, true) } func (c *Client) DeleteWebhook(ctx context.Context, id string) (json.RawMessage, error) { return c.jsonReq(ctx, "DELETE", "/webhooks/"+esc(id), nil, true) } func (c *Client) SponsorHealth(ctx context.Context) (json.RawMessage, error) { return c.jsonReq(ctx, "GET", "/sponsor/health", nil, true) } func (c *Client) SponsorCreateAccount(ctx context.Context, body any) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/sponsor/createAccount", body, true) } // SponsorCreatePQAccount: a smart account with a POST-QUANTUM owner (Falcon-512, 0x09-prefixed, 897 bytes), gas sponsored. func (c *Client) SponsorCreatePQAccount(ctx context.Context, falconPubKeyHex string, salt uint64) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/sponsor/createPqAccount", map[string]any{"falconPubKey": falconPubKeyHex, "salt": salt}, true) } func (c *Client) SponsorExecute(ctx context.Context, body any) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/sponsor/execute", body, true) } // VerifyWebhook checks a webhook delivery's HMAC-SHA256 signature in constant time. rawBody must be the EXACT bytes // received, not re-serialized JSON. Never act on a webhook without this. func VerifyWebhook(rawBody []byte, signatureHeader, secret string) bool { m := hmac.New(sha256.New, []byte(secret)) m.Write(rawBody) want := []byte(hex.EncodeToString(m.Sum(nil))) return subtle.ConstantTimeCompare(want, []byte(signatureHeader)) == 1 }