diff --git a/README.md b/README.md index 2645aea..208f0d5 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ const { valid } = await aere.pqVerify({ const receipt = await aere.notarize('0x' + sha256Hex); const proof = await aere.proofOf('0x' + sha256Hex); // { notarized, firstSeenAt, … } -// 1.2.0 (2026-09-17): the proof with its post-quantum finality: first appearance from the contract log, +// 1.2.0 (2026-09-17): the proof under a post-quantum certificate: first appearance from the contract log, // the anchor that covers it, a verdict, and how to verify every claim without Aere Cloud const full = await aere.proof('0x' + sha256Hex); // { block, txHash, finality: 'post-quantum', pqAnchor: {…}, verify: {…} } @@ -76,6 +76,12 @@ const r = await aere.verify(envelope, { chain: 2800 }); // { verdict const id = await aere.identityVerify({ presentation, audience: 'https://your.app/verifier', nonce, trustedIssuers: [issuerId], statusLists: [list] }); const ok = await aere.complianceCheck({ presentation, policy, audience: 'https://your.app/verifier', nonce, statusLists: [list], record: true }); +// payments (1.8.0): the risk step and the receipt for a person or a company, judged by the published module `payments/`. Prices are in +// USD at the rate YOU declare in the inputs (it is not a market price); the asset is only the settlement. A REFUSE is a verdict, not an error. +const risk = await aere.paymentsRisk({ policy, inputs }); // ALLOW / REFUSE, reasons, unmeasured, verdictHash +const { receipt } = await aere.paymentsReceipt({ policy, inputs, txHash }); // after the payer paid: instruction + notarized AIP-23 envelope +const check = await aere.paymentsVerify({ receipt, policy, inputs }); // VALID / INVALID / PARTIAL + // transfer history for an address (token from genesis, native from launch) const tx = await aere.transfers('0xYourAddress', 50); diff --git a/csharp/Aere.Cloud.Tests/Program.cs b/csharp/Aere.Cloud.Tests/Program.cs index 22041ff..592f6cc 100644 --- a/csharp/Aere.Cloud.Tests/Program.cs +++ b/csharp/Aere.Cloud.Tests/Program.cs @@ -1,4 +1,4 @@ -// Probele clientului .NET (Aere.Cloud 1.7.0), fara framework de probe: fiecare proba tipareste "--- PASS: Nume" sau "--- FAIL: Nume: motiv", +// Probele clientului .NET (Aere.Cloud 1.8.0), fara framework de probe: fiecare proba tipareste "--- PASS: Nume" sau "--- FAIL: Nume: motiv", // iar codul de iesire e numarul de probe picate (0 = toate verzi). Aceleasi garzi ca la clientul Go: cheia obligatorie, cheia trimisa NUMAI // unde trebuie, redirectarile NEurmate (dovedit pe un server local real, fiindca numai handlerul implicit al SDK-ului o poate arata), // exportul jurnalului verificat pe octetii primiti, verificarea unei copii pastrate (tinuta / stearsa / eroare), webhook-ul in timp constant. @@ -49,7 +49,7 @@ await Proba("ReadinessCheiaNumaiLaAttest", async () => await c.PqFinalityAsync(); Cer(!ciot.Ultima.Antete.ContainsKey("x-api-key"), "finalitatea AIP-21 fara cheie"); await c.HealthAsync(); Cer(!ciot.Ultima.Antete.ContainsKey("x-api-key"), "health fara cheie"); await c.AccountAsync(); Cer(ciot.Ultima.Antete.GetValueOrDefault("x-api-key") == Cheie, "account cu cheie"); - Cer(ciot.Ultima.Antete.GetValueOrDefault("user-agent", "").StartsWith("aere-cloud-csharp/1.7.0"), "user-agent-ul clientului"); + Cer(ciot.Ultima.Antete.GetValueOrDefault("user-agent", "").StartsWith("aere-cloud-csharp/1.8.0"), "user-agent-ul clientului"); }); await Proba("PerimetruFinalitateDovada", async () => @@ -88,6 +88,19 @@ await Proba("IdentityCompliance", async () => Cer(ciot.Ultima.Url.EndsWith("/v1/compliance/check") && ciot.Ultima.Antete.ContainsKey("x-api-key") && ciot.Ultima.Corp == "{\"record\":true}", "POST /compliance/check: " + ciot.Ultima.Url + " " + ciot.Ultima.Corp); }); +await Proba("Payments", async () => +{ + var (c, ciot) = Client(_ => Json(200, "{\"verdict\":\"REFUSE\"}")); + var r = await c.PaymentsRiskAsync(new Dictionary { ["policy"] = "p", ["inputs"] = "i" }); + Cer(ciot.Ultima.Metoda == "POST" && ciot.Ultima.Url.EndsWith("/v1/payments/risk") && ciot.Ultima.Antete.ContainsKey("x-api-key"), "POST /payments/risk cu cheie: " + ciot.Ultima.Url); + Cer(ciot.Ultima.Corp == "{\"policy\":\"p\",\"inputs\":\"i\"}", "cererea trimisa ca atare: " + ciot.Ultima.Corp); + Cer(r.GetProperty("verdict").GetString() == "REFUSE", "un REFUSE e un verdict (200), nu o eroare"); + await c.PaymentsReceiptAsync(new Dictionary { ["txHash"] = "0x11" }); + Cer(ciot.Ultima.Url.EndsWith("/v1/payments/receipt") && ciot.Ultima.Antete.ContainsKey("x-api-key") && ciot.Ultima.Corp == "{\"txHash\":\"0x11\"}", "POST /payments/receipt: " + ciot.Ultima.Url + " " + ciot.Ultima.Corp); + await c.PaymentsVerifyAsync(new Dictionary { ["receipt"] = "r" }); + Cer(ciot.Ultima.Url.EndsWith("/v1/payments/verify") && ciot.Ultima.Antete.ContainsKey("x-api-key") && ciot.Ultima.Corp == "{\"receipt\":\"r\"}", "POST /payments/verify: " + ciot.Ultima.Url + " " + ciot.Ultima.Corp); +}); + await Proba("ExportulZilei", async () => { var corp = Encoding.UTF8.GetBytes("{\"a\":1}\n{\"b\":2}\n"); diff --git a/csharp/Aere.Cloud/Aere.Cloud.csproj b/csharp/Aere.Cloud/Aere.Cloud.csproj index 6df7bfe..ba807e1 100644 --- a/csharp/Aere.Cloud/Aere.Cloud.csproj +++ b/csharp/Aere.Cloud/Aere.Cloud.csproj @@ -10,7 +10,7 @@ Aere.Cloud Aere.Cloud Aere.Cloud - 1.7.0 + 1.8.0 Aere Network Official .NET client for the Aere Cloud API (Aere Network, chain 2800): RPC, post-quantum verification, the Trust API (AERE Proof Protocol envelopes), chain data, notarization and proofs, quantum readiness, audit log, webhooks and gas sponsorship. MIT diff --git a/csharp/Aere.Cloud/AereCloudClient.cs b/csharp/Aere.Cloud/AereCloudClient.cs index daa8edd..90d5d45 100644 --- a/csharp/Aere.Cloud/AereCloudClient.cs +++ b/csharp/Aere.Cloud/AereCloudClient.cs @@ -1,6 +1,6 @@ // Aere.Cloud - the official .NET client for the Aere Cloud API (Aere Network, chain 2800). // -// Same routes and semantics as @aere/cloud (JS), aere-cloud (Python), aere-cloud (Java) and the Go client, 1.7.0. No dependency beyond +// Same routes and semantics as @aere/cloud (JS), aere-cloud (Python), aere-cloud (Java) and the Go client, 1.8.0. No dependency beyond // the .NET base library. JSON routes return the server's JSON as a JsonElement, so the integrator binds it to 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 thrown as AereCloudException (HTTP status, server body, path). @@ -52,7 +52,7 @@ public sealed record KeptCheck(string Day, string State, string Digest, string L public sealed class AereCloudClient : IDisposable { /// Version of this client, sent in the User-Agent (the edge refuses generic library agents on every route). - public const string Version = "1.7.0"; + public const string Version = "1.8.0"; /// The production gateway. public const string DefaultBaseUrl = "https://cloud.aere.network/v1"; @@ -145,6 +145,18 @@ public sealed class AereCloudClient : IDisposable /// with the reasons and, with record, an AIP-23 record without personal data. public Task ComplianceCheckAsync(object request, CancellationToken ct = default) => JsonAsync(HttpMethod.Post, "/compliance/check", request, true, ct); + /// Payments, the risk step (1.8.0): {policy, inputs} -> 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; judged by the published module payments/risc.mjs. + /// A refusal is a verdict (HTTP 200), not an error. + public Task PaymentsRiskAsync(object request, CancellationToken ct = default) => JsonAsync(HttpMethod.Post, "/payments/risk", request, true, ct); + + /// Payments, the receipt (1.8.0): {policy, inputs, txHash, purpose?} for a payment ALREADY on the chain the gateway serves -> the receipt + /// (instruction + AIP-23 settlement envelope), notarized. AereCloudException 409 (risk_refused, tx_mismatch, chain_mismatch), 404, 502. + public Task PaymentsReceiptAsync(object request, CancellationToken ct = default) => JsonAsync(HttpMethod.Post, "/payments/receipt", request, true, ct); + + /// Payments, verify a receipt (1.8.0): {receipt:{instruction, envelope}, policy, inputs} -> VALID / INVALID / PARTIAL. + public Task PaymentsVerifyAsync(object request, CancellationToken ct = default) => JsonAsync(HttpMethod.Post, "/payments/verify", request, true, ct); + /// GET /data/head. public Task DataHeadAsync(CancellationToken ct = default) => JsonAsync(HttpMethod.Get, "/data/head", null, true, ct); /// GET /data/validators. @@ -153,7 +165,7 @@ public sealed class AereCloudClient : IDisposable public Task AnchorsAsync(int limit = 10, CancellationToken ct = default) => JsonAsync(HttpMethod.Get, "/data/anchors?limit=" + limit, null, true, ct); /// GET /data/anchors/{height}. public Task AnchorAsync(ulong height, CancellationToken ct = default) => JsonAsync(HttpMethod.Get, "/data/anchors/" + height, null, true, ct); - /// The post-quantum finality point (finalized/safe = the parent of the last certified anchor). + /// 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). public Task FinalityAsync(CancellationToken ct = default) => JsonAsync(HttpMethod.Get, "/data/finality", null, true, ct); /// GET /data/transfers?address=&limit=. public Task TransfersAsync(string address, int limit = 50, CancellationToken ct = default) => @@ -162,7 +174,7 @@ public sealed class AereCloudClient : IDisposable public Task NotarizeAsync(string hash, CancellationToken ct = default) => JsonAsync(HttpMethod.Post, "/notarize", new Dictionary { ["hash"] = hash }, true, ct); /// GET /notarize/{hash}. public Task ProofOfAsync(string hash, CancellationToken ct = default) => JsonAsync(HttpMethod.Get, "/notarize/" + Esc(hash), null, true, ct); - /// The proof with its post-quantum finality (first appearance, covering anchor, verdict, verification steps). + /// The proof under a post-quantum certificate (first appearance, covering anchor, verdict, verification steps). public Task ProofAsync(string hash, CancellationToken ct = default) => JsonAsync(HttpMethod.Get, "/proof/" + Esc(hash), null, true, ct); // ---- quantum readiness ---- @@ -188,7 +200,7 @@ public sealed class AereCloudClient : IDisposable return JsonAsync(HttpMethod.Post, "/pq/readiness/perimeter", b, true, ct); } - /// AIP-21 per-block post-quantum finality. client: "besu", "nethermind" or "both" (with `agreement`). No key. + /// 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. public Task PqFinalityAsync(string block = "latest", string network = "testnet", string client = "besu", CancellationToken ct = default) => JsonAsync(HttpMethod.Get, "/pq/finality/" + Esc(block) + "?network=" + Esc(network) + "&client=" + Esc(client), null, false, ct); diff --git a/csharp/README.md b/csharp/README.md index 905b4ce..925c26b 100644 --- a/csharp/README.md +++ b/csharp/README.md @@ -2,8 +2,8 @@ The official .NET client for the [Aere Cloud API](https://aere.network/cloud.html) on Aere Network (chain 2800). **No dependencies** beyond the .NET base library (`System.Net.Http`, `System.Text.Json`, `System.Security.Cryptography`). -Targets .NET 8 (LTS) and runs on later versions. Same routes and semantics as the JS, Python, Go and Java clients 1.7.0, -including `VerifyAsync`, the Trust API, and `IdentityVerifyAsync` / `ComplianceCheckAsync`. +Targets .NET 8 (LTS) and runs on later versions. Same routes and semantics as the JS, Python, Go and Java clients 1.8.0, +including `VerifyAsync`, the Trust API, and `IdentityVerifyAsync` / `ComplianceCheckAsync`, and the payments routes `PaymentsRiskAsync` / `PaymentsReceiptAsync` / `PaymentsVerifyAsync` (1.8.0: the risk step and the receipt, judged by the published module `payments/`; USD at the rate you declare, a REFUSE is a verdict). JSON routes return the server's JSON as a `JsonElement`, so you bind it to the types you already use. The two routes that return files (the audit export and the check of a kept export) return typed results, with the SHA-256 computed diff --git a/csharp/control-negativ.sh b/csharp/control-negativ.sh index 43ed11e..4073443 100644 --- a/csharp/control-negativ.sh +++ b/csharp/control-negativ.sh @@ -26,6 +26,8 @@ P = [ ("lantul cerut scapat din Trust API", '(chain > 0 ? "?chain=" + chain : "")', '(chain > 0 ? "" : "")', "TrustApiVerify"), ("conformitatea pe ruta identitatii (1.7.0)", 'JsonAsync(HttpMethod.Post, "/compliance/check", request, true, ct);', 'JsonAsync(HttpMethod.Post, "/identity/verify", request, true, ct);', "IdentityCompliance"), ("identitatea fara cheie (1.7.0)", 'JsonAsync(HttpMethod.Post, "/identity/verify", request, true, ct);', 'JsonAsync(HttpMethod.Post, "/identity/verify", request, false, ct);', "IdentityCompliance"), + ("chitanta pe ruta riscului (1.8.0)", 'JsonAsync(HttpMethod.Post, "/payments/receipt", request, true, ct);', 'JsonAsync(HttpMethod.Post, "/payments/risk", request, true, ct);', "Payments"), + ("verificarea platii fara cheie (1.8.0)", 'JsonAsync(HttpMethod.Post, "/payments/verify", request, true, ct);', 'JsonAsync(HttpMethod.Post, "/payments/verify", request, false, ct);', "Payments"), ("un rezultat RPC fara forma citit ca zero", ' throw new AereCloudException(200, e.GetRawText(), "/rpc");', ' return 0;', "EroriSiBlockNumber"), ] if sys.argv[1] == "n": print(len(P)); sys.exit(0) diff --git a/go/README.md b/go/README.md index 90b7169..c6cd736 100644 --- a/go/README.md +++ b/go/README.md @@ -32,6 +32,9 @@ verdict, err := c.Verify(ctx, json.RawMessage(envelopeBytes), 2800) // identity and compliance (1.7.0): the presentation, keys, lists and policy travel as their JSON bytes (numbers above 2^53 stay exact) id, err := c.IdentityVerify(ctx, aerecloud.IdentityVerifyRequest{Presentation: presBytes, Audience: "https://your.app/verifier", Nonce: nonce, +// payments (1.8.0): the policy and the inputs travel as their JSON bytes (an amount above 2^53 stays exact); a REFUSE is a verdict +risk, err := c.PaymentsRisk(ctx, aerecloud.PaymentsRiskRequest{Policy: policyBytes, Inputs: inputsBytes}) +rec, err := c.PaymentsReceipt(ctx, aerecloud.PaymentsReceiptRequest{Policy: policyBytes, Inputs: inputsBytes, TxHash: txHash}) TrustedIssuers: []json.RawMessage{json.RawMessage(`"aere-id:..."`)}, StatusLists: []json.RawMessage{listBytes}}) ok, err := c.ComplianceCheck(ctx, aerecloud.ComplianceCheckRequest{Presentation: presBytes, Policy: policyBytes, Audience: "https://your.app/verifier", Nonce: nonce, Record: true}) diff --git a/go/aerecloud.go b/go/aerecloud.go index 443f770..501afdd 100644 --- a/go/aerecloud.go +++ b/go/aerecloud.go @@ -30,7 +30,7 @@ import ( ) // Version of this client, sent in the User-Agent (the edge refuses generic library agents on every route). -const Version = "1.7.0" +const Version = "1.8.0" // DefaultBaseURL is the production gateway. const DefaultBaseURL = "https://cloud.aere.network/v1" @@ -210,6 +210,48 @@ func (c *Client) ComplianceCheck(ctx context.Context, req ComplianceCheckRequest 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) } @@ -223,7 +265,7 @@ func (c *Client) Anchor(ctx context.Context, height uint64) (json.RawMessage, er 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. +// 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) } @@ -237,7 +279,7 @@ func (c *Client) ProofOf(ctx context.Context, hash string) (json.RawMessage, err 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). +// 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) } @@ -281,7 +323,7 @@ func (c *Client) ReadinessPerimeter(ctx context.Context, domain string, o Perime 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. +// 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) } diff --git a/go/aerecloud_test.go b/go/aerecloud_test.go index 34c6e0c..addf900 100644 --- a/go/aerecloud_test.go +++ b/go/aerecloud_test.go @@ -256,3 +256,30 @@ func TestVerifyWebhook(t *testing.T) { t.Fatal("o semnatura gresita sau un corp atins a trecut") } } + +// 1.8.0: cererile de plati pleaca pe rutele lor, cu cheie, cu politica si intrarile NESCHIMBATE (o suma peste 2^53 ramane exacta) +func TestPaymentsPastreazaOctetii(t *testing.T) { + s, c := porneste(t) + pol := `{"v":1,"kind":"aere-payment-risk-policy","id":"p"}` + in := `{"chainId":28001,"amount":12345678901234567891,"asset":"native"}` + if _, err := c.PaymentsRisk(context.Background(), PaymentsRiskRequest{Policy: json.RawMessage(pol), Inputs: json.RawMessage(in)}); err != nil { + t.Fatal(err) + } + v := s.ultima() + if v.Method != "POST" || v.Path != "/v1/payments/risk" || v.Key == "" || !strings.Contains(v.Body, `"inputs":`+in) || !strings.Contains(v.Body, `"policy":`+pol) { + t.Fatalf("cererea de risc: %+v", v) + } + if _, err := c.PaymentsReceipt(context.Background(), PaymentsReceiptRequest{Policy: json.RawMessage(pol), Inputs: json.RawMessage(in), TxHash: "0x11", Purpose: "order 7"}); err != nil { + t.Fatal(err) + } + if v = s.ultima(); v.Path != "/v1/payments/receipt" || v.Key == "" || !strings.Contains(v.Body, `"txHash":"0x11"`) || !strings.Contains(v.Body, `"purpose":"order 7"`) || !strings.Contains(v.Body, `"inputs":`+in) { + t.Fatalf("cererea de chitanta: %+v", v) + } + rec := `{"instruction":{"amount":"12345678901234567891"},"envelope":{"v":1}}` + if _, err := c.PaymentsVerify(context.Background(), PaymentsVerifyRequest{Receipt: json.RawMessage(rec), Policy: json.RawMessage(pol), Inputs: json.RawMessage(in)}); err != nil { + t.Fatal(err) + } + if v = s.ultima(); v.Path != "/v1/payments/verify" || v.Key == "" || !strings.Contains(v.Body, `"receipt":`+rec) { + t.Fatalf("cererea de verificare: %+v", v) + } +} diff --git a/go/control-negativ.mjs b/go/control-negativ.mjs index 9cb2cae..d2d24b5 100644 --- a/go/control-negativ.mjs +++ b/go/control-negativ.mjs @@ -28,6 +28,10 @@ const PLANTARI = [ ['IdentityVerify trece prezentarea printr-un map', 'return c.jsonReq(ctx, "POST", "/identity/verify", req, true)', 'var m map[string]any\n\t_ = json.Unmarshal(req.Presentation, &m)\n\tb, _ := json.Marshal(m)\n\treq.Presentation = b\n\treturn c.jsonReq(ctx, "POST", "/identity/verify", req, true)', 'TestIdentityVerifyPastreazaOctetii'], ['ComplianceCheck pe ruta de identitate', 'return c.jsonReq(ctx, "POST", "/compliance/check", req, true)', 'return c.jsonReq(ctx, "POST", "/identity/verify", req, true)', 'TestIdentityVerifyPastreazaOctetii'], + // 1.8.0: intrarile de risc trecute printr-un map (suma peste 2^53 devine float64) si chitanta trimisa pe ruta riscului + ['PaymentsRisk trece intrarile printr-un map', 'return c.jsonReq(ctx, "POST", "/payments/risk", req, true)', + 'var m map[string]any\n\t_ = json.Unmarshal(req.Inputs, &m)\n\tb, _ := json.Marshal(m)\n\treq.Inputs = b\n\treturn c.jsonReq(ctx, "POST", "/payments/risk", req, true)', 'TestPaymentsPastreazaOctetii'], + ['PaymentsReceipt pe ruta riscului', 'return c.jsonReq(ctx, "POST", "/payments/receipt", req, true)', 'return c.jsonReq(ctx, "POST", "/payments/risk", req, true)', 'TestPaymentsPastreazaOctetii'], ]; function ruleaza(src) { diff --git a/index.mjs b/index.mjs index 35e951b..69768a7 100644 --- a/index.mjs +++ b/index.mjs @@ -92,12 +92,40 @@ export class AereCloud { */ complianceCheck(request) { return this._req('POST', '/compliance/check', { body: request }); } + /** + * payments, the risk step (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: + * no sanctions list, no identity tier), and the hashes (policyHash, inputsHash, verdictHash) from which anyone reproduces the verdict. + * A refusal is a verdict (HTTP 200, `verdict: 'REFUSE'`), not an error. Needs a gateway that serves POST /v1/payments/risk. + * @param {{policy:object, inputs:object}} request inputs: { chainId, payer, payee, asset, amount, rate:{usdPerUnit, source, at}, history, now } + * @returns {Promise<{verdict:'ALLOW'|'REFUSE', reasons:string[], unmeasured:string[], measured:object, policyHash:string, inputsHash:string, verdictHash:string, verifier:object, reproduce:string}>} + */ + paymentsRisk(request) { return this._req('POST', '/payments/risk', { body: request }); } + + /** + * payments, the receipt (1.8.0): for 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 (an instruction and its AIP-23 settlement envelope) comes back notarized. + * Throws AereCloudError 409 (risk_refused, tx_mismatch, chain_mismatch), 404 (tx_not_found) or 502 (notarization_failed, with the + * receipt built but without a time). The amount in USD is exact at the rate YOU declare in the inputs; it is not a market price. + * @param {{policy:object, inputs:object, txHash:string, purpose?:string}} request + * @returns {Promise<{receipt:{instruction:object, envelope:object}, verdict:object, verifier:object, reproduce:string}>} + */ + paymentsReceipt(request) { return this._req('POST', '/payments/receipt', { body: request }); } + + /** + * payments, verify a receipt (1.8.0): the rules of the published module 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). + * @param {{receipt:{instruction:object, envelope:object}, policy:object, inputs:object}} request + * @returns {Promise<{verdict:'VALID'|'INVALID'|'PARTIAL', reasons:string[], rules:object, envelope:object|null, amountUsd:string, verifier:object, reproduce:string}>} + */ + paymentsVerify(request) { return this._req('POST', '/payments/verify', { body: request }); } + // ---- chain data ---- dataHead() { return this._req('GET', '/data/head'); } validators() { return this._req('GET', '/data/validators'); } anchors(limit = 10) { return this._req('GET', `/data/anchors?limit=${encodeURIComponent(limit)}`); } anchor(height) { return this._req('GET', `/data/anchors/${encodeURIComponent(height)}`); } - // 2026-09-26: the post-quantum finality point (finalized/safe = the parent of the last certified anchor), the tag nodes do not serve yet + // 2026-09-26: 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); the tags nodes did not serve then finality() { return this._req('GET', '/data/finality'); } transfers(address, limit = 50) { return this._req('GET', `/data/transfers?address=${encodeURIComponent(address)}&limit=${encodeURIComponent(limit)}`); @@ -107,7 +135,7 @@ export class AereCloud { /** Notarizeaza un digest de 32 de octeti (0x + 64 hex); noi platim gazul. */ notarize(hash) { return this._req('POST', '/notarize', { body: { hash } }); } proofOf(hash) { return this._req('GET', `/notarize/${encodeURIComponent(hash)}`); } - // 2026-09-17: the proof with its post-quantum finality (first appearance, covering anchor, verdict, verification steps) + // 2026-09-17: the proof under a post-quantum certificate (first appearance, covering anchor, verdict, verification steps) proof(hash) { return this._req('GET', `/proof/${encodeURIComponent(hash)}`); } // quantum readiness scan of a domain's public TLS edge: free without a key; { attest: true } notarizes the report digest @@ -125,8 +153,8 @@ export class AereCloud { return this._req('POST', '/pq/readiness/perimeter', { body }); } - // AIP-21 (2026-09-18): the per-block post-quantum finality record (seals heard by a validator, re-verified, quorum - // verdict). Free, no key. Testnet 28001 first; `network` is reserved for chain 2800 once the method reaches its validators. + // AIP-21 (2026-09-18): the per-block post-quantum seal record (seals heard by a validator, re-verified, quorum + // verdict: evidence of participation, not by itself of the decision, AIP-21 erratum 2026-10-01). Free, no key. Testnet 28001 first; `network` is reserved for chain 2800 once the method reaches its validators. // `client`: 'besu' (default), 'nethermind', or 'both' (two independent implementations, with `agreement` computed by the gateway). pqFinality(block = 'latest', network = 'testnet', client = 'besu') { return this._req('GET', `/pq/finality/${encodeURIComponent(block)}?network=${encodeURIComponent(network)}&client=${encodeURIComponent(client)}`, { auth: false }); diff --git a/java/README.md b/java/README.md index 0929bff..cd031dd 100644 --- a/java/README.md +++ b/java/README.md @@ -30,6 +30,8 @@ String verdict = aere.verify(envelopeJson, 2800); // identity and compliance (1.7.0): the request as JSON text, sent as given; read "verdict" (PARTIAL: some rows not judged) String id = aere.identityVerify("{\"presentation\":" + presentationJson + ",\"audience\":\"https://your.app/verifier\",\"nonce\":\"" + nonce + "\"}"); +// payments (1.8.0): the risk step and the receipt, the request as JSON text; USD at the rate you declare; a REFUSE is a verdict +String risk = aere.paymentsRisk("{\"policy\":" + policyJson + ",\"inputs\":" + inputsJson + "}"); String ok = aere.complianceCheck(requestWithPolicyJson); // verify a webhook delivery (constant-time HMAC) — never trust one without this: @@ -42,10 +44,10 @@ and `.path`. ## Since 1.5.0 (same surface as the JS and Python SDKs 1.5.0) ```java -aere.proof("0x" + sha256Hex); // a proof with its post-quantum finality +aere.proof("0x" + sha256Hex); // a proof under a post-quantum certificate aere.readiness("example.com", false, false); // quantum-readiness scan, free, no key sent aere.readinessPerimeter("example.com", new String[]{"api"}, null, false); // a domain's whole public perimeter -aere.pqFinality("latest", "testnet", "both"); // AIP-21 per-block PQ finality, both clients +aere.pqFinality("latest", "testnet", "both"); // AIP-21 per-block PQ seal record, both clients AereCloud.AuditExport day = aere.auditExport("2026-09-24", "jsonl"); // day.bytes are the exact bytes of the day file; day.verified is true only if THEIR sha256 equals the stated digest diff --git a/java/control-negativ.mjs b/java/control-negativ.mjs index c88831b..4c5a248 100644 --- a/java/control-negativ.mjs +++ b/java/control-negativ.mjs @@ -30,6 +30,9 @@ const PLANTARI = [ // 1.7.0: conformitatea trimisa pe ruta identitatii; identitatea fara cheie ['complianceCheck pe ruta identitatii', 'return req("POST", "/compliance/check", requestJson, true);', 'return req("POST", "/identity/verify", requestJson, true);', '1.7.0'], ['identityVerify fara cheie', 'return req("POST", "/identity/verify", requestJson, true);', 'return req("POST", "/identity/verify", requestJson, false);', '1.7.0'], + // 1.8.0: chitanta pe ruta riscului; verificarea fara cheie + ['paymentsReceipt pe ruta riscului', 'return req("POST", "/payments/receipt", requestJson, true);', 'return req("POST", "/payments/risk", requestJson, true);', '1.8.0'], + ['paymentsVerify fara cheie', 'return req("POST", "/payments/verify", requestJson, true);', 'return req("POST", "/payments/verify", requestJson, false);', '1.8.0'], ]; // 1.6.1: poarta Android = clasa compileaza cu `javac --release 8`. Un API de dupa Java 8 trece probele pe JDK 21 si trebuie prins // numai de poarta; plantarea NU e o proba, e o compilare care TREBUIE sa cada. diff --git a/java/src/main/java/network/aere/cloud/AereCloud.java b/java/src/main/java/network/aere/cloud/AereCloud.java index ccf5cf8..f5508ad 100644 --- a/java/src/main/java/network/aere/cloud/AereCloud.java +++ b/java/src/main/java/network/aere/cloud/AereCloud.java @@ -33,7 +33,8 @@ public final class AereCloud { // urmate (ca inainte: HttpClient nu le urma implicit); URLEncoder cu numele setului de caractere (varianta cu Charset e // Java 10+). Aceeasi suprafata publica. // 1.7.0 (2026-09-30): identityVerify() si complianceCheck() (POST /v1/identity/verify, /v1/compliance/check) - public static final String VERSIUNE = "1.7.0"; + // 1.8.0 (2026-10-03): paymentsRisk(), paymentsReceipt(), paymentsVerify() (POST /v1/payments/risk, /receipt, /verify) + public static final String VERSIUNE = "1.8.0"; private static final String BAZA_IMPLICITA = "https://cloud.aere.network/v1"; private static final int TIMP_MS = 30000; @@ -168,13 +169,19 @@ public final class AereCloud { public String identityVerify(String requestJson) { return req("POST", "/identity/verify", requestJson, true); } /** compliance check (1.7.0): {presentation, policy, audience, nonce, statusLists?, revocations?, record?} -> COMPLIANT / NOT_COMPLIANT with the reasons and, with record, an AIP-23 record without personal data. */ public String complianceCheck(String requestJson) { return req("POST", "/compliance/check", requestJson, true); } + /** payments, the risk step (1.8.0): {policy, inputs} -> 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; judged by the published module payments/risc.mjs. A refusal is a verdict (HTTP 200), not an error. */ + public String paymentsRisk(String requestJson) { return req("POST", "/payments/risk", requestJson, true); } + /** payments, the receipt (1.8.0): {policy, inputs, txHash, purpose?} for a payment ALREADY on the chain the gateway serves -> the receipt (instruction + AIP-23 settlement envelope), notarized. AereCloudException 409 (risk_refused, tx_mismatch, chain_mismatch), 404 (tx_not_found), 502 (notarization_failed). */ + public String paymentsReceipt(String requestJson) { return req("POST", "/payments/receipt", requestJson, true); } + /** payments, verify a receipt (1.8.0): {receipt:{instruction, envelope}, policy, inputs} -> VALID / INVALID / PARTIAL. */ + public String paymentsVerify(String requestJson) { return req("POST", "/payments/verify", requestJson, true); } // ---- chain data ---- public String dataHead() { return req("GET", "/data/head", null, true); } public String validators() { return req("GET", "/data/validators", null, true); } public String anchors(int limit) { return req("GET", "/data/anchors?limit=" + limit, null, true); } public String anchor(long height) { return req("GET", "/data/anchors/" + height, null, true); } - /** 2026-09-26: the post-quantum finality point (finalized/safe = the parent of the last certified anchor). */ + /** 2026-09-26: 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). */ public String finality() { return req("GET", "/data/finality", null, true); } public String transfers(String address, int limit) { return req("GET", "/data/transfers?address=" + address + "&limit=" + limit, null, true); diff --git a/java/src/test/java/network/aere/cloud/TestAereCloud.java b/java/src/test/java/network/aere/cloud/TestAereCloud.java index b2765d6..2e19489 100644 --- a/java/src/test/java/network/aere/cloud/TestAereCloud.java +++ b/java/src/test/java/network/aere/cloud/TestAereCloud.java @@ -127,6 +127,16 @@ public final class TestAereCloud { lc.complianceCheck("{\"policy\":{}}"); cere(ultima.get()[1].equals("/v1/compliance/check") && ultima.get()[2] != null && ultima.get()[3].equals("{\"policy\":{}}"), String.join(" | ", ultima.get())); }); + proba("1.8.0: paymentsRisk, paymentsReceipt si paymentsVerify trimit cererea neschimbata pe rutele lor, cu cheie", () -> { + String cerere = "{\"policy\":{\"id\":\"p\"},\"inputs\":{\"amount\":\"12345678901234567891\"}}"; + lc.paymentsRisk(cerere); + String[] v = ultima.get(); + cere(v[0].equals("POST") && v[1].equals("/v1/payments/risk") && v[2] != null && v[3].equals(cerere), String.join(" | ", v)); + lc.paymentsReceipt("{\"txHash\":\"0x11\"}"); + cere(ultima.get()[1].equals("/v1/payments/receipt") && ultima.get()[2] != null && ultima.get()[3].equals("{\"txHash\":\"0x11\"}"), String.join(" | ", ultima.get())); + lc.paymentsVerify("{\"receipt\":{}}"); + cere(ultima.get()[1].equals("/v1/payments/verify") && ultima.get()[2] != null && ultima.get()[3].equals("{\"receipt\":{}}"), String.join(" | ", ultima.get())); + }); proba("1.5.0: auditExport: jsonl verified=true pe octetii care dau digestul, FALSE pe alti octeti, null la cef; un 400 se ridica", () -> { raspunsuri.put("/v1/account/audit", new Object[]{200, zi, new String[]{"x-aere-digest", dZi, "x-aere-day", "2026-05-01", "x-aere-entries", "1"}}); AereCloud.AuditExport e = lc.auditExport("2026-05-01", "jsonl"); diff --git a/package.json b/package.json index d435cb7..1850e44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aere/cloud", - "version": "1.7.0", + "version": "1.8.0", "description": "Official client for the Aere Cloud API: keyed RPC, post-quantum verification, chain data, notarization, webhooks and gas sponsorship on Aere Network (chain 2800).", "type": "module", "main": "index.mjs", diff --git a/python/README.md b/python/README.md index 6837e86..7c1de54 100644 --- a/python/README.md +++ b/python/README.md @@ -43,6 +43,10 @@ receipt = aere.notarize("0x" + sha256_hex) r = aere.verify(envelope, chain=2800) # {"verdict", "levels", "verifier", "reproduce"} # identity and compliance (1.7.0): an AERE Identity presentation judged by the published command line; read "verdict" idv = aere.identity_verify({"presentation": presentation, "audience": "https://your.app/verifier", "nonce": nonce, "trustedIssuers": [issuer_id]}) +# payments (1.8.0): the risk step and the receipt, judged by the published module payments/; USD at the rate you declare; a REFUSE is a verdict +risk = aere.payments_risk({"policy": policy, "inputs": inputs}) +receipt = aere.payments_receipt({"policy": policy, "inputs": inputs, "txHash": tx_hash})["receipt"] +check = aere.payments_verify({"receipt": receipt, "policy": policy, "inputs": inputs}) cc = aere.compliance_check({"presentation": presentation, "policy": policy, "audience": "https://your.app/verifier", "nonce": nonce, "record": True}) proof = aere.proof_of("0x" + sha256_hex) @@ -54,7 +58,7 @@ acct = aere.sponsor_create_pq_account("0x09" + falcon_pk_hex, salt=0) Every method raises `AereCloudError` on a non-2xx response, carrying `.status` and the server's JSON `.body` so you can act on the exact error. -## Proof with post-quantum finality, quantum readiness (1.2.0, 2026-09-17) +## Proof under a post-quantum certificate, quantum readiness (1.2.0, 2026-09-17) ```python full = aere.proof(digest_hex) # first appearance, covering post-quantum anchor, finality, verification steps diff --git a/python/aere_cloud/__init__.py b/python/aere_cloud/__init__.py index 3f0d8df..205eb88 100644 --- a/python/aere_cloud/__init__.py +++ b/python/aere_cloud/__init__.py @@ -14,7 +14,7 @@ import urllib.request import urllib.error from typing import Any, Optional -__version__ = "1.7.0" +__version__ = "1.8.0" __all__ = ["AereCloud", "AereCloudError", "verify_webhook"] _BAZA = "https://cloud.aere.network/v1" @@ -124,6 +124,25 @@ class AereCloud: request: {presentation, policy, audience, nonce, statusLists?, revocations?, record?}.""" return self._req("POST", "/compliance/check", request) + def payments_risk(self, request: dict) -> dict: + """payments, the risk step (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. + request: {policy, inputs}; inputs: {chainId, payer, payee, asset, amount, rate:{usdPerUnit, source, at}, history, now}.""" + return self._req("POST", "/payments/risk", request) + + def payments_receipt(self, request: dict) -> dict: + """payments, the receipt (1.8.0): for 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. Raises AereCloudError 409 (risk_refused, tx_mismatch, chain_mismatch), 404 (tx_not_found), 502 (notarization_failed). + request: {policy, inputs, txHash, purpose?}. The USD amount is exact at the rate YOU declare; it is not a market price.""" + return self._req("POST", "/payments/receipt", request) + + def payments_verify(self, request: dict) -> dict: + """payments, verify a receipt (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. request: {receipt:{instruction, envelope}, policy, inputs}.""" + return self._req("POST", "/payments/verify", request) + # ---- chain data ---- def data_head(self) -> dict: return self._req("GET", "/data/head") @@ -138,7 +157,7 @@ class AereCloud: return self._req("GET", f"/data/anchors/{int(height)}") def finality(self) -> dict: - """2026-09-26: the post-quantum finality point (finalized/safe = the parent of the last certified anchor).""" + """2026-09-26: 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).""" return self._req("GET", "/data/finality") def transfers(self, address: str, limit: int = 50) -> dict: diff --git a/python/pyproject.toml b/python/pyproject.toml index 3f88f6e..08ca1df 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "aere-cloud" -version = "1.7.0" +version = "1.8.0" description = "Official Python client for the Aere Cloud API (Aere Network, chain 2800): RPC, post-quantum verification, chain data, notarization, webhooks, gas sponsorship." readme = "README.md" requires-python = ">=3.9" diff --git a/python/test_aere_cloud.py b/python/test_aere_cloud.py index a7fb713..af5f133 100644 --- a/python/test_aere_cloud.py +++ b/python/test_aere_cloud.py @@ -103,6 +103,22 @@ def t_identitate(): assert c.compliance_check(cc)["verdict"] == "COMPLIANT" assert tr.cereri[1].full_url.endswith("/compliance/check") and json.loads(tr.cereri[1].data.decode()) == cc +def t_plati(): + tr = Transport([(200, {"verdict": "REFUSE", "reasons": ["above the per-payment limit"]}), (201, {"receipt": {"instruction": {}, "envelope": {}}}), (200, {"verdict": "PARTIAL"})]) + c = AereCloud("k", opener=tr) + cerere = {"policy": {"v": 1, "kind": "aere-payment-risk-policy", "id": "p"}, "inputs": {"chainId": 28001, "asset": "native", "amount": "400000000000000000", "history": [], "now": 1}} + assert c.payments_risk(cerere)["verdict"] == "REFUSE", "un REFUSE e un verdict (200), nu o eroare" + assert tr.cereri[0].full_url.endswith("/payments/risk") and tr.cereri[0].get_method() == "POST" + assert tr.cereri[0].get_header("X-api-key") == "k", "payments_risk cere cheia" + assert json.loads(tr.cereri[0].data.decode()) == cerere + cc = {**cerere, "txHash": "0x" + "11" * 32, "purpose": "order 7"} + assert "receipt" in c.payments_receipt(cc) + assert tr.cereri[1].full_url.endswith("/payments/receipt") and json.loads(tr.cereri[1].data.decode()) == cc + cv = {"receipt": {"instruction": {"a": 1}, "envelope": {"b": 2}}, "policy": cerere["policy"], "inputs": cerere["inputs"]} + assert c.payments_verify(cv)["verdict"] == "PARTIAL" + assert tr.cereri[2].full_url.endswith("/payments/verify") and json.loads(tr.cereri[2].data.decode()) == cv + assert tr.cereri[2].get_header("X-api-key") == "k", "payments_verify cere cheia" + def t_pq_sponsor(): tr = Transport([(200, {"txHash": "0xab", "account": "0xcd", "alreadyDeployed": False})]) c = AereCloud("k", opener=tr) @@ -127,6 +143,7 @@ proba("un status de eroare devine AereCloudError cu corpul serverului", t_eroare proba("rutele de date construiesc corect query-ul", t_query) proba("verify (Trust API, 1.6.0): ruta, ?chain, cheia si plicul neschimbat", t_verify) proba("identity_verify si compliance_check (1.7.0): rutele, cheia si cererea neschimbata", t_identitate) +proba("payments_risk, payments_receipt si payments_verify (1.8.0): rutele, cheia si cererea neschimbata", t_plati) proba("sponsor_create_pq_account: corp si cale corecte", t_pq_sponsor) proba("verify_webhook: buna trece; stricata, prelungita, corp atins pica", t_webhook) diff --git a/test/control-negativ-js-python.mjs b/test/control-negativ-js-python.mjs index 76ed638..d7f03bb 100644 --- a/test/control-negativ-js-python.mjs +++ b/test/control-negativ-js-python.mjs @@ -16,6 +16,11 @@ const PLANTARI = [ ['JS: identityVerify trimite o cerere schimbata', 'index.mjs', "identityVerify(request) { return this._req('POST', '/identity/verify', { body: request }); }", "identityVerify(request) { return this._req('POST', '/identity/verify', { body: { ...request, maxAgeSeconds: 3600 } }); }", 'js', '1.7.0: identityVerify si complianceCheck'], ['Python: compliance_check pe ruta identitatii', 'python/aere_cloud/__init__.py', 'return self._req("POST", "/compliance/check", request)', 'return self._req("POST", "/identity/verify", request)', 'py', 'identity_verify si compliance_check'], ['Python: identity_verify trimite o cerere schimbata', 'python/aere_cloud/__init__.py', 'return self._req("POST", "/identity/verify", request)', 'return self._req("POST", "/identity/verify", {**request, "maxAgeSeconds": 3600})', 'py', 'identity_verify si compliance_check'], + // 1.8.0: chitanta trimisa pe ruta riscului; verificarea cu cererea schimbata + ['JS: paymentsReceipt pe ruta riscului', 'index.mjs', "paymentsReceipt(request) { return this._req('POST', '/payments/receipt', { body: request }); }", "paymentsReceipt(request) { return this._req('POST', '/payments/risk', { body: request }); }", 'js', 'paymentsRisk, paymentsReceipt si paymentsVerify'], + ['JS: paymentsVerify trimite o cerere schimbata', 'index.mjs', "paymentsVerify(request) { return this._req('POST', '/payments/verify', { body: request }); }", "paymentsVerify(request) { return this._req('POST', '/payments/verify', { body: { ...request, policy: {} } }); }", 'js', 'paymentsRisk, paymentsReceipt si paymentsVerify'], + ['Python: payments_receipt pe ruta riscului', 'python/aere_cloud/__init__.py', 'return self._req("POST", "/payments/receipt", request)', 'return self._req("POST", "/payments/risk", request)', 'py', 'payments_risk, payments_receipt si payments_verify'], + ['Python: payments_verify trimite o cerere schimbata', 'python/aere_cloud/__init__.py', 'return self._req("POST", "/payments/verify", request)', 'return self._req("POST", "/payments/verify", {**request, "policy": {}})', 'py', 'payments_risk, payments_receipt si payments_verify'], ]; function copie() { const d = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-sdk-cn-')); diff --git a/test/sdk.test.mjs b/test/sdk.test.mjs index e231063..c3372ac 100644 --- a/test/sdk.test.mjs +++ b/test/sdk.test.mjs @@ -73,6 +73,24 @@ await test('1.7.0: identityVerify si complianceCheck trimit cererea neschimbata assert.deepEqual(rute['POST /compliance/check']._vazut.body, cc); }); +await test('1.8.0: paymentsRisk, paymentsReceipt si paymentsVerify trimit cererea neschimbata pe rutele lor, cu cheie; un 409 se ridica cu corpul lui', async () => { + const rute = { 'POST /payments/risk': { status: 200, corp: { verdict: 'REFUSE', reasons: ['above the per-payment limit'] } }, 'POST /payments/receipt': { status: 201, corp: { receipt: { instruction: {}, envelope: {} } } }, 'POST /payments/verify': { status: 200, corp: { verdict: 'PARTIAL' } } }; + const c = new AereCloud({ apiKey: 'ak2800.0xAAA.AERE-SINTETIC-s', baseUrl: BAZA_TEST, fetch: fetchFals(rute) }); + const cerere = { policy: { v: 1, kind: 'aere-payment-risk-policy', id: 'p' }, inputs: { chainId: 28001, payer: '0x' + 'aa'.repeat(20), payee: '0x' + 'bb'.repeat(20), asset: 'native', amount: '400000000000000000', rate: { usdPerUnit: '0.5', source: 'declared', at: '2026-10-03T00:00:00Z' }, history: [], now: 1 } }; + assert.equal((await c.paymentsRisk(cerere)).verdict, 'REFUSE', 'un REFUSE e un verdict (200), nu o eroare'); + assert.deepEqual(rute['POST /payments/risk']._vazut.body, cerere); + assert.ok(rute['POST /payments/risk']._vazut.headers['x-api-key'], 'paymentsRisk cere cheia'); + const cc = { ...cerere, txHash: '0x' + '11'.repeat(32), purpose: 'order 7' }; + assert.ok((await c.paymentsReceipt(cc)).receipt, 'chitanta intoarsa'); + assert.deepEqual(rute['POST /payments/receipt']._vazut.body, cc); + const cv = { receipt: { instruction: { a: 1 }, envelope: { b: 2 } }, policy: cerere.policy, inputs: cerere.inputs }; + assert.equal((await c.paymentsVerify(cv)).verdict, 'PARTIAL'); + assert.deepEqual(rute['POST /payments/verify']._vazut.body, cv); + assert.ok(rute['POST /payments/verify']._vazut.headers['x-api-key'], 'paymentsVerify cere cheia'); + const c2 = new AereCloud({ apiKey: 'ak2800.0xAAA.AERE-SINTETIC-s', baseUrl: BAZA_TEST, fetch: fetchFals({ 'POST /payments/receipt': { status: 409, corp: { error: 'risk_refused', verdict: 'REFUSE' } } }) }); + await assert.rejects(() => c2.paymentsReceipt(cc), (e) => e instanceof AereCloudError && e.status === 409 && e.body.error === 'risk_refused'); +}); + await test('rutele de date construiesc corect query-ul', async () => { const rute = { 'GET /data/anchors?limit=3': { status: 200, corp: { anchors: [] } },