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
75 changes: 68 additions & 7 deletions e2e/clients/axios/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { wrapAxiosWithPayment, decodePaymentResponseHeader } from "@x402/axios";
import { privateKeyToAccount } from "viem/accounts";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { registerExactSvmScheme } from "@x402/svm/exact/client";
import { registerExactMultiversXClientScheme } from "@x402/multiversx/exact/client";
import { MultiversXSigner, ISignerProvider } from "@x402/multiversx";
import { UserSigner, UserSecretKey } from "@multiversx/sdk-wallet";
import { Transaction } from "@multiversx/sdk-core";
import { base58 } from "@scure/base";
import { createKeyPairSignerFromBytes } from "@solana/kit";
import { x402Client } from "@x402/core/client";
Expand All @@ -23,11 +27,65 @@ const client = new x402Client();
registerExactEvmScheme(client, { signer: evmAccount });
registerExactSvmScheme(client, { signer: svmSigner });

// Adapter to wrap UserSigner to ISignerProvider interface
/**
* Adapter class that wraps UserSigner to implement ISignerProvider interface.
*/
class UserSignerAdapter implements ISignerProvider {
/**
* Creates a new UserSignerAdapter.
*
* @param userSigner - The underlying UserSigner instance
* @param address - The bech32 address of the signer
*/
constructor(
private userSigner: UserSigner,
private address: string,
) { }

/**
* Signs a transaction using the underlying UserSigner.
*
* @param transaction - The transaction to sign
* @returns The signed transaction
*/
async signTransaction(transaction: Transaction): Promise<Transaction> {
const serialized = transaction.serializeForSigning();
const signature = await this.userSigner.sign(serialized);
transaction.applySignature(signature);
return transaction;
}

/**
* Gets the address of the signer.
*
* @returns The bech32 address
*/
async getAddress(): Promise<string> {
return this.address;
}
}

// Register MultiversX if key is provided
const mvxPrivateKeyHex = process.env.MVX_PRIVATE_KEY;
if (mvxPrivateKeyHex && mvxPrivateKeyHex.length === 64) {
try {
const secretKey = new UserSecretKey(Buffer.from(mvxPrivateKeyHex, "hex"));
const userSigner = new UserSigner(secretKey);
const address = secretKey.generatePublicKey().toAddress().bech32();
const signerAdapter = new UserSignerAdapter(userSigner, address);
const mvxSigner = new MultiversXSigner(signerAdapter);
registerExactMultiversXClientScheme(client, { signer: mvxSigner });
} catch {
console.error("⚠️ Failed to load MultiversX private key");
}
}

const axiosWithPayment = wrapAxiosWithPayment(axios.create(), client);

