Skip to content
Open
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
2 changes: 2 additions & 0 deletions examples/typescript/clients/sign-in-with-x/.env-local
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
EVM_PRIVATE_KEY=
RESOURCE_SERVER_URL=http://localhost:4021
8 changes: 8 additions & 0 deletions examples/typescript/clients/sign-in-with-x/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
docs/
dist/
node_modules/
coverage/
.github/
src/client
**/**/*.json
*.md
11 changes: 11 additions & 0 deletions examples/typescript/clients/sign-in-with-x/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 100,
"proseWrap": "never"
}
74 changes: 74 additions & 0 deletions examples/typescript/clients/sign-in-with-x/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import js from "@eslint/js";
import ts from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import prettier from "eslint-plugin-prettier";
import jsdoc from "eslint-plugin-jsdoc";
import importPlugin from "eslint-plugin-import";

export default [
{
ignores: ["dist/**", "node_modules/**"],
},
{
files: ["**/*.ts"],
languageOptions: {
parser: tsParser,
sourceType: "module",
ecmaVersion: 2020,
globals: {
process: "readonly",
__dirname: "readonly",
module: "readonly",
require: "readonly",
Buffer: "readonly",
console: "readonly",
exports: "readonly",
setTimeout: "readonly",
clearTimeout: "readonly",
setInterval: "readonly",
clearInterval: "readonly",
fetch: "readonly",
},
},
plugins: {
"@typescript-eslint": ts,
prettier: prettier,
jsdoc: jsdoc,
import: importPlugin,
},
rules: {
...ts.configs.recommended.rules,
"import/first": "error",
"prettier/prettier": "error",
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_$" }],
"jsdoc/tag-lines": ["error", "any", { startLines: 1 }],
"jsdoc/check-alignment": "error",
"jsdoc/no-undefined-types": "off",
"jsdoc/check-param-names": "error",
"jsdoc/check-tag-names": "error",
"jsdoc/check-types": "error",
"jsdoc/implements-on-classes": "error",
"jsdoc/require-description": "error",
"jsdoc/require-jsdoc": [
"error",
{
require: {
FunctionDeclaration: true,
MethodDefinition: true,
ClassDeclaration: true,
ArrowFunctionExpression: false,
FunctionExpression: false,
},
},
],
"jsdoc/require-param": "error",
"jsdoc/require-param-description": "error",
"jsdoc/require-param-type": "off",
"jsdoc/require-returns": "error",
"jsdoc/require-returns-description": "error",
"jsdoc/require-returns-type": "off",
"jsdoc/require-hyphen-before-param-description": ["error", "always"],
},
},
];
83 changes: 83 additions & 0 deletions examples/typescript/clients/sign-in-with-x/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { config } from "dotenv";
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import { wrapFetchWithSIWx } from "@x402/extensions/sign-in-with-x";
config();

const evmPrivateKey = process.env.EVM_PRIVATE_KEY as `0x${string}`;
const baseURL = process.env.RESOURCE_SERVER_URL || "http://localhost:4021";

const evmSigner = privateKeyToAccount(evmPrivateKey);

// Payment wrapper - handles initial 402 by paying
const client = new x402Client();
registerExactEvmScheme(client, { signer: evmSigner });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);

// SIWX wrapper - handles 402 by proving wallet ownership (for returning users)
const fetchWithSIWx = wrapFetchWithSIWx(fetch, evmSigner);

/**
* Demonstrates the SIWX flow for a single resource.
*
* @param path - The resource path
*/
async function demonstrateResource(path: string): Promise<void> {
const url = `${baseURL}${path}`;
console.log(`\n--- ${path} ---`);

// First request: pay for access
console.log("1. First request (paying)...");
const paidResponse = await fetchWithPayment(url);
console.log(" Response:", await paidResponse.json());

// Second request: use SIWX to prove we already paid
console.log("2. Second request (SIWX auth)...");
const siwxResponse = await fetchWithSIWx(url);
console.log(" Response:", await siwxResponse.json());
}

/**
* Tests SIWX auth only (assumes server pre-seeded with TEST_ADDRESS).
*
* @param path - The resource path
*/
async function testSIWxOnly(path: string): Promise<void> {
const url = `${baseURL}${path}`;
console.log(`\n--- Testing SIWX auth for ${path} ---`);

console.log("1. Request without auth (expect 402)...");
const noAuthResponse = await fetch(url);
console.log(` Status: ${noAuthResponse.status}`);

console.log("2. Request with SIWX auth...");
const siwxResponse = await fetchWithSIWx(url);
console.log(" Response:", await siwxResponse.json());
}

/**
* Main entry point demonstrating SIWX authentication flow.
*/
async function main(): Promise<void> {
console.log(`Client address: ${evmSigner.address}`);
console.log(`Server: ${baseURL}`);

const testOnly = process.env.TEST_SIWX_ONLY === "true";

if (testOnly) {
// Test mode: assumes server has TEST_ADDRESS pre-seeded
await testSIWxOnly("/weather");
console.log("\nSIWX auth test complete.");
} else {
// Full flow: pay then auth
await demonstrateResource("/weather");
await demonstrateResource("/joke");
console.log("\nDone. Each resource required payment once, then SIWX auth worked.");
}
}

main().catch(err => {
console.error(err);
process.exit(1);
});
34 changes: 34 additions & 0 deletions examples/typescript/clients/sign-in-with-x/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@x402/sign-in-with-x-client-example",
"private": true,
"type": "module",
"scripts": {
"start": "tsx index.ts",
"dev": "tsx index.ts",
"format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"",
"format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"",
"lint": "eslint . --ext .ts --fix",
"lint:check": "eslint . --ext .ts"
},
"dependencies": {
"@x402/core": "workspace:*",
"@x402/evm": "workspace:*",
"@x402/extensions": "workspace:*",
"@x402/fetch": "workspace:*",
"dotenv": "^16.4.7",
"viem": "^2.39.0"
},
"devDependencies": {
"@eslint/js": "^9.24.0",
"@types/node": "^22.10.5",
"@typescript-eslint/eslint-plugin": "^8.29.1",
"@typescript-eslint/parser": "^8.29.1",
"eslint": "^9.24.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsdoc": "^50.6.9",
"eslint-plugin-prettier": "^5.2.6",
"prettier": "3.5.2",
"tsx": "^4.7.0",
"typescript": "^5.3.0"
}
}
Loading
Loading