W0: add regulatory profile baseline to checkout and ID flows
Some checks are pending
check / secretapi (push) Waiting to run
Some checks are pending
check / secretapi (push) Waiting to run
This commit is contained in:
parent
c80b1db18b
commit
a3a53992bd
@ -153,6 +153,7 @@ The envelope is pre-execution pricing metadata and is authoritative for checkout
|
||||
- `SECRET_API_QUOTE_TTL_SECONDS` (default `900`)
|
||||
- `SECRET_API_WALLET_SESSION_TTL_SECONDS` (default `2592000`)
|
||||
- `SECRET_API_REQUIRE_WALLET_SESSION` (default `true`; set `false` only for controlled local harness/debug usage)
|
||||
- `SECRET_API_REGULATORY_PROFILE_ID` (`us_general_2026` default, `eu_ai_act_2026_baseline` supported)
|
||||
- `SECRET_API_DOMAIN_NAME`
|
||||
- `SECRET_API_VERIFYING_CONTRACT`
|
||||
- `SECRET_API_MEMBERSHIP_CONTRACT`
|
||||
|
||||
@ -465,22 +465,23 @@ func (a *app) handleMembershipQuote(w http.ResponseWriter, r *http.Request) {
|
||||
"value": quote.ValueHex,
|
||||
}
|
||||
writeJSON(w, http.StatusOK, membershipQuoteResponse{
|
||||
QuoteID: quote.QuoteID,
|
||||
ChainID: quote.ChainID,
|
||||
Currency: quote.Currency,
|
||||
AmountAtomic: quote.AmountAtomic,
|
||||
Decimals: quote.Decimals,
|
||||
CostEnvelope: newQuoteCostEnvelope(quote.Currency, quote.Decimals, quote.AmountAtomic),
|
||||
Deadline: quote.ExpiresAt.Format(time.RFC3339Nano),
|
||||
ContractAddress: quote.ContractAddress,
|
||||
Method: quote.Method,
|
||||
Calldata: quote.Calldata,
|
||||
Value: quote.ValueHex,
|
||||
OwnerWallet: address,
|
||||
PayerWallet: payerAddress,
|
||||
SponsorshipMode: sponsorshipMode,
|
||||
SponsorOrgRoot: strings.TrimSpace(req.SponsorOrgRoot),
|
||||
Tx: tx,
|
||||
QuoteID: quote.QuoteID,
|
||||
ChainID: quote.ChainID,
|
||||
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
|
||||
Currency: quote.Currency,
|
||||
AmountAtomic: quote.AmountAtomic,
|
||||
Decimals: quote.Decimals,
|
||||
CostEnvelope: newQuoteCostEnvelope(quote.Currency, quote.Decimals, quote.AmountAtomic),
|
||||
Deadline: quote.ExpiresAt.Format(time.RFC3339Nano),
|
||||
ContractAddress: quote.ContractAddress,
|
||||
Method: quote.Method,
|
||||
Calldata: quote.Calldata,
|
||||
Value: quote.ValueHex,
|
||||
OwnerWallet: address,
|
||||
PayerWallet: payerAddress,
|
||||
SponsorshipMode: sponsorshipMode,
|
||||
SponsorOrgRoot: strings.TrimSpace(req.SponsorOrgRoot),
|
||||
Tx: tx,
|
||||
})
|
||||
}
|
||||
|
||||
@ -602,6 +603,7 @@ func (a *app) handleMembershipConfirm(w http.ResponseWriter, r *http.Request) {
|
||||
Status: "membership_active",
|
||||
DesignationCode: rec.Code,
|
||||
DisplayToken: rec.DisplayToken,
|
||||
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
|
||||
TxHash: strings.ToLower(req.TxHash),
|
||||
ActivatedAt: now.Format(time.RFC3339Nano),
|
||||
IdentityAssurance: rec.IdentityAssurance,
|
||||
@ -639,7 +641,7 @@ func (a *app) handleMembershipStatus(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, errNotFound) {
|
||||
writeJSON(w, http.StatusOK, membershipStatusResponse{Status: "none"})
|
||||
writeJSON(w, http.StatusOK, membershipStatusResponse{Status: "none", RegulatoryProfileID: a.cfg.RegulatoryProfileID})
|
||||
return
|
||||
}
|
||||
writeError(w, http.StatusInternalServerError, "failed to resolve membership status")
|
||||
@ -654,6 +656,7 @@ func (a *app) handleMembershipStatus(w http.ResponseWriter, r *http.Request) {
|
||||
Status: status,
|
||||
Wallet: rec.Address,
|
||||
DesignationCode: rec.Code,
|
||||
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
|
||||
IdentityAssurance: normalizeAssuranceLevel(rec.IdentityAssurance),
|
||||
IdentityAttestedBy: strings.TrimSpace(rec.IdentityAttestedBy),
|
||||
IdentityAttestationID: strings.TrimSpace(rec.IdentityAttestationID),
|
||||
|
||||
@ -89,6 +89,7 @@ func TestMembershipDistinctPayerProof(t *testing.T) {
|
||||
t.Fatalf("tx.from mismatch: %+v", quote.Tx)
|
||||
}
|
||||
assertQuoteCostEnvelope(t, quote.CostEnvelope, quote.Currency, quote.Decimals, quote.AmountAtomic)
|
||||
assertRegulatoryProfileID(t, quote.RegulatoryProfileID, cfg.RegulatoryProfileID)
|
||||
}
|
||||
|
||||
func TestMembershipCompanySponsorWithoutOwnerProof(t *testing.T) {
|
||||
@ -1095,6 +1096,7 @@ func TestMarketplaceCheckoutBundlesMembershipAndMintsEntitlement(t *testing.T) {
|
||||
t.Fatalf("expected default merchant id, got %+v", quote)
|
||||
}
|
||||
assertQuoteCostEnvelope(t, quote.CostEnvelope, quote.Currency, quote.Decimals, quote.TotalAmountAtomic)
|
||||
assertRegulatoryProfileID(t, quote.RegulatoryProfileID, cfg.RegulatoryProfileID)
|
||||
|
||||
confirm := postJSONExpect[marketplaceCheckoutConfirmResponse](t, a, "/marketplace/checkout/confirm", marketplaceCheckoutConfirmRequest{
|
||||
QuoteID: quote.QuoteID,
|
||||
@ -1109,11 +1111,13 @@ func TestMarketplaceCheckoutBundlesMembershipAndMintsEntitlement(t *testing.T) {
|
||||
if confirm.Status != "entitlement_active" || confirm.EntitlementID == "" {
|
||||
t.Fatalf("unexpected confirm response: %+v", confirm)
|
||||
}
|
||||
assertRegulatoryProfileID(t, confirm.RegulatoryProfileID, cfg.RegulatoryProfileID)
|
||||
|
||||
status := getJSONExpect[membershipStatusResponse](t, a, "/secret/membership/status?wallet="+ownerAddr, http.StatusOK)
|
||||
if status.Status != "active" {
|
||||
t.Fatalf("expected active membership after bundled checkout, got %+v", status)
|
||||
}
|
||||
assertRegulatoryProfileID(t, status.RegulatoryProfileID, cfg.RegulatoryProfileID)
|
||||
|
||||
entitlements := getJSONExpect[marketplaceEntitlementsResponse](t, a, "/marketplace/entitlements?wallet="+ownerAddr, http.StatusOK)
|
||||
if len(entitlements.Entitlements) == 0 {
|
||||
@ -2070,6 +2074,13 @@ func assertQuoteCostEnvelope(t *testing.T, got quoteCostEnvelope, wantCurrency s
|
||||
}
|
||||
}
|
||||
|
||||
func assertRegulatoryProfileID(t *testing.T, got string, want string) {
|
||||
t.Helper()
|
||||
if normalizeRegulatoryProfileID(got) != normalizeRegulatoryProfileID(want) {
|
||||
t.Fatalf("regulatory profile mismatch: got=%s want=%s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func signTypedData(t *testing.T, key *ecdsa.PrivateKey, typedData apitypes.TypedData) string {
|
||||
t.Helper()
|
||||
hash, _, err := apitypes.TypedDataAndHash(typedData)
|
||||
|
||||
@ -14,6 +14,7 @@ type Config struct {
|
||||
DBPath string
|
||||
AllowedOrigin string
|
||||
DeploymentClass string
|
||||
RegulatoryProfileID string
|
||||
MemberPollIntervalSec int
|
||||
IntentTTL time.Duration
|
||||
QuoteTTL time.Duration
|
||||
@ -47,6 +48,7 @@ func loadConfig() Config {
|
||||
DBPath: env("SECRET_API_DB_PATH", "./secret.db"),
|
||||
AllowedOrigin: env("SECRET_API_ALLOWED_ORIGIN", "https://edut.ai"),
|
||||
DeploymentClass: normalizeDeploymentClass(env("SECRET_API_DEPLOYMENT_CLASS", "development")),
|
||||
RegulatoryProfileID: normalizeRegulatoryProfileID(env("SECRET_API_REGULATORY_PROFILE_ID", defaultRegulatoryProfileID)),
|
||||
MemberPollIntervalSec: envInt("SECRET_API_MEMBER_POLL_INTERVAL_SECONDS", 30),
|
||||
IntentTTL: time.Duration(envInt("SECRET_API_INTENT_TTL_SECONDS", 900)) * time.Second,
|
||||
QuoteTTL: time.Duration(envInt("SECRET_API_QUOTE_TTL_SECONDS", 900)) * time.Second,
|
||||
@ -79,6 +81,9 @@ func (c Config) Validate() error {
|
||||
if c.ChainID <= 0 {
|
||||
return fmt.Errorf("SECRET_API_CHAIN_ID must be positive")
|
||||
}
|
||||
if !isKnownRegulatoryProfileID(c.RegulatoryProfileID) {
|
||||
return fmt.Errorf("SECRET_API_REGULATORY_PROFILE_ID must be %s or %s", regulatoryProfileUSGeneral2026, regulatoryProfileEUAIBaseline2026)
|
||||
}
|
||||
currency := strings.ToUpper(strings.TrimSpace(c.MintCurrency))
|
||||
switch currency {
|
||||
case "USDC":
|
||||
|
||||
@ -115,3 +115,21 @@ func TestConfigValidateRejectsProductionWhenStrictVerificationDisabled(t *testin
|
||||
t.Fatalf("expected production config validation failure when strict onchain verification disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigValidateAllowsKnownRegulatoryProfiles(t *testing.T) {
|
||||
cfg := loadConfigIsolated(t)
|
||||
for _, profileID := range []string{regulatoryProfileUSGeneral2026, regulatoryProfileEUAIBaseline2026} {
|
||||
cfg.RegulatoryProfileID = profileID
|
||||
if err := cfg.Validate(); err != nil {
|
||||
t.Fatalf("expected regulatory profile %s to validate, got %v", profileID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigValidateRejectsUnknownRegulatoryProfile(t *testing.T) {
|
||||
cfg := loadConfigIsolated(t)
|
||||
cfg.RegulatoryProfileID = "unknown_profile"
|
||||
if err := cfg.Validate(); err == nil {
|
||||
t.Fatalf("expected regulatory profile validation failure")
|
||||
}
|
||||
}
|
||||
|
||||
@ -489,6 +489,7 @@ func (a *app) handleMarketplaceCheckoutQuote(w http.ResponseWriter, r *http.Requ
|
||||
writeJSON(w, http.StatusOK, marketplaceCheckoutQuoteResponse{
|
||||
QuoteID: quote.QuoteID,
|
||||
MerchantID: quote.MerchantID,
|
||||
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
|
||||
Wallet: quote.Wallet,
|
||||
PayerWallet: quote.PayerWallet,
|
||||
OfferID: quote.OfferID,
|
||||
@ -606,19 +607,20 @@ func (a *app) handleMarketplaceCheckoutConfirm(w http.ResponseWriter, r *http.Re
|
||||
if quote.ConfirmedAt != nil {
|
||||
if existing, existingErr := a.store.getMarketplaceEntitlementByQuote(r.Context(), quote.QuoteID); existingErr == nil {
|
||||
writeJSON(w, http.StatusOK, marketplaceCheckoutConfirmResponse{
|
||||
Status: "entitlement_active",
|
||||
EntitlementID: existing.EntitlementID,
|
||||
MerchantID: existing.MerchantID,
|
||||
OfferID: existing.OfferID,
|
||||
OrgRootID: existing.OrgRootID,
|
||||
PrincipalID: existing.PrincipalID,
|
||||
PrincipalRole: existing.PrincipalRole,
|
||||
Wallet: existing.Wallet,
|
||||
TxHash: existing.TxHash,
|
||||
PolicyHash: existing.PolicyHash,
|
||||
ActivatedAt: existing.IssuedAt.Format(time.RFC3339Nano),
|
||||
AccessClass: existing.AccessClass,
|
||||
AvailabilityState: existing.AvailabilityState,
|
||||
Status: "entitlement_active",
|
||||
EntitlementID: existing.EntitlementID,
|
||||
MerchantID: existing.MerchantID,
|
||||
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
|
||||
OfferID: existing.OfferID,
|
||||
OrgRootID: existing.OrgRootID,
|
||||
PrincipalID: existing.PrincipalID,
|
||||
PrincipalRole: existing.PrincipalRole,
|
||||
Wallet: existing.Wallet,
|
||||
TxHash: existing.TxHash,
|
||||
PolicyHash: existing.PolicyHash,
|
||||
ActivatedAt: existing.IssuedAt.Format(time.RFC3339Nano),
|
||||
AccessClass: existing.AccessClass,
|
||||
AvailabilityState: existing.AvailabilityState,
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -709,19 +711,20 @@ func (a *app) handleMarketplaceCheckoutConfirm(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, marketplaceCheckoutConfirmResponse{
|
||||
Status: "entitlement_active",
|
||||
EntitlementID: ent.EntitlementID,
|
||||
MerchantID: ent.MerchantID,
|
||||
OfferID: ent.OfferID,
|
||||
OrgRootID: ent.OrgRootID,
|
||||
PrincipalID: ent.PrincipalID,
|
||||
PrincipalRole: ent.PrincipalRole,
|
||||
Wallet: ent.Wallet,
|
||||
TxHash: ent.TxHash,
|
||||
PolicyHash: ent.PolicyHash,
|
||||
ActivatedAt: ent.IssuedAt.Format(time.RFC3339Nano),
|
||||
AccessClass: ent.AccessClass,
|
||||
AvailabilityState: ent.AvailabilityState,
|
||||
Status: "entitlement_active",
|
||||
EntitlementID: ent.EntitlementID,
|
||||
MerchantID: ent.MerchantID,
|
||||
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
|
||||
OfferID: ent.OfferID,
|
||||
OrgRootID: ent.OrgRootID,
|
||||
PrincipalID: ent.PrincipalID,
|
||||
PrincipalRole: ent.PrincipalRole,
|
||||
Wallet: ent.Wallet,
|
||||
TxHash: ent.TxHash,
|
||||
PolicyHash: ent.PolicyHash,
|
||||
ActivatedAt: ent.IssuedAt.Format(time.RFC3339Nano),
|
||||
AccessClass: ent.AccessClass,
|
||||
AvailabilityState: ent.AvailabilityState,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -67,6 +67,7 @@ type marketplaceQuoteLineItem struct {
|
||||
type marketplaceCheckoutQuoteResponse struct {
|
||||
QuoteID string `json:"quote_id"`
|
||||
MerchantID string `json:"merchant_id,omitempty"`
|
||||
RegulatoryProfileID string `json:"regulatory_profile_id"`
|
||||
Wallet string `json:"wallet"`
|
||||
PayerWallet string `json:"payer_wallet,omitempty"`
|
||||
OfferID string `json:"offer_id"`
|
||||
@ -104,19 +105,20 @@ type marketplaceCheckoutConfirmRequest struct {
|
||||
}
|
||||
|
||||
type marketplaceCheckoutConfirmResponse struct {
|
||||
Status string `json:"status"`
|
||||
EntitlementID string `json:"entitlement_id"`
|
||||
MerchantID string `json:"merchant_id,omitempty"`
|
||||
OfferID string `json:"offer_id"`
|
||||
OrgRootID string `json:"org_root_id,omitempty"`
|
||||
PrincipalID string `json:"principal_id,omitempty"`
|
||||
PrincipalRole string `json:"principal_role,omitempty"`
|
||||
Wallet string `json:"wallet"`
|
||||
TxHash string `json:"tx_hash"`
|
||||
PolicyHash string `json:"policy_hash"`
|
||||
ActivatedAt string `json:"activated_at"`
|
||||
AccessClass string `json:"access_class"`
|
||||
AvailabilityState string `json:"availability_state"`
|
||||
Status string `json:"status"`
|
||||
EntitlementID string `json:"entitlement_id"`
|
||||
MerchantID string `json:"merchant_id,omitempty"`
|
||||
RegulatoryProfileID string `json:"regulatory_profile_id"`
|
||||
OfferID string `json:"offer_id"`
|
||||
OrgRootID string `json:"org_root_id,omitempty"`
|
||||
PrincipalID string `json:"principal_id,omitempty"`
|
||||
PrincipalRole string `json:"principal_role,omitempty"`
|
||||
Wallet string `json:"wallet"`
|
||||
TxHash string `json:"tx_hash"`
|
||||
PolicyHash string `json:"policy_hash"`
|
||||
ActivatedAt string `json:"activated_at"`
|
||||
AccessClass string `json:"access_class"`
|
||||
AvailabilityState string `json:"availability_state"`
|
||||
}
|
||||
|
||||
type marketplaceEntitlement struct {
|
||||
|
||||
@ -68,22 +68,23 @@ type membershipQuoteRequest struct {
|
||||
}
|
||||
|
||||
type membershipQuoteResponse struct {
|
||||
QuoteID string `json:"quote_id"`
|
||||
ChainID int64 `json:"chain_id"`
|
||||
Currency string `json:"currency"`
|
||||
AmountAtomic string `json:"amount_atomic"`
|
||||
Decimals int `json:"decimals"`
|
||||
CostEnvelope quoteCostEnvelope `json:"cost_envelope"`
|
||||
Deadline string `json:"deadline"`
|
||||
ContractAddress string `json:"contract_address"`
|
||||
Method string `json:"method"`
|
||||
Calldata string `json:"calldata"`
|
||||
Value string `json:"value"`
|
||||
OwnerWallet string `json:"owner_wallet,omitempty"`
|
||||
PayerWallet string `json:"payer_wallet,omitempty"`
|
||||
SponsorshipMode string `json:"sponsorship_mode,omitempty"`
|
||||
SponsorOrgRoot string `json:"sponsor_org_root_id,omitempty"`
|
||||
Tx map[string]any `json:"tx"`
|
||||
QuoteID string `json:"quote_id"`
|
||||
ChainID int64 `json:"chain_id"`
|
||||
RegulatoryProfileID string `json:"regulatory_profile_id"`
|
||||
Currency string `json:"currency"`
|
||||
AmountAtomic string `json:"amount_atomic"`
|
||||
Decimals int `json:"decimals"`
|
||||
CostEnvelope quoteCostEnvelope `json:"cost_envelope"`
|
||||
Deadline string `json:"deadline"`
|
||||
ContractAddress string `json:"contract_address"`
|
||||
Method string `json:"method"`
|
||||
Calldata string `json:"calldata"`
|
||||
Value string `json:"value"`
|
||||
OwnerWallet string `json:"owner_wallet,omitempty"`
|
||||
PayerWallet string `json:"payer_wallet,omitempty"`
|
||||
SponsorshipMode string `json:"sponsorship_mode,omitempty"`
|
||||
SponsorOrgRoot string `json:"sponsor_org_root_id,omitempty"`
|
||||
Tx map[string]any `json:"tx"`
|
||||
}
|
||||
|
||||
type membershipConfirmRequest struct {
|
||||
@ -101,6 +102,7 @@ type membershipConfirmResponse struct {
|
||||
Status string `json:"status"`
|
||||
DesignationCode string `json:"designation_code"`
|
||||
DisplayToken string `json:"display_token"`
|
||||
RegulatoryProfileID string `json:"regulatory_profile_id"`
|
||||
TxHash string `json:"tx_hash"`
|
||||
ActivatedAt string `json:"activated_at"`
|
||||
IdentityAssurance string `json:"identity_assurance_level"`
|
||||
@ -112,6 +114,7 @@ type membershipStatusResponse struct {
|
||||
Status string `json:"status"`
|
||||
Wallet string `json:"wallet,omitempty"`
|
||||
DesignationCode string `json:"designation_code,omitempty"`
|
||||
RegulatoryProfileID string `json:"regulatory_profile_id"`
|
||||
IdentityAssurance string `json:"identity_assurance_level,omitempty"`
|
||||
IdentityAttestedBy string `json:"identity_attested_by,omitempty"`
|
||||
IdentityAttestationID string `json:"identity_attestation_id,omitempty"`
|
||||
|
||||
30
backend/secretapi/regulatory_profile.go
Normal file
30
backend/secretapi/regulatory_profile.go
Normal file
@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
regulatoryProfileUSGeneral2026 = "us_general_2026"
|
||||
regulatoryProfileEUAIBaseline2026 = "eu_ai_act_2026_baseline"
|
||||
defaultRegulatoryProfileID = regulatoryProfileUSGeneral2026
|
||||
)
|
||||
|
||||
func normalizeRegulatoryProfileID(raw string) string {
|
||||
value := strings.ToLower(strings.TrimSpace(raw))
|
||||
switch value {
|
||||
case "", "us", "us_general", regulatoryProfileUSGeneral2026:
|
||||
return regulatoryProfileUSGeneral2026
|
||||
case "eu", "eu_ai_act", regulatoryProfileEUAIBaseline2026:
|
||||
return regulatoryProfileEUAIBaseline2026
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func isKnownRegulatoryProfileID(value string) bool {
|
||||
switch normalizeRegulatoryProfileID(value) {
|
||||
case regulatoryProfileUSGeneral2026, regulatoryProfileEUAIBaseline2026:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -58,6 +58,7 @@ Success (`200`):
|
||||
```json
|
||||
{
|
||||
"quote_id": "cq_01HZZXFQ27ZP6MP0V2R9M6V3KX",
|
||||
"regulatory_profile_id": "us_general_2026",
|
||||
"wallet": "0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5",
|
||||
"payer_wallet": "0x2299547f6fA9A8f9b6d9aEA9F9D8A4B53C8A0e11",
|
||||
"offer_id": "edut.workspace.core",
|
||||
@ -157,6 +158,7 @@ Success (`200`):
|
||||
{
|
||||
"status": "entitlement_active",
|
||||
"entitlement_id": "ent:8453:0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5:000001",
|
||||
"regulatory_profile_id": "us_general_2026",
|
||||
"offer_id": "edut.workspace.core",
|
||||
"org_root_id": "org.acme.root",
|
||||
"principal_id": "human.joshua",
|
||||
|
||||
@ -159,6 +159,7 @@ Success (`200`):
|
||||
{
|
||||
"quote_id": "mq_01HZZX4F8VQXJ6A57R8P3SCB2W",
|
||||
"chain_id": 8453,
|
||||
"regulatory_profile_id": "us_general_2026",
|
||||
"currency": "USDC",
|
||||
"amount_atomic": "100000000",
|
||||
"decimals": 6,
|
||||
@ -238,6 +239,7 @@ Success (`200`):
|
||||
"status": "membership_active",
|
||||
"designation_code": "0217073045482",
|
||||
"display_token": "0217-0730-4548-2",
|
||||
"regulatory_profile_id": "us_general_2026",
|
||||
"tx_hash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"activated_at": "2026-02-17T07:33:09Z",
|
||||
"identity_assurance_level": "onramp_attested",
|
||||
@ -269,6 +271,7 @@ Success (`200`):
|
||||
"status": "active",
|
||||
"wallet": "0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5",
|
||||
"designation_code": "0217073045482",
|
||||
"regulatory_profile_id": "us_general_2026",
|
||||
"identity_assurance_level": "onramp_attested",
|
||||
"identity_attested_by": "moonpay",
|
||||
"identity_attestation_id": "mp_session_01JAA..."
|
||||
|
||||
@ -222,12 +222,15 @@ components:
|
||||
description: If true, quote may bundle first-time membership fee into total.
|
||||
CheckoutQuoteResponse:
|
||||
type: object
|
||||
required: [quote_id, wallet, offer_id, currency, amount_atomic, total_amount_atomic, decimals, cost_envelope, policy_hash, expires_at]
|
||||
required: [quote_id, regulatory_profile_id, wallet, offer_id, currency, amount_atomic, total_amount_atomic, decimals, cost_envelope, policy_hash, expires_at]
|
||||
properties:
|
||||
quote_id:
|
||||
type: string
|
||||
merchant_id:
|
||||
type: string
|
||||
regulatory_profile_id:
|
||||
type: string
|
||||
enum: [us_general_2026, eu_ai_act_2026_baseline]
|
||||
wallet:
|
||||
type: string
|
||||
payer_wallet:
|
||||
@ -378,7 +381,7 @@ components:
|
||||
type: integer
|
||||
CheckoutConfirmResponse:
|
||||
type: object
|
||||
required: [status, entitlement_id, offer_id, wallet, tx_hash]
|
||||
required: [status, entitlement_id, regulatory_profile_id, offer_id, wallet, tx_hash]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@ -387,6 +390,9 @@ components:
|
||||
type: string
|
||||
merchant_id:
|
||||
type: string
|
||||
regulatory_profile_id:
|
||||
type: string
|
||||
enum: [us_general_2026, eu_ai_act_2026_baseline]
|
||||
offer_id:
|
||||
type: string
|
||||
org_root_id:
|
||||
|
||||
@ -355,12 +355,15 @@ components:
|
||||
type: string
|
||||
MembershipQuoteResponse:
|
||||
type: object
|
||||
required: [quote_id, chain_id, currency, amount_atomic, decimals, cost_envelope, deadline, contract_address]
|
||||
required: [quote_id, chain_id, regulatory_profile_id, currency, amount_atomic, decimals, cost_envelope, deadline, contract_address]
|
||||
properties:
|
||||
quote_id:
|
||||
type: string
|
||||
chain_id:
|
||||
type: integer
|
||||
regulatory_profile_id:
|
||||
type: string
|
||||
enum: [us_general_2026, eu_ai_act_2026_baseline]
|
||||
currency:
|
||||
type: string
|
||||
enum: [USDC]
|
||||
@ -469,7 +472,7 @@ components:
|
||||
description: Optional provider attestation reference id.
|
||||
MembershipConfirmResponse:
|
||||
type: object
|
||||
required: [status, designation_code, display_token, tx_hash, activated_at, identity_assurance_level]
|
||||
required: [status, designation_code, display_token, regulatory_profile_id, tx_hash, activated_at, identity_assurance_level]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@ -478,6 +481,9 @@ components:
|
||||
type: string
|
||||
display_token:
|
||||
type: string
|
||||
regulatory_profile_id:
|
||||
type: string
|
||||
enum: [us_general_2026, eu_ai_act_2026_baseline]
|
||||
tx_hash:
|
||||
type: string
|
||||
activated_at:
|
||||
@ -492,7 +498,7 @@ components:
|
||||
type: string
|
||||
MembershipStatusResponse:
|
||||
type: object
|
||||
required: [status]
|
||||
required: [status, regulatory_profile_id]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@ -501,6 +507,9 @@ components:
|
||||
type: string
|
||||
designation_code:
|
||||
type: string
|
||||
regulatory_profile_id:
|
||||
type: string
|
||||
enum: [us_general_2026, eu_ai_act_2026_baseline]
|
||||
identity_assurance_level:
|
||||
type: string
|
||||
enum: [none, crypto_direct_unattested, sponsored_unattested, onramp_attested]
|
||||
|
||||
@ -258,6 +258,7 @@ Response:
|
||||
{
|
||||
"quote_id": "mq_...",
|
||||
"chain_id": 8453,
|
||||
"regulatory_profile_id": "us_general_2026",
|
||||
"currency": "USDC",
|
||||
"amount_atomic": "100000000",
|
||||
"decimals": 6,
|
||||
@ -314,11 +315,14 @@ Response:
|
||||
"status": "membership_active",
|
||||
"designation_code": "0217073045482",
|
||||
"display_token": "0217-0730-4548-2",
|
||||
"regulatory_profile_id": "us_general_2026",
|
||||
"tx_hash": "0x...",
|
||||
"activated_at": "2026-02-17T07:33:09Z"
|
||||
}
|
||||
```
|
||||
|
||||
`regulatory_profile_id` binds quote/activation responses to deployment policy posture (`us_general_2026` or `eu_ai_act_2026_baseline`).
|
||||
|
||||
## Security Controls
|
||||
|
||||
1. Intent TTL and one-time nonce consumption.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user