Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion e2e/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,7 @@ async function runTest() {
const facilitatorSupportsAptos = facilitatorConfig?.protocolFamilies?.includes('aptos') ?? false;
const facilitatorSupportsHedera = facilitatorConfig?.protocolFamilies?.includes('hedera') ?? false;
const facilitatorSupportsStellar = facilitatorConfig?.protocolFamilies?.includes('stellar') ?? false;
const facilitatorSupportsTvm = facilitatorConfig?.protocolFamilies?.includes('tvm') ?? false;

const serverConfig: ServerConfig = {
port,
Expand All @@ -1284,7 +1285,7 @@ async function runTest() {
hederaAsset: process.env.HEDERA_ASSET,
hederaAmount: process.env.HEDERA_AMOUNT,
stellarPayTo: facilitatorSupportsStellar ? (serverStellarAddress || '') : '',
tvmPayTo: serverTvmAddress || '',
tvmPayTo: facilitatorSupportsTvm ? (serverTvmAddress || '') : '',
networks,
facilitatorUrl,
mockFacilitatorUrl,
Expand Down
3 changes: 3 additions & 0 deletions go/.changes/unreleased/fixed-20260518-153527.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: fixed
body: unwrap ERC-6492 signatures for exact/upto permit2 flows and batch-settlement
time: 2026-05-18T15:35:27.136946+02:00
21 changes: 19 additions & 2 deletions go/mechanisms/evm/batch-settlement/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"

"github.com/x402-foundation/x402/go/mechanisms/evm"
)

// erc3009DepositNonceABI is the ABI tuple (bytes32, uint256) used to derive
Expand Down Expand Up @@ -95,7 +97,10 @@ func BuildErc3009CollectorData(validAfter, validBefore, salt, signature string)
if !ok {
return nil, fmt.Errorf("invalid salt: %s", salt)
}
sigBytes := common.FromHex(signature)
sigBytes, err := unwrapERC6492HexSignature(signature)
if err != nil {
return nil, err
}

encoded, err := erc3009CollectorDataABI.Pack(va, vb, saltBig, sigBytes)
if err != nil {
Expand Down Expand Up @@ -156,7 +161,10 @@ func BuildPermit2CollectorData(nonce, deadline, permit2Signature string, eip2612
if !ok {
return nil, fmt.Errorf("invalid permit2 deadline: %s", deadline)
}
sigBytes := common.FromHex(permit2Signature)
sigBytes, err := unwrapERC6492HexSignature(permit2Signature)
if err != nil {
return nil, err
}
if eip2612PermitData == nil {
eip2612PermitData = []byte{}
}
Expand All @@ -167,3 +175,12 @@ func BuildPermit2CollectorData(nonce, deadline, permit2Signature string, eip2612
}
return encoded, nil
}

func unwrapERC6492HexSignature(signature string) ([]byte, error) {
sigBytes := common.FromHex(signature)
sigData, err := evm.ParseERC6492Signature(sigBytes)
if err != nil {
return nil, fmt.Errorf("invalid ERC-6492 signature: %w", err)
}
return sigData.InnerSignature, nil
}
83 changes: 83 additions & 0 deletions go/mechanisms/evm/batch-settlement/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"bytes"
"strings"
"testing"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)

func TestBuildErc3009DepositNonce_Deterministic(t *testing.T) {
Expand Down Expand Up @@ -206,3 +209,83 @@ func TestBuildEip2612PermitData_InvalidValue(t *testing.T) {
t.Fatal("expected error for non-numeric value")
}
}

func TestBuildErc3009CollectorData_UnwrapsERC6492Signature(t *testing.T) {
innerSig := common.FromHex("0x" + strings.Repeat("ab", 65))
wrapped := wrapERC6492Signature(t, innerSig)

collectorData, err := BuildErc3009CollectorData("0", "9999999999", "0x01", "0x"+common.Bytes2Hex(wrapped))
if err != nil {
t.Fatalf("BuildErc3009CollectorData: %v", err)
}

signature, err := decodeErc3009CollectorSignature(collectorData)
if err != nil {
t.Fatalf("decode collector data: %v", err)
}
if string(signature) != string(innerSig) {
t.Fatalf("expected inner signature, got %x", signature)
}
}

func TestBuildPermit2CollectorData_UnwrapsERC6492Signature(t *testing.T) {
innerSig := common.FromHex("0x" + strings.Repeat("ab", 65))
wrapped := wrapERC6492Signature(t, innerSig)

collectorData, err := BuildPermit2CollectorData("123", "9999999999", "0x"+common.Bytes2Hex(wrapped), nil)
if err != nil {
t.Fatalf("BuildPermit2CollectorData: %v", err)
}

signature, err := decodePermit2CollectorSignature(collectorData)
if err != nil {
t.Fatalf("decode collector data: %v", err)
}
if string(signature) != string(innerSig) {
t.Fatalf("expected inner signature, got %x", signature)
}
}

func wrapERC6492Signature(t *testing.T, innerSig []byte) []byte {
t.Helper()
addressTy, err := abi.NewType("address", "", nil)
if err != nil {
t.Fatalf("address type: %v", err)
}
bytesTy, err := abi.NewType("bytes", "", nil)
if err != nil {
t.Fatalf("bytes type: %v", err)
}
arguments := abi.Arguments{{Type: addressTy}, {Type: bytesTy}, {Type: bytesTy}}
packed, err := arguments.Pack(
common.HexToAddress("0xca11bde05977b3631167028862be2a173976ca11"),
[]byte{0xde, 0xad, 0xbe, 0xef},
innerSig,
)
if err != nil {
t.Fatalf("pack: %v", err)
}
return append(packed, common.Hex2Bytes("6492649264926492649264926492649264926492649264926492649264926492")...)
}

func decodeErc3009CollectorSignature(collectorData []byte) ([]byte, error) {
uint256Ty, _ := abi.NewType("uint256", "", nil)
bytesTy, _ := abi.NewType("bytes", "", nil)
args := abi.Arguments{{Type: uint256Ty}, {Type: uint256Ty}, {Type: uint256Ty}, {Type: bytesTy}}
unpacked, err := args.Unpack(collectorData)
if err != nil {
return nil, err
}
return unpacked[3].([]byte), nil
}

func decodePermit2CollectorSignature(collectorData []byte) ([]byte, error) {
uint256Ty, _ := abi.NewType("uint256", "", nil)
bytesTy, _ := abi.NewType("bytes", "", nil)
args := abi.Arguments{{Type: uint256Ty}, {Type: uint256Ty}, {Type: bytesTy}, {Type: bytesTy}}
unpacked, err := args.Unpack(collectorData)
if err != nil {
return nil, err
}
return unpacked[2].([]byte), nil
}
6 changes: 5 additions & 1 deletion go/mechanisms/evm/exact/facilitator/permit2_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ func BuildPermit2SettleArgs(permit2Payload *evm.ExactPermit2Payload) (*Permit2Se
if err != nil {
return nil, err
}
sigData, err := evm.ParseERC6492Signature(signatureBytes)
if err != nil {
return nil, err
}

args := &Permit2SettleArgs{}
args.Permit.Permitted.Token = common.HexToAddress(permit2Payload.Permit2Authorization.Permitted.Token)
Expand All @@ -60,7 +64,7 @@ func BuildPermit2SettleArgs(permit2Payload *evm.ExactPermit2Payload) (*Permit2Se
args.Owner = common.HexToAddress(permit2Payload.Permit2Authorization.From)
args.Witness.To = common.HexToAddress(permit2Payload.Permit2Authorization.Witness.To)
args.Witness.ValidAfter = validAfter
args.Signature = signatureBytes
args.Signature = sigData.InnerSignature
return args, nil
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package facilitator

import (
"strings"
"testing"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"

"github.com/x402-foundation/x402/go/mechanisms/evm"
)

func TestBuildPermit2SettleArgs_UnwrapsERC6492Signature(t *testing.T) {
innerSig := common.FromHex("0x" + strings.Repeat("ab", 65))
wrapped := wrapERC6492Signature(t, innerSig)

args, err := BuildPermit2SettleArgs(&evm.ExactPermit2Payload{
Signature: "0x" + common.Bytes2Hex(wrapped),
Permit2Authorization: evm.Permit2Authorization{
From: "0x1234567890123456789012345678901234567890",
Permitted: evm.Permit2TokenPermissions{
Token: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
Amount: "1000000",
},
Nonce: "123",
Deadline: "9999999999",
Witness: evm.Permit2Witness{
To: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
ValidAfter: "0",
},
},
})
if err != nil {
t.Fatalf("BuildPermit2SettleArgs: %v", err)
}
if string(args.Signature) != string(innerSig) {
t.Fatalf("expected inner signature, got %x", args.Signature)
}
}

func wrapERC6492Signature(t *testing.T, innerSig []byte) []byte {
t.Helper()
addressTy, err := abi.NewType("address", "", nil)
if err != nil {
t.Fatalf("address type: %v", err)
}
bytesTy, err := abi.NewType("bytes", "", nil)
if err != nil {
t.Fatalf("bytes type: %v", err)
}
arguments := abi.Arguments{{Type: addressTy}, {Type: bytesTy}, {Type: bytesTy}}
packed, err := arguments.Pack(
common.HexToAddress("0xca11bde05977b3631167028862be2a173976ca11"),
[]byte{0xde, 0xad, 0xbe, 0xef},
innerSig,
)
if err != nil {
t.Fatalf("pack: %v", err)
}
return append(packed, common.Hex2Bytes("6492649264926492649264926492649264926492649264926492649264926492")...)
}
6 changes: 5 additions & 1 deletion go/mechanisms/evm/upto/facilitator/permit2_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func BuildUptoPermit2SettleArgs(permit2Payload *evm.UptoPermit2Payload, settleme
if err != nil {
return nil, err
}
sigData, err := evm.ParseERC6492Signature(signatureBytes)
if err != nil {
return nil, err
}

args := &UptoPermit2SettleArgs{}
args.Permit.Permitted.Token = common.HexToAddress(permit2Payload.Permit2Authorization.Permitted.Token)
Expand All @@ -73,7 +77,7 @@ func BuildUptoPermit2SettleArgs(permit2Payload *evm.UptoPermit2Payload, settleme
args.Witness.To = common.HexToAddress(permit2Payload.Permit2Authorization.Witness.To)
args.Witness.Facilitator = common.HexToAddress(permit2Payload.Permit2Authorization.Witness.Facilitator)
args.Witness.ValidAfter = validAfter
args.Signature = signatureBytes
args.Signature = sigData.InnerSignature
return args, nil
}

Expand Down
47 changes: 47 additions & 0 deletions go/mechanisms/evm/upto/facilitator/permit2_helpers_erc6492_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package facilitator

import (
"strings"
"testing"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)

func TestBuildUptoPermit2SettleArgs_UnwrapsERC6492Signature(t *testing.T) {
innerSig := common.FromHex("0x" + strings.Repeat("ab", 65))
wrapped := wrapERC6492Signature(t, innerSig)

p := buildValidUptoPayload(testFacilitatorAddr)
p.Signature = "0x" + common.Bytes2Hex(wrapped)

args, err := BuildUptoPermit2SettleArgs(p, nil)
if err != nil {
t.Fatalf("BuildUptoPermit2SettleArgs: %v", err)
}
if string(args.Signature) != string(innerSig) {
t.Fatalf("expected inner signature, got %x", args.Signature)
}
}

func wrapERC6492Signature(t *testing.T, innerSig []byte) []byte {
t.Helper()
addressTy, err := abi.NewType("address", "", nil)
if err != nil {
t.Fatalf("address type: %v", err)
}
bytesTy, err := abi.NewType("bytes", "", nil)
if err != nil {
t.Fatalf("bytes type: %v", err)
}
arguments := abi.Arguments{{Type: addressTy}, {Type: bytesTy}, {Type: bytesTy}}
packed, err := arguments.Pack(
common.HexToAddress("0xca11bde05977b3631167028862be2a173976ca11"),
[]byte{0xde, 0xad, 0xbe, 0xef},
innerSig,
)
if err != nil {
t.Fatalf("pack: %v", err)
}
return append(packed, common.Hex2Bytes("6492649264926492649264926492649264926492649264926492649264926492")...)
}
1 change: 1 addition & 0 deletions python/x402/changelog.d/2352.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
unwrap ERC-6492 signatures for permit2 flows
5 changes: 4 additions & 1 deletion python/x402/mechanisms/evm/exact/permit2_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
X402_EXACT_PERMIT2_PROXY_ADDRESS,
X402_EXACT_PERMIT2_PROXY_SETTLE_WITH_PERMIT_ABI,
)
from ..erc6492 import parse_erc6492_signature # noqa: E402
from ..signer import ClientEvmSigner, FacilitatorEvmSigner # noqa: E402
from ..types import ( # noqa: E402
ExactPermit2Authorization,
Expand Down Expand Up @@ -458,7 +459,9 @@ def _build_permit2_settle_args(

Returns (permit_tuple, owner_addr, witness_tuple, sig_bytes).
"""
sig_bytes = hex_to_bytes(permit2_payload.signature or "")
sig_bytes = parse_erc6492_signature(
hex_to_bytes(permit2_payload.signature or "")
).inner_signature
permit_tuple = (
(
to_checksum_address(permit2_payload.permit2_authorization.permitted.token),
Expand Down
5 changes: 4 additions & 1 deletion python/x402/mechanisms/evm/upto/permit2_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
X402_UPTO_PERMIT2_PROXY_ADDRESS,
X402_UPTO_PERMIT2_PROXY_SETTLE_WITH_PERMIT_ABI,
)
from ..erc6492 import parse_erc6492_signature # noqa: E402

# Reuse exact's allowance verification and settle error mapping
from ..exact.permit2_utils import ( # noqa: E402
Expand Down Expand Up @@ -568,7 +569,9 @@ def _build_upto_permit2_settle_args(

Returns (permit_tuple, amount, owner_addr, witness_tuple, sig_bytes).
"""
sig_bytes = hex_to_bytes(permit2_payload.signature or "")
sig_bytes = parse_erc6492_signature(
hex_to_bytes(permit2_payload.signature or "")
).inner_signature
permit_tuple = (
(
to_checksum_address(permit2_payload.permit2_authorization.permitted.token),
Expand Down
Loading
Loading