// 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.8.0" // 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) } // Verify is the Trust API (1.6.0): one AERE Proof Protocol (AIP-23) envelope of any kind -> VALID / INVALID / PARTIAL with the levels checked and the command that reproduces the verdict; the judgement is the published reference verifier. Needs a gateway that serves POST /v1/verify. // chain: 2800 or 28001; 0 = the chain the envelope declares. // envelope: the envelope's JSON bytes as you received them. The verdict hashes the statement in its own key order, so the bytes are // sent as they are; a Go map would sort the keys and turn a valid envelope into an INVALID one (measured 2026-09-29, before 1.6.0 shipped). func (c *Client) Verify(ctx context.Context, envelope json.RawMessage, chain uint64) (json.RawMessage, error) { p := "/verify" if chain > 0 { p += "?chain=" + strconv.FormatUint(chain, 10) } return c.jsonReq(ctx, "POST", p, envelope, true) } // IdentityVerifyRequest is the body of POST /v1/identity/verify (1.7.0). The presentation, keys, status lists and revocations are kept as // their JSON bytes: a Go map or float64 would change numbers above 2^53 and the canonical text the signatures cover. type IdentityVerifyRequest struct { Presentation json.RawMessage `json:"presentation"` Audience string `json:"audience"` Nonce string `json:"nonce"` TrustedIssuers []json.RawMessage `json:"trustedIssuers,omitempty"` StatusLists []json.RawMessage `json:"statusLists,omitempty"` Revocations []json.RawMessage `json:"revocations,omitempty"` MaxAgeSeconds int `json:"maxAgeSeconds,omitempty"` } // ComplianceCheckRequest is the body of POST /v1/compliance/check (1.7.0). type ComplianceCheckRequest struct { Presentation json.RawMessage `json:"presentation"` Policy json.RawMessage `json:"policy"` Audience string `json:"audience"` Nonce string `json:"nonce"` StatusLists []json.RawMessage `json:"statusLists,omitempty"` Revocations []json.RawMessage `json:"revocations,omitempty"` Record bool `json:"record,omitempty"` } // IdentityVerify (1.7.0): an AERE Identity presentation judged by the published command line -> VALID / PARTIAL / INVALID with every row, // the claims shown and the subject when no row failed, and the command (with the moment) that reproduces the verdict. Read "verdict": // PARTIAL means some rows could not be judged. Needs a gateway that serves POST /v1/identity/verify. func (c *Client) IdentityVerify(ctx context.Context, req IdentityVerifyRequest) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/identity/verify", req, true) } // ComplianceCheck (1.7.0): your policy judged on an AERE Identity presentation -> COMPLIANT / NOT_COMPLIANT with the reasons, and with // Record the record as an AIP-23 envelope without personal data. No claims come back. func (c *Client) ComplianceCheck(ctx context.Context, req ComplianceCheckRequest) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/compliance/check", req, true) } // PaymentsRiskRequest is the body of POST /v1/payments/risk (1.8.0). The policy and the inputs are kept as their JSON bytes: the // verdict's hashes cover their canonical text, and a Go map or float64 would change an amount above 2^53. type PaymentsRiskRequest struct { Policy json.RawMessage `json:"policy"` Inputs json.RawMessage `json:"inputs"` } // PaymentsReceiptRequest is the body of POST /v1/payments/receipt (1.8.0). type PaymentsReceiptRequest struct { Policy json.RawMessage `json:"policy"` Inputs json.RawMessage `json:"inputs"` TxHash string `json:"txHash"` Purpose string `json:"purpose,omitempty"` } // PaymentsVerifyRequest is the body of POST /v1/payments/verify (1.8.0): the receipt as PaymentsReceipt returned it. type PaymentsVerifyRequest struct { Receipt json.RawMessage `json:"receipt"` Policy json.RawMessage `json:"policy"` Inputs json.RawMessage `json:"inputs"` } // PaymentsRisk (1.8.0): one payment by a person or a company judged under YOUR declared policy by the published module // (payments/risc.mjs) -> ALLOW / REFUSE with the reasons, what was measured and what was NOT (named in "unmeasured", never simulated), // and the hashes from which anyone reproduces the verdict. A refusal is a verdict (HTTP 200), not an error. func (c *Client) PaymentsRisk(ctx context.Context, req PaymentsRiskRequest) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/payments/risk", req, true) } // PaymentsReceipt (1.8.0): the receipt of a payment ALREADY on the chain the gateway serves; the verdict is reproduced and must be // ALLOW, the transaction must be the payment judged, and the receipt (instruction + AIP-23 settlement envelope) comes back notarized. // An *Error with Status 409 (risk_refused, tx_mismatch, chain_mismatch), 404 (tx_not_found) or 502 (notarization_failed) otherwise. func (c *Client) PaymentsReceipt(ctx context.Context, req PaymentsReceiptRequest) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/payments/receipt", req, true) } // PaymentsVerify (1.8.0): the published module's rules against the policy, the inputs and the chain, and the envelope judged by the // reference verifier -> VALID / INVALID / PARTIAL (PARTIAL: the notarization is not under a certified anchor yet). func (c *Client) PaymentsVerify(ctx context.Context, req PaymentsVerifyRequest) (json.RawMessage, error) { return c.jsonReq(ctx, "POST", "/payments/verify", req, 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 certified point (finalized/safe = the parent of the last certified anchor; evidence that its signers sealed it, not by itself that it was decided, AIP-22 erratum 2026-10-01), 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 under a post-quantum certificate (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 seal record (participation, not by itself the decision: AIP-21 erratum 2026-10-01). 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 }