axiosWithPayment
.get(url)
.then(async (response) => {
.then(async response => {
const data = response.data;
// Check both v2 (PAYMENT-RESPONSE) and v1 (X-PAYMENT-RESPONSE) headers
const paymentResponse =
Expand Down Expand Up @@ -58,11 +116,14 @@ axiosWithPayment
console.log(JSON.stringify(result));
process.exit(0);
})
.catch((error) => {
console.error(JSON.stringify({
success: false,
error: error.message || "Request failed",
status_code: error.response?.status || 500,
}));
.catch(error => {
console.error(`[DEBUG] Caught error: ${error.message}, status: ${error.response?.status}`);
console.error(
JSON.stringify({
success: false,
error: error.message || "Request failed",
status_code: error.response?.status || 500,
}),
);
process.exit(1);
});
5 changes: 4 additions & 1 deletion e2e/clients/axios/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
"@x402/svm": "workspace:*",
"axios": "^1.7.9",
"dotenv": "^16.4.7",
"viem": "^2.21.26"
"viem": "^2.21.26",
"@x402/multiversx": "workspace:*",
"@multiversx/sdk-wallet": "^4.2.0",
"@multiversx/sdk-core": "^13.0.0"
},
"devDependencies": {
"@eslint/js": "^9.24.0",
Expand Down
4 changes: 3 additions & 1 deletion e2e/clients/axios/test.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"language": "typescript",
"protocolFamilies": [
"evm",
"svm"
"svm",
"multiversx"
],
"x402Versions": [
1,
Expand All @@ -14,6 +15,7 @@
"required": [
"EVM_PRIVATE_KEY",
"SVM_PRIVATE_KEY",
"MVX_PRIVATE_KEY",
"RESOURCE_SERVER_URL",
"ENDPOINT_PATH"
],
Expand Down
69 changes: 65 additions & 4 deletions e2e/clients/fetch/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { config } from "dotenv";
import { wrapFetchWithPayment, decodePaymentResponseHeader } from "@x402/fetch";
import { wrapFetchWithPayment } from "@x402/fetch";
import { privateKeyToAccount } from "viem/accounts";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { registerExactSvmScheme } from "@x402/svm/exact/client";
import { registerExactMultiversXClientScheme } from "@x402/multiversx/exact/client";
import { MultiversXSigner, ISignerProvider } from "@x402/multiversx";
import { UserSigner, UserSecretKey } from "@multiversx/sdk-wallet";
import { Transaction } from "@multiversx/sdk-core";
import { base58 } from "@scure/base";
import { createKeyPairSignerFromBytes } from "@solana/kit";
import { x402Client, x402HTTPClient } from "@x402/core/client";
Expand All @@ -13,20 +17,77 @@ const baseURL = process.env.RESOURCE_SERVER_URL as string;
const endpointPath = process.env.ENDPOINT_PATH as string;
const url = `${baseURL}${endpointPath}`;
const evmAccount = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const svmSigner = await createKeyPairSignerFromBytes(base58.decode(process.env.SVM_PRIVATE_KEY as string));
const svmSigner = await createKeyPairSignerFromBytes(
base58.decode(process.env.SVM_PRIVATE_KEY as string),
);

// Create client and register EVM and SVM schemes using the new register helpers
const client = new x402Client();
registerExactEvmScheme(client, { signer: evmAccount });
registerExactSvmScheme(client, { signer: svmSigner });

/**
* Adapter class that wraps UserSigner to implement ISignerProvider interface.
*/
class UserSignerAdapter implements ISignerProvider {
/**
* Creates a new UserSignerAdapter.
*
* @param userSigner - The underlying UserSigner instance
* @param address - The bech32 address of the signer
*/
constructor(
private userSigner: UserSigner,
private address: string,
) {}

/**
* Signs a transaction using the underlying UserSigner.
*
* @param transaction - The transaction to sign
* @returns The signed transaction
*/
async signTransaction(transaction: Transaction): Promise<Transaction> {
const serialized = transaction.serializeForSigning();
const signature = await this.userSigner.sign(serialized);
transaction.applySignature(signature);
return transaction;
}

/**
* Gets the address of the signer.
*
* @returns The bech32 address
*/
async getAddress(): Promise<string> {
return this.address;
}
}

// Register MultiversX if key is provided
const mvxPrivateKeyHex = process.env.MVX_PRIVATE_KEY;
if (mvxPrivateKeyHex && mvxPrivateKeyHex.length === 64) {
try {
const secretKey = new UserSecretKey(Buffer.from(mvxPrivateKeyHex, "hex"));
const userSigner = new UserSigner(secretKey);
const address = secretKey.generatePublicKey().toAddress().bech32();
const signerAdapter = new UserSignerAdapter(userSigner, address);
const mvxSigner = new MultiversXSigner(signerAdapter);
registerExactMultiversXClientScheme(client, { signer: mvxSigner });
} catch {
console.error("⚠️ Failed to load MultiversX private key");
}
}

const fetchWithPayment = wrapFetchWithPayment(fetch, client);

fetchWithPayment(url, {
method: "GET",
}).then(async response => {
}).then(async (response: Response) => {
const data = await response.json();
const paymentResponse = new x402HTTPClient(client).getPaymentSettleResponse((name) => response.headers.get(name));
const paymentResponse = new x402HTTPClient(client).getPaymentSettleResponse(name =>
response.headers.get(name),
);

if (!paymentResponse) {
// No payment was required
Expand Down
5 changes: 4 additions & 1 deletion e2e/clients/fetch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
"@x402/svm": "workspace:*",
"axios": "^1.7.9",
"dotenv": "^16.4.7",
"viem": "^2.21.26"
"viem": "^2.21.26",
"@x402/multiversx": "workspace:*",
"@multiversx/sdk-wallet": "^4.2.0",
"@multiversx/sdk-core": "^13.0.0"
},
"devDependencies": {
"@eslint/js": "^9.24.0",
Expand Down
4 changes: 3 additions & 1 deletion e2e/clients/fetch/test.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"language": "typescript",
"protocolFamilies": [
"evm",
"svm"
"svm",
"multiversx"
],
"x402Versions": [
1,
Expand All @@ -14,6 +15,7 @@
"required": [
"EVM_PRIVATE_KEY",
"SVM_PRIVATE_KEY",
"MVX_PRIVATE_KEY",
"RESOURCE_SERVER_URL",
"ENDPOINT_PATH"
],
Expand Down
14 changes: 14 additions & 0 deletions e2e/clients/go-http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
x402http "github.com/coinbase/x402/go/http"
evm "github.com/coinbase/x402/go/mechanisms/evm/exact/client"
evmv1 "github.com/coinbase/x402/go/mechanisms/evm/exact/v1/client"
multiversx "github.com/coinbase/x402/go/mechanisms/multiversx/exact/client"
svm "github.com/coinbase/x402/go/mechanisms/svm/exact/client"
svmv1 "github.com/coinbase/x402/go/mechanisms/svm/exact/v1/client"
evmsigners "github.com/coinbase/x402/go/signers/evm"
mvxsigners "github.com/coinbase/x402/go/signers/multiversx"
svmsigners "github.com/coinbase/x402/go/signers/svm"
)

Expand Down Expand Up @@ -49,6 +51,11 @@ func main() {
log.Fatal("❌ SVM_PRIVATE_KEY environment variable is required")
}

mvxPrivateKey := os.Getenv("MVX_PRIVATE_KEY")
if mvxPrivateKey == "" {
log.Fatal("❌ MVX_PRIVATE_KEY environment variable is required")
}

evmSigner, err := evmsigners.NewClientSignerFromPrivateKey(evmPrivateKey)
if err != nil {
outputError(fmt.Sprintf("Failed to create EVM signer: %v", err))
Expand All @@ -61,10 +68,17 @@ func main() {
return
}

mvxSigner, err := mvxsigners.NewClientSignerFromPrivateKey(mvxPrivateKey)
if err != nil {
outputError(fmt.Sprintf("Failed to create MultiversX signer: %v", err))
return
}

// Create x402 client with fluent API
x402Client := x402.Newx402Client().
Register("eip155:*", evm.NewExactEvmScheme(evmSigner)).
Register("solana:*", svm.NewExactSvmScheme(svmSigner)).
Register("multiversx:*", multiversx.NewExactMultiversXScheme(mvxSigner)).
RegisterV1("base-sepolia", evmv1.NewExactEvmSchemeV1(evmSigner)).
RegisterV1("base", evmv1.NewExactEvmSchemeV1(evmSigner)).
RegisterV1("solana-devnet", svmv1.NewExactSvmSchemeV1(svmSigner)).
Expand Down
4 changes: 3 additions & 1 deletion e2e/clients/go-http/test.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"language": "go",
"protocolFamilies": [
"evm",
"svm"
"svm",
"multiversx"
],
"x402Versions": [
1,
Expand All @@ -14,6 +15,7 @@
"required": [
"EVM_PRIVATE_KEY",
"SVM_PRIVATE_KEY",
"MVX_PRIVATE_KEY",
"RESOURCE_SERVER_URL",
"ENDPOINT_PATH"
],
Expand Down
Loading