diff --git a/cmd/facilitator/main.go b/cmd/facilitator/main.go index f6b567e..dd7f6ee 100644 --- a/cmd/facilitator/main.go +++ b/cmd/facilitator/main.go @@ -46,10 +46,9 @@ func run() { } log.Logger = zerolog.New(os.Stdout).With().Timestamp().Caller().Logger() - facilitator, err := facilitator.NewFacilitator(config.Scheme, config.Url, config.PrivateKey) + facilitator, err := facilitator.NewFacilitator(config.Scheme, config.Network, config.Url, config.PrivateKey) if err != nil { - log.Fatal().Err(err). - Msg("Failed to create facilitator, shutting down...") + log.Fatal().Err(err).Msg("Failed to create facilitator, shutting down...") } api := api.NewServer(facilitator) diff --git a/facilitator/evm.go b/facilitator/evm.go index 4d33762..86332e2 100644 --- a/facilitator/evm.go +++ b/facilitator/evm.go @@ -28,26 +28,29 @@ type EVMFacilitator struct { address common.Address } -func NewEVMFacilitator(networkOrRpcUrl string, privateKeyHex string) (*EVMFacilitator, error) { - var url string - if chainInfo := evm.GetChainInfo(networkOrRpcUrl); chainInfo != nil { - url = chainInfo.DefaultUrl // if networkName is provided, use default URL - } else { - url = networkOrRpcUrl // if it's not a known network name, treat it as a URL +func NewEVMFacilitator(network string, url string, privateKeyHex string) (*EVMFacilitator, error) { + if network == "" && url == "" { + return nil, fmt.Errorf("network or rpc url must be provided") + } else if url == "" { + // if url is not provided, use default URL + if chainInfo := evm.GetChainInfo(network); chainInfo == nil { + return nil, fmt.Errorf("unsupported network name: %s", network) + } else { + url = chainInfo.DefaultUrl + } } client, err := ethclient.Dial(url) if err != nil { return nil, fmt.Errorf("failed to connect to Ethereum client: %w", err) } - networkId, err := client.NetworkID(context.Background()) if err != nil { return nil, fmt.Errorf("failed to get network ID: %w", err) } - network := evm.GetChainName(networkId) - if network == "" { - return nil, fmt.Errorf("unsupported network ID: %s", networkId.String()) + chainName := evm.GetChainName(networkId) + if chainName == "" || chainName != network { + return nil, fmt.Errorf("unsupported network: %s", network) } privateKey, err := hex.DecodeString(privateKeyHex) @@ -143,7 +146,7 @@ func (t *EVMFacilitator) Verify(ctx context.Context, payload *types.PaymentPaylo if err != nil { return nil, err } - if valid := evm.VerifySignature(pubkey, digest, sig); !valid { + if valid := evm.VerifySignature(pubkey, digest, sig[:64]); !valid { return &types.PaymentVerifyResponse{ IsValid: false, InvalidReason: types.ErrInvalidSignature.Error(), diff --git a/facilitator/evm_test.go b/facilitator/evm_test.go index 87416f3..3f13bcf 100644 --- a/facilitator/evm_test.go +++ b/facilitator/evm_test.go @@ -13,21 +13,45 @@ import ( ) const ( - PrivateKey = "" - X402Version = 1 - Network = "base-sepolia" - Token = "USDC" + PrivateKey = "" + Network = "base-sepolia" + Token = "USDC" ) func TestEVMVerify(t *testing.T) { - facilitator, err := NewEVMFacilitator(Network, PrivateKey) + facilitator, err := NewEVMFacilitator(Network, "", PrivateKey) require.NoError(t, err) - _ = facilitator + privKey, err := hex.DecodeString("") + require.NoError(t, err) + evmPayload, err := evm.NewEVMPayload(Network, Token, + "", "", big.NewInt(10000), evm.NewRawPrivateSigner(privKey)) + require.NoError(t, err) + + evmPayloadJson, err := json.Marshal(evmPayload) + require.NoError(t, err) + + payload := &types.PaymentPayload{ + X402Version: int(types.X402VersionV1), + Scheme: string(types.EVM), + Network: Network, + Payload: evmPayloadJson, + } + req := &types.PaymentRequirements{ + Scheme: string(types.EVM), + Network: Network, + Asset: Token, + } + + res, err := facilitator.Verify(t.Context(), payload, req) + require.NoError(t, err) + jsonRes, err := json.MarshalIndent(res, "", "\t") + require.NoError(t, err) + fmt.Println(string(jsonRes)) } func TestEVMSettle(t *testing.T) { - facilitator, err := NewEVMFacilitator(Network, PrivateKey) + facilitator, err := NewEVMFacilitator(Network, "", PrivateKey) require.NoError(t, err) privKey, err := hex.DecodeString("") @@ -38,10 +62,8 @@ func TestEVMSettle(t *testing.T) { evmPayloadJson, err := json.Marshal(evmPayload) require.NoError(t, err) - domainConfig := evm.GetDomainConfig(Network, Token) - payload := &types.PaymentPayload{ - X402Version: X402Version, + X402Version: int(types.X402VersionV1), Scheme: string(types.EVM), Network: Network, Payload: evmPayloadJson, @@ -50,7 +72,7 @@ func TestEVMSettle(t *testing.T) { req := &types.PaymentRequirements{ Scheme: string(types.EVM), Network: Network, - Asset: domainConfig.VerifyingContract.String(), + Asset: Token, } res, err := facilitator.Settle(t.Context(), payload, req) diff --git a/facilitator/iface.go b/facilitator/iface.go index 15b589a..a024b08 100644 --- a/facilitator/iface.go +++ b/facilitator/iface.go @@ -13,16 +13,16 @@ type Facilitator interface { Supported() []*types.SupportedKind } -func NewFacilitator(scheme types.Scheme, networkOrRpcUrl string, privateKeyHex string) (Facilitator, error) { +func NewFacilitator(scheme types.Scheme, network, rpcUrl string, privateKeyHex string) (Facilitator, error) { switch scheme { case types.EVM: - return NewEVMFacilitator(networkOrRpcUrl, privateKeyHex) + return NewEVMFacilitator(network, rpcUrl, privateKeyHex) case types.Solana: - return NewSolanaFacilitator(networkOrRpcUrl, privateKeyHex) + return NewSolanaFacilitator(network, rpcUrl, privateKeyHex) case types.Sui: - return NewSuiFacilitator(networkOrRpcUrl, privateKeyHex) + return NewSuiFacilitator(network, rpcUrl, privateKeyHex) case types.Tron: - return NewTronFacilitator(networkOrRpcUrl, privateKeyHex) + return NewTronFacilitator(network, rpcUrl, privateKeyHex) default: return nil, fmt.Errorf("unsupporsed scheme: %s", scheme) } diff --git a/facilitator/solana.go b/facilitator/solana.go index 8ff8205..a3f7158 100644 --- a/facilitator/solana.go +++ b/facilitator/solana.go @@ -17,7 +17,7 @@ type SolanaFacilitator struct { feePayer solTypes.Account } -func NewSolanaFacilitator(url string, privateKeyHex string) (*SolanaFacilitator, error) { +func NewSolanaFacilitator(network string, url string, privateKeyHex string) (*SolanaFacilitator, error) { client := client.NewClient(url) privKey, err := hex.DecodeString(privateKeyHex) diff --git a/facilitator/sui.go b/facilitator/sui.go index bd071d5..0019e60 100644 --- a/facilitator/sui.go +++ b/facilitator/sui.go @@ -9,7 +9,7 @@ import ( type SuiFacilitator struct { } -func NewSuiFacilitator(url string, privateKeyHex string) (*SuiFacilitator, error) { +func NewSuiFacilitator(network string, url string, privateKeyHex string) (*SuiFacilitator, error) { return &SuiFacilitator{}, nil } diff --git a/facilitator/tron.go b/facilitator/tron.go index d9ea944..737b2b6 100644 --- a/facilitator/tron.go +++ b/facilitator/tron.go @@ -9,7 +9,7 @@ import ( type TronFacilitator struct { } -func NewTronFacilitator(url string, privateKeyHex string) (*TronFacilitator, error) { +func NewTronFacilitator(network string, url string, privateKeyHex string) (*TronFacilitator, error) { return &TronFacilitator{}, nil } diff --git a/scheme/evm/crypto.go b/scheme/evm/crypto.go index ae6b471..5d39f6a 100644 --- a/scheme/evm/crypto.go +++ b/scheme/evm/crypto.go @@ -34,7 +34,10 @@ func sigToPub(hash, sig []byte) (*secp256k1.PublicKey, error) { } // Convert to secp256k1 input format with 'recovery id' v at the beginning. btcsig := make([]byte, SignatureLength) - btcsig[0] = sig[RecoveryIDOffset] + 27 + btcsig[0] = sig[RecoveryIDOffset] + if btcsig[0] < 27 { + btcsig[0] += 27 + } copy(btcsig[1:], sig) pub, _, err := decred_ecdsa.RecoverCompact(btcsig, hash) diff --git a/types/scheme.go b/types/scheme.go index 44ff2bd..c4efbdb 100644 --- a/types/scheme.go +++ b/types/scheme.go @@ -8,3 +8,9 @@ const ( Sui Scheme = "sui" Tron Scheme = "tron" ) + +type X402Version int + +const ( + X402VersionV1 X402Version = 1 +)