Skip to content

Commit 0d42a3f

Browse files
committed
feat: cleanup go bazaar extension
1 parent 06e84a0 commit 0d42a3f

File tree

12 files changed

+218
-138
lines changed

12 files changed

+218
-138
lines changed

e2e/facilitators/go/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ require (
6666
github.com/tklauser/numcpus v0.6.1 // indirect
6767
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
6868
github.com/ugorji/go/codec v1.3.0 // indirect
69+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
70+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
71+
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
6972
go.mongodb.org/mongo-driver v1.12.2 // indirect
7073
go.uber.org/atomic v1.7.0 // indirect
7174
go.uber.org/multierr v1.6.0 // indirect

e2e/facilitators/go/go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,12 @@ github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5
263263
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
264264
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
265265
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
266+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
267+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
268+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
269+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
270+
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
271+
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
266272
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
267273
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
268274
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=

e2e/facilitators/go/main.go

Lines changed: 44 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ import (
1717
"time"
1818

1919
x402 "github.com/coinbase/x402/go"
20+
"github.com/coinbase/x402/go/extensions/bazaar"
2021
exttypes "github.com/coinbase/x402/go/extensions/types"
2122
evmmech "github.com/coinbase/x402/go/mechanisms/evm"
2223
evm "github.com/coinbase/x402/go/mechanisms/evm/exact/facilitator"
2324
evmv1 "github.com/coinbase/x402/go/mechanisms/evm/exact/v1/facilitator"
2425
svmmech "github.com/coinbase/x402/go/mechanisms/svm"
2526
svm "github.com/coinbase/x402/go/mechanisms/svm/exact/facilitator"
2627
svmv1 "github.com/coinbase/x402/go/mechanisms/svm/exact/v1/facilitator"
28+
x402types "github.com/coinbase/x402/go/types"
2729
"github.com/ethereum/go-ethereum"
2830
"github.com/ethereum/go-ethereum/accounts/abi"
2931
"github.com/ethereum/go-ethereum/common"
@@ -717,79 +719,52 @@ func main() {
717719

718720
log.Printf("✅ Payment verified: %s", paymentHash)
719721

720-
// Hook 2: Extract and catalog Bazaar discovery info (uses raw bytes escape hatch)
721-
version := ctx.Payload.GetVersion()
722-
var resourceURL string
723-
var discoveryExt map[string]interface{}
724-
725-
if version == 2 {
726-
// V2: Unmarshal payload to get Extensions and Resource
727-
var payloadV2 x402.PaymentPayload
728-
if err := json.Unmarshal(ctx.PayloadBytes, &payloadV2); err == nil {
729-
if payloadV2.Resource != nil {
730-
resourceURL = payloadV2.Resource.URL
722+
// Hook 2: Extract and catalog Bazaar discovery info using bazaar package
723+
discovered, err := bazaar.ExtractDiscoveryInfo(
724+
ctx.PayloadBytes,
725+
ctx.RequirementsBytes,
726+
true, // validate
727+
)
728+
if err != nil {
729+
log.Printf("Warning: Failed to extract discovery info: %v", err)
730+
} else if discovered != nil {
731+
log.Printf("📝 Cataloging discovered resource: %s %s", discovered.Method, discovered.ResourceURL)
732+
733+
// Unmarshal requirements for cataloging based on version
734+
version := ctx.Payload.GetVersion()
735+
if version == 2 {
736+
var requirements x402.PaymentRequirements
737+
if err := json.Unmarshal(ctx.RequirementsBytes, &requirements); err == nil {
738+
bazaarCatalog.CatalogResource(
739+
discovered.ResourceURL,
740+
discovered.Method,
741+
version,
742+
discovered.DiscoveryInfo,
743+
requirements,
744+
)
731745
}
732-
if payloadV2.Extensions != nil {
733-
if bazaar, ok := payloadV2.Extensions[exttypes.BAZAAR]; ok {
734-
if bazaarMap, ok := bazaar.(map[string]interface{}); ok {
735-
discoveryExt = bazaarMap
736-
}
746+
} else if version == 1 {
747+
var requirementsV1 x402types.PaymentRequirementsV1
748+
if err := json.Unmarshal(ctx.RequirementsBytes, &requirementsV1); err == nil {
749+
// Convert V1 requirements to V2 format for catalog
750+
// This is acceptable for e2e testing as catalog interface expects V2
751+
requirements := x402.PaymentRequirements{
752+
Scheme: requirementsV1.Scheme,
753+
Network: requirementsV1.Network,
754+
Asset: requirementsV1.Asset,
755+
Amount: requirementsV1.MaxAmountRequired, // V1 uses maxAmountRequired
756+
PayTo: requirementsV1.PayTo,
757+
MaxTimeoutSeconds: requirementsV1.MaxTimeoutSeconds,
737758
}
759+
bazaarCatalog.CatalogResource(
760+
discovered.ResourceURL,
761+
discovered.Method,
762+
version,
763+
discovered.DiscoveryInfo,
764+
requirements,
765+
)
738766
}
739767
}
740-
} else if version == 1 {
741-
// V1: Unmarshal requirements to get Resource and OutputSchema
742-
var reqsV1 x402.PaymentRequirements
743-
if err := json.Unmarshal(ctx.RequirementsBytes, &reqsV1); err == nil {
744-
// V1 uses requirements for resource URL
745-
if reqsV1.Extra != nil {
746-
if resource, ok := reqsV1.Extra["resource"].(string); ok {
747-
resourceURL = resource
748-
}
749-
}
750-
// V1 uses outputSchema for discovery info
751-
if reqsV1.Extra != nil {
752-
if outputSchema, ok := reqsV1.Extra["outputSchema"]; ok {
753-
if schemaMap, ok := outputSchema.(map[string]interface{}); ok {
754-
discoveryExt = schemaMap
755-
}
756-
}
757-
}
758-
}
759-
}
760-
761-
// Catalog if we found discovery info
762-
if resourceURL != "" && discoveryExt != nil {
763-
// Extract method from discovery extension
764-
method := "GET" // Default
765-
if input, ok := discoveryExt["input"].(map[string]interface{}); ok {
766-
if m, ok := input["method"].(string); ok {
767-
method = m
768-
}
769-
}
770-
771-
// Unmarshal requirements to generic PaymentRequirements type
772-
// NOTE: This is a simplified e2e test implementation. It does not properly
773-
// handle V1 vs V2 type differences (V1 uses maxAmountRequired, V2 uses amount).
774-
// For production Bazaar cataloging, use proper V1/V2 type handling.
775-
// This is sufficient for testing that the extension mechanism works.
776-
var requirements x402.PaymentRequirements
777-
if err := json.Unmarshal(ctx.RequirementsBytes, &requirements); err == nil {
778-
// Convert discoveryExt to DiscoveryInfo (simplified)
779-
// In production, would use bazaar.ExtractDiscoveryInfo() with proper types
780-
discoveryInfoJSON, _ := json.Marshal(discoveryExt)
781-
var discoveryInfo exttypes.DiscoveryInfo
782-
json.Unmarshal(discoveryInfoJSON, &discoveryInfo)
783-
784-
log.Printf("📝 Cataloging discovered resource: %s %s", method, resourceURL)
785-
bazaarCatalog.CatalogResource(
786-
resourceURL,
787-
method,
788-
version,
789-
&discoveryInfo,
790-
requirements,
791-
)
792-
}
793768
}
794769
}
795770
return nil

e2e/pnpm-lock.yaml

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/extensions/bazaar/bazaar_test.go

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -345,19 +345,28 @@ func TestExtractDiscoveryInfo_FullFlow(t *testing.T) {
345345
X402Version: 2,
346346
Accepted: requirements,
347347
Payload: map[string]interface{}{},
348+
Resource: &x402.ResourceInfo{
349+
URL: "https://api.example.com/data",
350+
},
348351
Extensions: map[string]interface{}{
349352
bazaar.BAZAAR: extension,
350353
},
351354
}
352355

353-
info, err := bazaar.ExtractDiscoveryInfo(paymentPayload, map[string]interface{}{}, true)
356+
// Marshal to bytes (new signature)
357+
payloadBytes, _ := json.Marshal(paymentPayload)
358+
requirementsBytes, _ := json.Marshal(requirements)
359+
360+
info, err := bazaar.ExtractDiscoveryInfo(payloadBytes, requirementsBytes, true)
354361
require.NoError(t, err)
355362
require.NotNil(t, info)
356363

357364
bodyInput, ok := info.DiscoveryInfo.Input.(bazaar.BodyInput)
358365
require.True(t, ok)
359366
assert.Equal(t, bazaar.MethodPOST, bodyInput.Method)
360367
assert.Equal(t, "http", bodyInput.Type)
368+
assert.Equal(t, "https://api.example.com/data", info.ResourceURL)
369+
assert.Equal(t, 2, info.X402Version)
361370
})
362371

363372
t.Run("should extract info from v1 PaymentRequirements", func(t *testing.T) {
@@ -381,28 +390,29 @@ func TestExtractDiscoveryInfo_FullFlow(t *testing.T) {
381390
"payTo": "0x...",
382391
"maxTimeoutSeconds": 300,
383392
"asset": "0x...",
384-
"extra": map[string]interface{}{},
385393
}
386394

387-
v1ReqStruct := x402.PaymentRequirements{
388-
Scheme: "exact",
389-
Network: "eip155:8453",
395+
v1Payload := map[string]interface{}{
396+
"x402Version": 1,
397+
"scheme": "exact",
398+
"network": "eip155:8453",
399+
"payload": map[string]interface{}{},
390400
}
391401

392-
v1Payload := x402.PaymentPayload{
393-
X402Version: 1,
394-
Accepted: v1ReqStruct,
395-
Payload: map[string]interface{}{},
396-
}
402+
// Marshal to bytes (new signature)
403+
payloadBytes, _ := json.Marshal(v1Payload)
404+
requirementsBytes, _ := json.Marshal(v1Requirements)
397405

398-
info, err := bazaar.ExtractDiscoveryInfo(v1Payload, v1Requirements, true)
406+
info, err := bazaar.ExtractDiscoveryInfo(payloadBytes, requirementsBytes, true)
399407
require.NoError(t, err)
400408
require.NotNil(t, info)
401409

402410
queryInput, ok := info.DiscoveryInfo.Input.(bazaar.QueryInput)
403411
require.True(t, ok)
404412
assert.Equal(t, bazaar.MethodGET, queryInput.Method)
405413
assert.Equal(t, "http", queryInput.Type)
414+
assert.Equal(t, "https://api.example.com/data", info.ResourceURL)
415+
assert.Equal(t, 1, info.X402Version)
406416
})
407417

408418
t.Run("should return nil when no discovery info is present", func(t *testing.T) {
@@ -417,10 +427,33 @@ func TestExtractDiscoveryInfo_FullFlow(t *testing.T) {
417427
Payload: map[string]interface{}{},
418428
}
419429

420-
info, err := bazaar.ExtractDiscoveryInfo(paymentPayload, map[string]interface{}{}, true)
430+
// Marshal to bytes (new signature)
431+
payloadBytes, _ := json.Marshal(paymentPayload)
432+
requirementsBytes, _ := json.Marshal(requirements)
433+
434+
info, err := bazaar.ExtractDiscoveryInfo(payloadBytes, requirementsBytes, true)
421435
require.NoError(t, err)
422436
assert.Nil(t, info)
423437
})
438+
439+
t.Run("should return error for invalid json", func(t *testing.T) {
440+
info, err := bazaar.ExtractDiscoveryInfo([]byte("invalid"), []byte("{}"), true)
441+
require.Error(t, err)
442+
assert.Nil(t, info)
443+
assert.Contains(t, err.Error(), "failed to parse version")
444+
})
445+
446+
t.Run("should return error for unsupported version", func(t *testing.T) {
447+
payload := map[string]interface{}{
448+
"x402Version": 99,
449+
}
450+
payloadBytes, _ := json.Marshal(payload)
451+
452+
info, err := bazaar.ExtractDiscoveryInfo(payloadBytes, []byte("{}"), true)
453+
require.Error(t, err)
454+
assert.Nil(t, info)
455+
assert.Contains(t, err.Error(), "unsupported version")
456+
})
424457
}
425458

426459
func TestValidateAndExtract(t *testing.T) {
@@ -837,19 +870,19 @@ func TestIntegration_FullWorkflow(t *testing.T) {
837870
"extra": map[string]interface{}{},
838871
}
839872

840-
v1ReqStruct := x402.PaymentRequirements{
841-
Scheme: "exact",
842-
Network: "eip155:8453",
873+
v1Payload := map[string]interface{}{
874+
"x402Version": 1,
875+
"scheme": "exact",
876+
"network": "eip155:8453",
877+
"payload": map[string]interface{}{},
843878
}
844879

845-
v1Payload := x402.PaymentPayload{
846-
X402Version: 1,
847-
Accepted: v1ReqStruct,
848-
Payload: map[string]interface{}{},
849-
}
880+
// Marshal to bytes (new signature)
881+
payloadBytes, _ := json.Marshal(v1Payload)
882+
requirementsBytes, _ := json.Marshal(v1Requirements)
850883

851884
// Facilitator extracts v1 info and transforms to v2
852-
info, err := bazaar.ExtractDiscoveryInfo(v1Payload, v1Requirements, true)
885+
info, err := bazaar.ExtractDiscoveryInfo(payloadBytes, requirementsBytes, true)
853886
require.NoError(t, err)
854887
require.NotNil(t, info)
855888

@@ -864,6 +897,10 @@ func TestIntegration_FullWorkflow(t *testing.T) {
864897
assert.NotNil(t, bodyMap["query"])
865898
assert.NotNil(t, bodyMap["chain"])
866899
assert.NotNil(t, bodyMap["type_hint"])
900+
901+
// Verify resource URL extracted correctly
902+
assert.Equal(t, "https://mesh.heurist.xyz/x402/agents/TokenResolverAgent/search", info.ResourceURL)
903+
assert.Equal(t, 1, info.X402Version)
867904
})
868905

869906
t.Run("should handle unified extraction for both v1 and v2", func(t *testing.T) {
@@ -889,21 +926,30 @@ func TestIntegration_FullWorkflow(t *testing.T) {
889926
X402Version: 2,
890927
Accepted: v2Requirements,
891928
Payload: map[string]interface{}{},
929+
Resource: &x402.ResourceInfo{
930+
URL: "https://api.example.com/items",
931+
},
892932
Extensions: map[string]interface{}{
893933
bazaar.BAZAAR: v2Extension,
894934
},
895935
}
896936

897-
v2Info, err := bazaar.ExtractDiscoveryInfo(v2Payload, map[string]interface{}{}, true)
937+
// Marshal to bytes (new signature)
938+
v2PayloadBytes, _ := json.Marshal(v2Payload)
939+
v2RequirementsBytes, _ := json.Marshal(v2Requirements)
940+
941+
v2Info, err := bazaar.ExtractDiscoveryInfo(v2PayloadBytes, v2RequirementsBytes, true)
898942
require.NoError(t, err)
899943
require.NotNil(t, v2Info)
900944

901945
queryInput, ok := v2Info.DiscoveryInfo.Input.(bazaar.QueryInput)
902946
require.True(t, ok)
903947
assert.Equal(t, bazaar.MethodGET, queryInput.Method)
948+
assert.Equal(t, 2, v2Info.X402Version)
904949

905950
// V1 case - discovery info is in PaymentRequirements.outputSchema
906951
v1Requirements := map[string]interface{}{
952+
"resource": "https://api.example.com/search",
907953
"outputSchema": map[string]interface{}{
908954
"input": map[string]interface{}{
909955
"discoverable": true,
@@ -914,24 +960,25 @@ func TestIntegration_FullWorkflow(t *testing.T) {
914960
},
915961
}
916962

917-
v1ReqStructSecond := x402.PaymentRequirements{
918-
Scheme: "exact",
919-
Network: "eip155:8453",
963+
v1Payload := map[string]interface{}{
964+
"x402Version": 1,
965+
"scheme": "exact",
966+
"network": "eip155:8453",
967+
"payload": map[string]interface{}{},
920968
}
921969

922-
v1Payload := x402.PaymentPayload{
923-
X402Version: 1,
924-
Accepted: v1ReqStructSecond,
925-
Payload: map[string]interface{}{},
926-
}
970+
// Marshal to bytes (new signature)
971+
v1PayloadBytes, _ := json.Marshal(v1Payload)
972+
v1RequirementsBytes, _ := json.Marshal(v1Requirements)
927973

928-
v1Info, err := bazaar.ExtractDiscoveryInfo(v1Payload, v1Requirements, true)
974+
v1Info, err := bazaar.ExtractDiscoveryInfo(v1PayloadBytes, v1RequirementsBytes, true)
929975
require.NoError(t, err)
930976
require.NotNil(t, v1Info)
931977

932978
queryInput2, ok := v1Info.DiscoveryInfo.Input.(bazaar.QueryInput)
933979
require.True(t, ok)
934980
assert.Equal(t, bazaar.MethodGET, queryInput2.Method)
981+
assert.Equal(t, 1, v1Info.X402Version)
935982

936983
// Both v1 and v2 return the same DiscoveryInfo structure
937984
assert.IsType(t, v2Info.DiscoveryInfo.Input, v1Info.DiscoveryInfo.Input)

0 commit comments

Comments
 (0)