Emit entitlement calldata in quotes and verify tx payload
This commit is contained in:
parent
86a57b2888
commit
0696762d24
@ -12,6 +12,7 @@ SECRET_API_QUOTE_TTL_SECONDS=900
|
|||||||
SECRET_API_DOMAIN_NAME=EDUT Designation
|
SECRET_API_DOMAIN_NAME=EDUT Designation
|
||||||
SECRET_API_VERIFYING_CONTRACT=0x0000000000000000000000000000000000000000
|
SECRET_API_VERIFYING_CONTRACT=0x0000000000000000000000000000000000000000
|
||||||
SECRET_API_MEMBERSHIP_CONTRACT=0x0000000000000000000000000000000000000000
|
SECRET_API_MEMBERSHIP_CONTRACT=0x0000000000000000000000000000000000000000
|
||||||
|
SECRET_API_ENTITLEMENT_CONTRACT=0x0000000000000000000000000000000000000000
|
||||||
SECRET_API_MINT_CURRENCY=USDC
|
SECRET_API_MINT_CURRENCY=USDC
|
||||||
SECRET_API_MINT_AMOUNT_ATOMIC=100000000
|
SECRET_API_MINT_AMOUNT_ATOMIC=100000000
|
||||||
SECRET_API_MINT_DECIMALS=6
|
SECRET_API_MINT_DECIMALS=6
|
||||||
|
|||||||
@ -86,6 +86,7 @@ Company-first sponsor path is also supported:
|
|||||||
- `SECRET_API_CHAIN_ID` (default `84532`)
|
- `SECRET_API_CHAIN_ID` (default `84532`)
|
||||||
- `SECRET_API_CHAIN_RPC_URL` (optional, enables on-chain tx receipt verification)
|
- `SECRET_API_CHAIN_RPC_URL` (optional, enables on-chain tx receipt verification)
|
||||||
- `SECRET_API_REQUIRE_ONCHAIN_TX_VERIFICATION` (default `false`; when `true`, membership confirm and marketplace checkout confirm fail closed without chain receipt verification)
|
- `SECRET_API_REQUIRE_ONCHAIN_TX_VERIFICATION` (default `false`; when `true`, membership confirm and marketplace checkout confirm fail closed without chain receipt verification)
|
||||||
|
- `SECRET_API_ENTITLEMENT_CONTRACT` (optional; when set, marketplace quote emits purchase calldata for entitlement settlement contract)
|
||||||
|
|
||||||
### Membership
|
### Membership
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -741,6 +742,38 @@ func TestMarketplaceConfirmRequiresChainRPCWhenStrictVerificationEnabled(t *test
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarketplaceQuoteUsesEntitlementContractTransactionWhenConfigured(t *testing.T) {
|
||||||
|
a, _, cleanup := newTestApp(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
a.cfg.EntitlementContract = "0x1111111111111111111111111111111111111111"
|
||||||
|
ownerKey := mustKey(t)
|
||||||
|
ownerAddr := strings.ToLower(crypto.PubkeyToAddress(ownerKey.PublicKey).Hex())
|
||||||
|
if err := seedActiveMembership(context.Background(), a.store, ownerAddr); err != nil {
|
||||||
|
t.Fatalf("seed active membership: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quote := postJSONExpect[marketplaceCheckoutQuoteResponse](t, a, "/marketplace/checkout/quote", marketplaceCheckoutQuoteRequest{
|
||||||
|
Wallet: ownerAddr,
|
||||||
|
OfferID: offerIDWorkspaceCore,
|
||||||
|
OrgRootID: "org.marketplace.txshape",
|
||||||
|
PrincipalID: "human.owner",
|
||||||
|
PrincipalRole: "org_root_owner",
|
||||||
|
WorkspaceID: "workspace.alpha",
|
||||||
|
}, http.StatusOK)
|
||||||
|
|
||||||
|
if got := strings.ToLower(strings.TrimSpace(fmt.Sprint(quote.Tx["to"]))); got != strings.ToLower(a.cfg.EntitlementContract) {
|
||||||
|
t.Fatalf("tx.to mismatch: got=%s want=%s quote=%+v", got, a.cfg.EntitlementContract, quote.Tx)
|
||||||
|
}
|
||||||
|
if got := strings.ToLower(strings.TrimSpace(fmt.Sprint(quote.Tx["from"]))); got != ownerAddr {
|
||||||
|
t.Fatalf("tx.from mismatch: got=%s want=%s quote=%+v", got, ownerAddr, quote.Tx)
|
||||||
|
}
|
||||||
|
data := strings.ToLower(strings.TrimSpace(fmt.Sprint(quote.Tx["data"])))
|
||||||
|
if !strings.HasPrefix(data, "0x") || data == "0x" {
|
||||||
|
t.Fatalf("expected encoded entitlement calldata, got=%+v", quote.Tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarketplaceWorkspaceAddOnRequiresCoreEntitlement(t *testing.T) {
|
func TestMarketplaceWorkspaceAddOnRequiresCoreEntitlement(t *testing.T) {
|
||||||
a, _, cleanup := newTestApp(t)
|
a, _, cleanup := newTestApp(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|||||||
@ -9,11 +9,14 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
const membershipABI = `[{"inputs":[{"internalType":"address","name":"recipient","type":"address"}],"name":"mintMembership","outputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountPaid","type":"uint256"},{"indexed":false,"internalType":"address","name":"currency","type":"address"}],"name":"MembershipMinted","type":"event"}]`
|
const membershipABI = `[{"inputs":[{"internalType":"address","name":"recipient","type":"address"}],"name":"mintMembership","outputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountPaid","type":"uint256"},{"indexed":false,"internalType":"address","name":"currency","type":"address"}],"name":"MembershipMinted","type":"event"}]`
|
||||||
|
|
||||||
|
const entitlementABI = `[{"inputs":[{"internalType":"string","name":"offerId","type":"string"},{"internalType":"address","name":"ownerWallet","type":"address"},{"internalType":"bytes32","name":"orgRootKey","type":"bytes32"},{"internalType":"bytes32","name":"workspaceKey","type":"bytes32"}],"name":"purchaseEntitlement","outputs":[{"internalType":"uint256","name":"entitlementId","type":"uint256"}],"stateMutability":"payable","type":"function"}]`
|
||||||
|
|
||||||
func encodeMintMembershipCalldata(recipient string) (string, error) {
|
func encodeMintMembershipCalldata(recipient string) (string, error) {
|
||||||
parsed, err := abi.JSON(strings.NewReader(membershipABI))
|
parsed, err := abi.JSON(strings.NewReader(membershipABI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,12 +79,27 @@ func verifyMintedOnChain(ctx context.Context, cfg Config, txHash string, expecte
|
|||||||
}
|
}
|
||||||
|
|
||||||
func verifyTransactionSenderOnChain(ctx context.Context, cfg Config, txHash string, expectedSender string) error {
|
func verifyTransactionSenderOnChain(ctx context.Context, cfg Config, txHash string, expectedSender string) error {
|
||||||
|
return verifyTransactionCallOnChain(ctx, cfg, txHash, expectedSender, "", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyTransactionCallOnChain(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg Config,
|
||||||
|
txHash string,
|
||||||
|
expectedSender string,
|
||||||
|
expectedTo string,
|
||||||
|
expectedData string,
|
||||||
|
expectedValueHex string,
|
||||||
|
) error {
|
||||||
if strings.TrimSpace(cfg.ChainRPCURL) == "" {
|
if strings.TrimSpace(cfg.ChainRPCURL) == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
expectedSender = strings.TrimSpace(expectedSender)
|
expectedSender = strings.TrimSpace(expectedSender)
|
||||||
if expectedSender == "" {
|
expectedTo = strings.TrimSpace(expectedTo)
|
||||||
return fmt.Errorf("expected sender missing")
|
expectedData = strings.ToLower(strings.TrimSpace(expectedData))
|
||||||
|
expectedValueHex = strings.ToLower(strings.TrimSpace(expectedValueHex))
|
||||||
|
if expectedSender == "" && expectedTo == "" && expectedData == "" && expectedValueHex == "" {
|
||||||
|
return fmt.Errorf("no expected tx parameters provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := ethclient.DialContext(ctx, cfg.ChainRPCURL)
|
client, err := ethclient.DialContext(ctx, cfg.ChainRPCURL)
|
||||||
@ -120,10 +138,63 @@ func verifyTransactionSenderOnChain(ctx context.Context, cfg Config, txHash stri
|
|||||||
return fmt.Errorf("resolve tx sender: %w", err)
|
return fmt.Errorf("resolve tx sender: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if expectedSender != "" {
|
||||||
gotSender := strings.ToLower(from.Hex())
|
gotSender := strings.ToLower(from.Hex())
|
||||||
wantSender := strings.ToLower(common.HexToAddress(expectedSender).Hex())
|
wantSender := strings.ToLower(common.HexToAddress(expectedSender).Hex())
|
||||||
if gotSender != wantSender {
|
if gotSender != wantSender {
|
||||||
return fmt.Errorf("tx sender mismatch got=%s want=%s", gotSender, wantSender)
|
return fmt.Errorf("tx sender mismatch got=%s want=%s", gotSender, wantSender)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedTo != "" {
|
||||||
|
if tx.To() == nil {
|
||||||
|
return fmt.Errorf("tx target missing")
|
||||||
|
}
|
||||||
|
gotTo := strings.ToLower(tx.To().Hex())
|
||||||
|
wantTo := strings.ToLower(common.HexToAddress(expectedTo).Hex())
|
||||||
|
if gotTo != wantTo {
|
||||||
|
return fmt.Errorf("tx target mismatch got=%s want=%s", gotTo, wantTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedData != "" {
|
||||||
|
gotData := "0x" + common.Bytes2Hex(tx.Data())
|
||||||
|
if strings.ToLower(gotData) != expectedData {
|
||||||
|
return fmt.Errorf("tx calldata mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedValueHex != "" {
|
||||||
|
wantValue := new(big.Int)
|
||||||
|
if _, ok := wantValue.SetString(strings.TrimPrefix(expectedValueHex, "0x"), 16); !ok {
|
||||||
|
return fmt.Errorf("invalid expected tx value")
|
||||||
|
}
|
||||||
|
if tx.Value().Cmp(wantValue) != 0 {
|
||||||
|
return fmt.Errorf("tx value mismatch got=%s want=%s", tx.Value().String(), wantValue.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func encodePurchaseEntitlementCalldata(offerID, ownerWallet, orgRootID, workspaceID string) (string, error) {
|
||||||
|
parsed, err := abi.JSON(strings.NewReader(entitlementABI))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("parse entitlement abi: %w", err)
|
||||||
|
}
|
||||||
|
ownerAddress := common.HexToAddress(ownerWallet)
|
||||||
|
orgRootKey := hashIdentifierKey(orgRootID)
|
||||||
|
workspaceKey := hashIdentifierKey(workspaceID)
|
||||||
|
data, err := parsed.Pack("purchaseEntitlement", strings.TrimSpace(offerID), ownerAddress, orgRootKey, workspaceKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("pack entitlement calldata: %w", err)
|
||||||
|
}
|
||||||
|
return "0x" + common.Bytes2Hex(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashIdentifierKey(value string) common.Hash {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value == "" {
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
|
return common.BytesToHash(crypto.Keccak256([]byte(value)))
|
||||||
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ type Config struct {
|
|||||||
DomainName string
|
DomainName string
|
||||||
VerifyingContract string
|
VerifyingContract string
|
||||||
MembershipContract string
|
MembershipContract string
|
||||||
|
EntitlementContract string
|
||||||
MintCurrency string
|
MintCurrency string
|
||||||
MintAmountAtomic string
|
MintAmountAtomic string
|
||||||
MintDecimals int
|
MintDecimals int
|
||||||
@ -51,6 +52,7 @@ func loadConfig() Config {
|
|||||||
DomainName: env("SECRET_API_DOMAIN_NAME", "EDUT Designation"),
|
DomainName: env("SECRET_API_DOMAIN_NAME", "EDUT Designation"),
|
||||||
VerifyingContract: strings.ToLower(env("SECRET_API_VERIFYING_CONTRACT", "0x0000000000000000000000000000000000000000")),
|
VerifyingContract: strings.ToLower(env("SECRET_API_VERIFYING_CONTRACT", "0x0000000000000000000000000000000000000000")),
|
||||||
MembershipContract: strings.ToLower(env("SECRET_API_MEMBERSHIP_CONTRACT", "0x0000000000000000000000000000000000000000")),
|
MembershipContract: strings.ToLower(env("SECRET_API_MEMBERSHIP_CONTRACT", "0x0000000000000000000000000000000000000000")),
|
||||||
|
EntitlementContract: strings.ToLower(env("SECRET_API_ENTITLEMENT_CONTRACT", "0x0000000000000000000000000000000000000000")),
|
||||||
MintCurrency: strings.ToUpper(env("SECRET_API_MINT_CURRENCY", "USDC")),
|
MintCurrency: strings.ToUpper(env("SECRET_API_MINT_CURRENCY", "USDC")),
|
||||||
MintAmountAtomic: env("SECRET_API_MINT_AMOUNT_ATOMIC", "100000000"),
|
MintAmountAtomic: env("SECRET_API_MINT_AMOUNT_ATOMIC", "100000000"),
|
||||||
MintDecimals: envInt("SECRET_API_MINT_DECIMALS", 6),
|
MintDecimals: envInt("SECRET_API_MINT_DECIMALS", 6),
|
||||||
|
|||||||
@ -376,6 +376,20 @@ func (a *app) handleMarketplaceCheckoutQuote(w http.ResponseWriter, r *http.Requ
|
|||||||
writeErrorCode(w, http.StatusInternalServerError, "quote_generation_failed", "failed to generate quote id")
|
writeErrorCode(w, http.StatusInternalServerError, "quote_generation_failed", "failed to generate quote id")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
txTo := strings.ToLower(strings.TrimSpace(a.cfg.MembershipContract))
|
||||||
|
txData := "0x"
|
||||||
|
txValueHex := "0x0"
|
||||||
|
if entitlementContract := strings.ToLower(strings.TrimSpace(a.cfg.EntitlementContract)); entitlementContract != "" &&
|
||||||
|
!strings.EqualFold(entitlementContract, "0x0000000000000000000000000000000000000000") {
|
||||||
|
entitlementCalldata, calldataErr := encodePurchaseEntitlementCalldata(offer.OfferID, wallet, orgRootID, workspaceID)
|
||||||
|
if calldataErr != nil {
|
||||||
|
writeErrorCode(w, http.StatusInternalServerError, "quote_generation_failed", "failed to encode checkout transaction")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
txTo = entitlementContract
|
||||||
|
txData = entitlementCalldata
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
expiresAt := now.Add(a.cfg.QuoteTTL)
|
expiresAt := now.Add(a.cfg.QuoteTTL)
|
||||||
accessClass := "connected"
|
accessClass := "connected"
|
||||||
@ -400,6 +414,9 @@ func (a *app) handleMarketplaceCheckoutQuote(w http.ResponseWriter, r *http.Requ
|
|||||||
PolicyHash: a.cfg.GovernancePolicyHash,
|
PolicyHash: a.cfg.GovernancePolicyHash,
|
||||||
AccessClass: accessClass,
|
AccessClass: accessClass,
|
||||||
AvailabilityState: "active",
|
AvailabilityState: "active",
|
||||||
|
ExpectedTxTo: txTo,
|
||||||
|
ExpectedTxData: txData,
|
||||||
|
ExpectedTxValueHex: txValueHex,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
}
|
}
|
||||||
@ -427,9 +444,10 @@ func (a *app) handleMarketplaceCheckoutQuote(w http.ResponseWriter, r *http.Requ
|
|||||||
PolicyHash: quote.PolicyHash,
|
PolicyHash: quote.PolicyHash,
|
||||||
ExpiresAt: quote.ExpiresAt.Format(time.RFC3339Nano),
|
ExpiresAt: quote.ExpiresAt.Format(time.RFC3339Nano),
|
||||||
Tx: map[string]any{
|
Tx: map[string]any{
|
||||||
"to": strings.ToLower(a.cfg.MembershipContract),
|
"from": quote.PayerWallet,
|
||||||
"value": "0x0",
|
"to": quote.ExpectedTxTo,
|
||||||
"data": "0x",
|
"value": quote.ExpectedTxValueHex,
|
||||||
|
"data": quote.ExpectedTxData,
|
||||||
},
|
},
|
||||||
AccessClass: quote.AccessClass,
|
AccessClass: quote.AccessClass,
|
||||||
AvailabilityState: quote.AvailabilityState,
|
AvailabilityState: quote.AvailabilityState,
|
||||||
@ -535,7 +553,15 @@ func (a *app) handleMarketplaceCheckoutConfirm(w http.ResponseWriter, r *http.Re
|
|||||||
if expectedPayer == "" {
|
if expectedPayer == "" {
|
||||||
expectedPayer = wallet
|
expectedPayer = wallet
|
||||||
}
|
}
|
||||||
if err := verifyTransactionSenderOnChain(r.Context(), a.cfg, req.TxHash, expectedPayer); err != nil {
|
if err := verifyTransactionCallOnChain(
|
||||||
|
r.Context(),
|
||||||
|
a.cfg,
|
||||||
|
req.TxHash,
|
||||||
|
expectedPayer,
|
||||||
|
quote.ExpectedTxTo,
|
||||||
|
quote.ExpectedTxData,
|
||||||
|
quote.ExpectedTxValueHex,
|
||||||
|
); err != nil {
|
||||||
writeErrorCode(w, http.StatusConflict, "tx_verification_failed", fmt.Sprintf("tx verification pending/failed: %v", err))
|
writeErrorCode(w, http.StatusConflict, "tx_verification_failed", fmt.Sprintf("tx verification pending/failed: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,6 +141,9 @@ type marketplaceQuoteRecord struct {
|
|||||||
PolicyHash string
|
PolicyHash string
|
||||||
AccessClass string
|
AccessClass string
|
||||||
AvailabilityState string
|
AvailabilityState string
|
||||||
|
ExpectedTxTo string
|
||||||
|
ExpectedTxData string
|
||||||
|
ExpectedTxValueHex string
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
ExpiresAt time.Time
|
ExpiresAt time.Time
|
||||||
ConfirmedAt *time.Time
|
ConfirmedAt *time.Time
|
||||||
|
|||||||
@ -97,6 +97,9 @@ func (s *store) migrate(ctx context.Context) error {
|
|||||||
policy_hash TEXT NOT NULL,
|
policy_hash TEXT NOT NULL,
|
||||||
access_class TEXT NOT NULL,
|
access_class TEXT NOT NULL,
|
||||||
availability_state TEXT NOT NULL,
|
availability_state TEXT NOT NULL,
|
||||||
|
expected_tx_to TEXT,
|
||||||
|
expected_tx_data TEXT,
|
||||||
|
expected_tx_value_hex TEXT,
|
||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
expires_at TEXT NOT NULL,
|
expires_at TEXT NOT NULL,
|
||||||
confirmed_at TEXT,
|
confirmed_at TEXT,
|
||||||
@ -239,6 +242,15 @@ func (s *store) migrate(ctx context.Context) error {
|
|||||||
if err := s.ensureColumn(ctx, "quotes", "sponsor_org_root_id", "TEXT"); err != nil {
|
if err := s.ensureColumn(ctx, "quotes", "sponsor_org_root_id", "TEXT"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.ensureColumn(ctx, "marketplace_quotes", "expected_tx_to", "TEXT"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.ensureColumn(ctx, "marketplace_quotes", "expected_tx_data", "TEXT"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.ensureColumn(ctx, "marketplace_quotes", "expected_tx_value_hex", "TEXT"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,8 +432,8 @@ func (s *store) putMarketplaceQuote(ctx context.Context, quote marketplaceQuoteR
|
|||||||
INSERT INTO marketplace_quotes (
|
INSERT INTO marketplace_quotes (
|
||||||
quote_id, wallet, payer_wallet, offer_id, org_root_id, principal_id, principal_role, workspace_id,
|
quote_id, wallet, payer_wallet, offer_id, org_root_id, principal_id, principal_role, workspace_id,
|
||||||
currency, amount_atomic, total_amount_atomic, decimals, membership_included, line_items_json,
|
currency, amount_atomic, total_amount_atomic, decimals, membership_included, line_items_json,
|
||||||
policy_hash, access_class, availability_state, created_at, expires_at, confirmed_at, confirmed_tx_hash
|
policy_hash, access_class, availability_state, expected_tx_to, expected_tx_data, expected_tx_value_hex, created_at, expires_at, confirmed_at, confirmed_tx_hash
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT(quote_id) DO UPDATE SET
|
ON CONFLICT(quote_id) DO UPDATE SET
|
||||||
wallet=excluded.wallet,
|
wallet=excluded.wallet,
|
||||||
payer_wallet=excluded.payer_wallet,
|
payer_wallet=excluded.payer_wallet,
|
||||||
@ -439,6 +451,9 @@ func (s *store) putMarketplaceQuote(ctx context.Context, quote marketplaceQuoteR
|
|||||||
policy_hash=excluded.policy_hash,
|
policy_hash=excluded.policy_hash,
|
||||||
access_class=excluded.access_class,
|
access_class=excluded.access_class,
|
||||||
availability_state=excluded.availability_state,
|
availability_state=excluded.availability_state,
|
||||||
|
expected_tx_to=excluded.expected_tx_to,
|
||||||
|
expected_tx_data=excluded.expected_tx_data,
|
||||||
|
expected_tx_value_hex=excluded.expected_tx_value_hex,
|
||||||
created_at=excluded.created_at,
|
created_at=excluded.created_at,
|
||||||
expires_at=excluded.expires_at,
|
expires_at=excluded.expires_at,
|
||||||
confirmed_at=excluded.confirmed_at,
|
confirmed_at=excluded.confirmed_at,
|
||||||
@ -460,6 +475,9 @@ func (s *store) putMarketplaceQuote(ctx context.Context, quote marketplaceQuoteR
|
|||||||
strings.TrimSpace(quote.PolicyHash),
|
strings.TrimSpace(quote.PolicyHash),
|
||||||
strings.ToLower(strings.TrimSpace(quote.AccessClass)),
|
strings.ToLower(strings.TrimSpace(quote.AccessClass)),
|
||||||
strings.ToLower(strings.TrimSpace(quote.AvailabilityState)),
|
strings.ToLower(strings.TrimSpace(quote.AvailabilityState)),
|
||||||
|
nullableString(strings.ToLower(strings.TrimSpace(quote.ExpectedTxTo))),
|
||||||
|
nullableString(strings.ToLower(strings.TrimSpace(quote.ExpectedTxData))),
|
||||||
|
nullableString(strings.ToLower(strings.TrimSpace(quote.ExpectedTxValueHex))),
|
||||||
quote.CreatedAt.UTC().Format(time.RFC3339Nano),
|
quote.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||||
quote.ExpiresAt.UTC().Format(time.RFC3339Nano),
|
quote.ExpiresAt.UTC().Format(time.RFC3339Nano),
|
||||||
formatNullableTime(quote.ConfirmedAt),
|
formatNullableTime(quote.ConfirmedAt),
|
||||||
@ -472,12 +490,13 @@ func (s *store) getMarketplaceQuote(ctx context.Context, quoteID string) (market
|
|||||||
row := s.db.QueryRowContext(ctx, `
|
row := s.db.QueryRowContext(ctx, `
|
||||||
SELECT quote_id, wallet, payer_wallet, offer_id, org_root_id, principal_id, principal_role, workspace_id,
|
SELECT quote_id, wallet, payer_wallet, offer_id, org_root_id, principal_id, principal_role, workspace_id,
|
||||||
currency, amount_atomic, total_amount_atomic, decimals, membership_included, line_items_json,
|
currency, amount_atomic, total_amount_atomic, decimals, membership_included, line_items_json,
|
||||||
policy_hash, access_class, availability_state, created_at, expires_at, confirmed_at, confirmed_tx_hash
|
policy_hash, access_class, availability_state, expected_tx_to, expected_tx_data, expected_tx_value_hex, created_at, expires_at, confirmed_at, confirmed_tx_hash
|
||||||
FROM marketplace_quotes
|
FROM marketplace_quotes
|
||||||
WHERE quote_id = ?
|
WHERE quote_id = ?
|
||||||
`, strings.TrimSpace(quoteID))
|
`, strings.TrimSpace(quoteID))
|
||||||
var rec marketplaceQuoteRecord
|
var rec marketplaceQuoteRecord
|
||||||
var payerWallet, orgRootID, principalID, principalRole, workspaceID sql.NullString
|
var payerWallet, orgRootID, principalID, principalRole, workspaceID sql.NullString
|
||||||
|
var expectedTxTo, expectedTxData, expectedTxValueHex sql.NullString
|
||||||
var createdAt, expiresAt, confirmedAt, confirmedTxHash sql.NullString
|
var createdAt, expiresAt, confirmedAt, confirmedTxHash sql.NullString
|
||||||
var membershipIncluded int
|
var membershipIncluded int
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
@ -498,6 +517,9 @@ func (s *store) getMarketplaceQuote(ctx context.Context, quoteID string) (market
|
|||||||
&rec.PolicyHash,
|
&rec.PolicyHash,
|
||||||
&rec.AccessClass,
|
&rec.AccessClass,
|
||||||
&rec.AvailabilityState,
|
&rec.AvailabilityState,
|
||||||
|
&expectedTxTo,
|
||||||
|
&expectedTxData,
|
||||||
|
&expectedTxValueHex,
|
||||||
&createdAt,
|
&createdAt,
|
||||||
&expiresAt,
|
&expiresAt,
|
||||||
&confirmedAt,
|
&confirmedAt,
|
||||||
@ -514,6 +536,9 @@ func (s *store) getMarketplaceQuote(ctx context.Context, quoteID string) (market
|
|||||||
rec.PrincipalID = principalID.String
|
rec.PrincipalID = principalID.String
|
||||||
rec.PrincipalRole = principalRole.String
|
rec.PrincipalRole = principalRole.String
|
||||||
rec.WorkspaceID = workspaceID.String
|
rec.WorkspaceID = workspaceID.String
|
||||||
|
rec.ExpectedTxTo = expectedTxTo.String
|
||||||
|
rec.ExpectedTxData = expectedTxData.String
|
||||||
|
rec.ExpectedTxValueHex = expectedTxValueHex.String
|
||||||
rec.MembershipIncluded = membershipIncluded == 1
|
rec.MembershipIncluded = membershipIncluded == 1
|
||||||
rec.CreatedAt = parseRFC3339Nullable(createdAt)
|
rec.CreatedAt = parseRFC3339Nullable(createdAt)
|
||||||
rec.ExpiresAt = parseRFC3339Nullable(expiresAt)
|
rec.ExpiresAt = parseRFC3339Nullable(expiresAt)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user