diff --git a/README.md b/README.md index 3f03fa1..d0059f6 100644 --- a/README.md +++ b/README.md @@ -103,3 +103,48 @@ fetchLinkup() .then(console.log) .catch(console.error); ``` + +### 💳 X402 Payment Protocol + +The SDK supports the [X402 payment protocol](https://www.x402.org/), allowing you to pay for API +calls with on-chain transactions instead of an API key. + +#### Prerequisites + +Install the required peer dependencies: + +```bash +npm i viem @x402/core @x402/evm +``` + +#### 📝 Example + +Create a viem `LocalAccount` compatible with Base (Ethereum): + +```typescript +import { privateKeyToAccount } from 'viem/accounts'; + +const account = privateKeyToAccount(''); +``` + +```typescript +import { mnemonicToAccount } from 'viem/accounts'; + +const account = mnemonicToAccount(''); +``` + +Then pass it to `createX402Signer` and use the Linkup client: + +```typescript +import { LinkupClient } from 'linkup-sdk'; +import { createX402Signer } from 'linkup-sdk/x402'; + +const signer = createX402Signer(account); +const client = new LinkupClient({ signer }); + +const response = await client.search({ + query: 'What is the X402 payment protocol?', + depth: 'standard', + outputType: 'sourcedAnswer', +}); +``` diff --git a/jest.config.ts b/jest.config.js similarity index 100% rename from jest.config.ts rename to jest.config.js diff --git a/package-lock.json b/package-lock.json index 329d456..50076e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "linkup-sdk", - "version": "1.0.9", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "linkup-sdk", - "version": "1.0.9", + "version": "0.0.0", "license": "MIT", "dependencies": { "axios": "^1.8.2", @@ -19,17 +19,42 @@ "@commitlint/config-conventional": "^19.8.1", "@types/jest": "^29.5.14", "@types/node": "^24.0.4", + "@x402/evm": "^2.7.0", "husky": "^9.1.7", "jest": "^29.7.0", "knip": "^5.61.2", "semantic-release": "^24.2.5", "ts-jest": "^29.2.5", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "viem": "^2.47.4" }, "engines": { "node": ">=20.19.0" + }, + "peerDependencies": { + "@x402/core": "^2.6.0", + "@x402/evm": "^2.6.0", + "viem": "^2.0.0" + }, + "peerDependenciesMeta": { + "@x402/core": { + "optional": true + }, + "@x402/evm": { + "optional": true + }, + "viem": { + "optional": true + } } }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -1149,32 +1174,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@emnapi/core": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", @@ -1647,6 +1646,48 @@ "@tybys/wasm-util": "^0.9.0" } }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2071,6 +2112,45 @@ "node": ">=12" } }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -2496,38 +2576,6 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -2671,32 +2719,48 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "node_modules/@x402/core": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@x402/core/-/core-2.7.0.tgz", + "integrity": "sha512-2l1QaRO50qtWpnTJed45HtGRAUp3z2Mep1caNCuaNBzeiE8OfWfuXmip+ujgp3vWZHxRqag221aRSnKbA6gDaQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "zod": "^3.24.2" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "node_modules/@x402/evm": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@x402/evm/-/evm-2.7.0.tgz", + "integrity": "sha512-085gqcu1ljaomMaitGx2KeQmHVe6ZyaFTT1UFZjhmHdBlNe+jNb+Moa4O1JTMyVNbbNCAZ2M1Do+LqVnAK4OIA==", "dev": true, - "optional": true, - "peer": true, + "license": "Apache-2.0", "dependencies": { - "acorn": "^8.11.0" + "@x402/core": "~2.7.0", + "viem": "^2.39.3", + "zod": "^3.24.2" + } + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" }, - "engines": { - "node": ">=0.4.0" + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } } }, "node_modules/agent-base": { @@ -2785,14 +2849,6 @@ "node": ">= 8" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3462,14 +3518,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3593,17 +3641,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -3920,6 +3957,13 @@ "node": ">=4" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -4233,6 +4277,21 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4919,6 +4978,22 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/issue-parser": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", @@ -6501,6 +6576,7 @@ }, "node_modules/npm/node_modules/@isaacs/cliui": { "version": "8.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6517,6 +6593,7 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { "version": "6.1.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -6528,11 +6605,13 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -6549,6 +6628,7 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { "version": "7.1.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -6563,6 +6643,7 @@ }, "node_modules/npm/node_modules/@isaacs/fs-minipass": { "version": "4.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6574,11 +6655,13 @@ }, "node_modules/npm/node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/agent": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6594,6 +6677,7 @@ }, "node_modules/npm/node_modules/@npmcli/arborist": { "version": "8.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6642,6 +6726,7 @@ }, "node_modules/npm/node_modules/@npmcli/config": { "version": "9.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6660,6 +6745,7 @@ }, "node_modules/npm/node_modules/@npmcli/fs": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6671,6 +6757,7 @@ }, "node_modules/npm/node_modules/@npmcli/git": { "version": "6.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6690,6 +6777,7 @@ }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6705,6 +6793,7 @@ }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { "version": "4.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6719,6 +6808,7 @@ }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { "version": "8.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6734,6 +6824,7 @@ }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { "version": "20.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6764,6 +6855,7 @@ }, "node_modules/npm/node_modules/@npmcli/name-from-folder": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -6772,6 +6864,7 @@ }, "node_modules/npm/node_modules/@npmcli/node-gyp": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -6780,6 +6873,7 @@ }, "node_modules/npm/node_modules/@npmcli/package-json": { "version": "6.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6797,6 +6891,7 @@ }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { "version": "8.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6808,6 +6903,7 @@ }, "node_modules/npm/node_modules/@npmcli/query": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6819,6 +6915,7 @@ }, "node_modules/npm/node_modules/@npmcli/redact": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -6827,6 +6924,7 @@ }, "node_modules/npm/node_modules/@npmcli/run-script": { "version": "9.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6843,6 +6941,7 @@ }, "node_modules/npm/node_modules/@pkgjs/parseargs": { "version": "0.11.0", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -6852,6 +6951,7 @@ }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { "version": "0.3.2", + "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { @@ -6860,6 +6960,7 @@ }, "node_modules/npm/node_modules/@sigstore/tuf": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -6872,6 +6973,7 @@ }, "node_modules/npm/node_modules/@tufjs/canonical-json": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -6880,6 +6982,7 @@ }, "node_modules/npm/node_modules/abbrev": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -6888,6 +6991,7 @@ }, "node_modules/npm/node_modules/agent-base": { "version": "7.1.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -6899,6 +7003,7 @@ }, "node_modules/npm/node_modules/aggregate-error": { "version": "3.1.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -6911,6 +7016,7 @@ }, "node_modules/npm/node_modules/ansi-regex": { "version": "5.0.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -6919,6 +7025,7 @@ }, "node_modules/npm/node_modules/ansi-styles": { "version": "6.2.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -6930,21 +7037,25 @@ }, "node_modules/npm/node_modules/aproba": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/archy": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/balanced-match": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/bin-links": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -6960,6 +7071,7 @@ }, "node_modules/npm/node_modules/binary-extensions": { "version": "2.3.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -6971,6 +7083,7 @@ }, "node_modules/npm/node_modules/brace-expansion": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -6979,6 +7092,7 @@ }, "node_modules/npm/node_modules/cacache": { "version": "19.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7001,6 +7115,7 @@ }, "node_modules/npm/node_modules/cacache/node_modules/chownr": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "engines": { @@ -7009,6 +7124,7 @@ }, "node_modules/npm/node_modules/cacache/node_modules/minizlib": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7021,6 +7137,7 @@ }, "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -7035,6 +7152,7 @@ }, "node_modules/npm/node_modules/cacache/node_modules/p-map": { "version": "7.0.2", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7046,6 +7164,7 @@ }, "node_modules/npm/node_modules/cacache/node_modules/tar": { "version": "7.4.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7062,6 +7181,7 @@ }, "node_modules/npm/node_modules/cacache/node_modules/yallist": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "engines": { @@ -7070,6 +7190,7 @@ }, "node_modules/npm/node_modules/chalk": { "version": "5.3.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7081,6 +7202,7 @@ }, "node_modules/npm/node_modules/chownr": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -7089,6 +7211,7 @@ }, "node_modules/npm/node_modules/ci-info": { "version": "4.1.0", + "dev": true, "funding": [ { "type": "github", @@ -7103,6 +7226,7 @@ }, "node_modules/npm/node_modules/cidr-regex": { "version": "4.1.1", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -7114,6 +7238,7 @@ }, "node_modules/npm/node_modules/clean-stack": { "version": "2.2.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7122,6 +7247,7 @@ }, "node_modules/npm/node_modules/cli-columns": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7134,6 +7260,7 @@ }, "node_modules/npm/node_modules/cmd-shim": { "version": "7.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -7142,6 +7269,7 @@ }, "node_modules/npm/node_modules/color-convert": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7153,16 +7281,19 @@ }, "node_modules/npm/node_modules/color-name": { "version": "1.1.4", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/common-ancestor-path": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/cross-spawn": { "version": "7.0.6", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7176,6 +7307,7 @@ }, "node_modules/npm/node_modules/cross-spawn/node_modules/which": { "version": "2.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7190,6 +7322,7 @@ }, "node_modules/npm/node_modules/cssesc": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -7201,6 +7334,7 @@ }, "node_modules/npm/node_modules/debug": { "version": "4.3.7", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7217,6 +7351,7 @@ }, "node_modules/npm/node_modules/diff": { "version": "5.2.0", + "dev": true, "inBundle": true, "license": "BSD-3-Clause", "engines": { @@ -7225,16 +7360,19 @@ }, "node_modules/npm/node_modules/eastasianwidth": { "version": "0.2.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/emoji-regex": { "version": "8.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/encoding": { "version": "0.1.13", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -7244,6 +7382,7 @@ }, "node_modules/npm/node_modules/env-paths": { "version": "2.2.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7252,16 +7391,19 @@ }, "node_modules/npm/node_modules/err-code": { "version": "2.0.3", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/exponential-backoff": { "version": "3.1.1", + "dev": true, "inBundle": true, "license": "Apache-2.0" }, "node_modules/npm/node_modules/fastest-levenshtein": { "version": "1.0.16", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7270,6 +7412,7 @@ }, "node_modules/npm/node_modules/foreground-child": { "version": "3.3.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7285,6 +7428,7 @@ }, "node_modules/npm/node_modules/fs-minipass": { "version": "3.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7296,6 +7440,7 @@ }, "node_modules/npm/node_modules/glob": { "version": "10.4.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7315,11 +7460,13 @@ }, "node_modules/npm/node_modules/graceful-fs": { "version": "4.2.11", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/hosted-git-info": { "version": "8.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7331,11 +7478,13 @@ }, "node_modules/npm/node_modules/http-cache-semantics": { "version": "4.1.1", + "dev": true, "inBundle": true, "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { "version": "7.0.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7348,6 +7497,7 @@ }, "node_modules/npm/node_modules/https-proxy-agent": { "version": "7.0.5", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7360,6 +7510,7 @@ }, "node_modules/npm/node_modules/iconv-lite": { "version": "0.6.3", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -7372,6 +7523,7 @@ }, "node_modules/npm/node_modules/ignore-walk": { "version": "7.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7383,6 +7535,7 @@ }, "node_modules/npm/node_modules/imurmurhash": { "version": "0.1.4", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7391,6 +7544,7 @@ }, "node_modules/npm/node_modules/indent-string": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7399,6 +7553,7 @@ }, "node_modules/npm/node_modules/ini": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -7407,6 +7562,7 @@ }, "node_modules/npm/node_modules/init-package-json": { "version": "7.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7424,6 +7580,7 @@ }, "node_modules/npm/node_modules/ip-address": { "version": "9.0.5", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7436,6 +7593,7 @@ }, "node_modules/npm/node_modules/ip-regex": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7447,6 +7605,7 @@ }, "node_modules/npm/node_modules/is-cidr": { "version": "5.1.0", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -7458,6 +7617,7 @@ }, "node_modules/npm/node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7466,11 +7626,13 @@ }, "node_modules/npm/node_modules/isexe": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/jackspeak": { "version": "3.4.3", + "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -7485,11 +7647,13 @@ }, "node_modules/npm/node_modules/jsbn": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/json-parse-even-better-errors": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7498,6 +7662,7 @@ }, "node_modules/npm/node_modules/json-stringify-nice": { "version": "1.1.4", + "dev": true, "inBundle": true, "license": "ISC", "funding": { @@ -7506,6 +7671,7 @@ }, "node_modules/npm/node_modules/jsonparse": { "version": "1.3.1", + "dev": true, "engines": [ "node >= 0.2.0" ], @@ -7514,16 +7680,19 @@ }, "node_modules/npm/node_modules/just-diff": { "version": "6.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/just-diff-apply": { "version": "5.5.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/libnpmaccess": { "version": "9.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7536,6 +7705,7 @@ }, "node_modules/npm/node_modules/libnpmdiff": { "version": "7.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7554,6 +7724,7 @@ }, "node_modules/npm/node_modules/libnpmexec": { "version": "9.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7574,6 +7745,7 @@ }, "node_modules/npm/node_modules/libnpmfund": { "version": "6.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7585,6 +7757,7 @@ }, "node_modules/npm/node_modules/libnpmhook": { "version": "11.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7597,6 +7770,7 @@ }, "node_modules/npm/node_modules/libnpmorg": { "version": "7.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7609,6 +7783,7 @@ }, "node_modules/npm/node_modules/libnpmpack": { "version": "8.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7623,6 +7798,7 @@ }, "node_modules/npm/node_modules/libnpmpublish": { "version": "10.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7641,6 +7817,7 @@ }, "node_modules/npm/node_modules/libnpmsearch": { "version": "8.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7652,6 +7829,7 @@ }, "node_modules/npm/node_modules/libnpmteam": { "version": "7.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7664,6 +7842,7 @@ }, "node_modules/npm/node_modules/libnpmversion": { "version": "7.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7679,11 +7858,13 @@ }, "node_modules/npm/node_modules/lru-cache": { "version": "10.4.3", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/make-fetch-happen": { "version": "14.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7705,6 +7886,7 @@ }, "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7713,6 +7895,7 @@ }, "node_modules/npm/node_modules/minimatch": { "version": "9.0.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7727,6 +7910,7 @@ }, "node_modules/npm/node_modules/minipass": { "version": "7.1.2", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -7735,6 +7919,7 @@ }, "node_modules/npm/node_modules/minipass-collect": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7746,6 +7931,7 @@ }, "node_modules/npm/node_modules/minipass-fetch": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7762,6 +7948,7 @@ }, "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7774,6 +7961,7 @@ }, "node_modules/npm/node_modules/minipass-flush": { "version": "1.0.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7785,6 +7973,7 @@ }, "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { "version": "3.3.6", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7796,6 +7985,7 @@ }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7807,6 +7997,7 @@ }, "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { "version": "3.3.6", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7818,6 +8009,7 @@ }, "node_modules/npm/node_modules/minipass-sized": { "version": "1.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7829,6 +8021,7 @@ }, "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { "version": "3.3.6", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7840,6 +8033,7 @@ }, "node_modules/npm/node_modules/minizlib": { "version": "2.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7852,6 +8046,7 @@ }, "node_modules/npm/node_modules/minizlib/node_modules/minipass": { "version": "3.3.6", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7863,6 +8058,7 @@ }, "node_modules/npm/node_modules/mkdirp": { "version": "1.0.4", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -7874,11 +8070,13 @@ }, "node_modules/npm/node_modules/ms": { "version": "2.1.3", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/mute-stream": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -7887,6 +8085,7 @@ }, "node_modules/npm/node_modules/node-gyp": { "version": "11.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7910,6 +8109,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "engines": { @@ -7918,6 +8118,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/minizlib": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7930,6 +8131,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -7944,6 +8146,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/tar": { "version": "7.4.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7960,6 +8163,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "engines": { @@ -7968,6 +8172,7 @@ }, "node_modules/npm/node_modules/nopt": { "version": "8.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7982,6 +8187,7 @@ }, "node_modules/npm/node_modules/nopt/node_modules/abbrev": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -7990,6 +8196,7 @@ }, "node_modules/npm/node_modules/normalize-package-data": { "version": "7.0.0", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -8003,6 +8210,7 @@ }, "node_modules/npm/node_modules/npm-audit-report": { "version": "6.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8011,6 +8219,7 @@ }, "node_modules/npm/node_modules/npm-bundled": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8022,6 +8231,7 @@ }, "node_modules/npm/node_modules/npm-install-checks": { "version": "7.1.1", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -8033,6 +8243,7 @@ }, "node_modules/npm/node_modules/npm-normalize-package-bin": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8041,6 +8252,7 @@ }, "node_modules/npm/node_modules/npm-package-arg": { "version": "12.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8055,6 +8267,7 @@ }, "node_modules/npm/node_modules/npm-packlist": { "version": "9.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8066,6 +8279,7 @@ }, "node_modules/npm/node_modules/npm-pick-manifest": { "version": "10.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8080,6 +8294,7 @@ }, "node_modules/npm/node_modules/npm-profile": { "version": "11.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8092,6 +8307,7 @@ }, "node_modules/npm/node_modules/npm-registry-fetch": { "version": "18.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8110,6 +8326,7 @@ }, "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8122,6 +8339,7 @@ }, "node_modules/npm/node_modules/npm-user-validate": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "engines": { @@ -8130,6 +8348,7 @@ }, "node_modules/npm/node_modules/p-map": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8144,11 +8363,13 @@ }, "node_modules/npm/node_modules/package-json-from-dist": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "BlueOak-1.0.0" }, "node_modules/npm/node_modules/pacote": { "version": "19.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8179,6 +8400,7 @@ }, "node_modules/npm/node_modules/parse-conflict-json": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8192,6 +8414,7 @@ }, "node_modules/npm/node_modules/path-key": { "version": "3.1.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8200,6 +8423,7 @@ }, "node_modules/npm/node_modules/path-scurry": { "version": "1.11.1", + "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -8215,6 +8439,7 @@ }, "node_modules/npm/node_modules/postcss-selector-parser": { "version": "6.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8227,6 +8452,7 @@ }, "node_modules/npm/node_modules/proc-log": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8235,6 +8461,7 @@ }, "node_modules/npm/node_modules/proggy": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8243,6 +8470,7 @@ }, "node_modules/npm/node_modules/promise-all-reject-late": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC", "funding": { @@ -8251,6 +8479,7 @@ }, "node_modules/npm/node_modules/promise-call-limit": { "version": "3.0.2", + "dev": true, "inBundle": true, "license": "ISC", "funding": { @@ -8259,11 +8488,13 @@ }, "node_modules/npm/node_modules/promise-inflight": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/promise-retry": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8276,6 +8507,7 @@ }, "node_modules/npm/node_modules/promzard": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8287,6 +8519,7 @@ }, "node_modules/npm/node_modules/qrcode-terminal": { "version": "0.12.0", + "dev": true, "inBundle": true, "bin": { "qrcode-terminal": "bin/qrcode-terminal.js" @@ -8294,6 +8527,7 @@ }, "node_modules/npm/node_modules/read": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8305,6 +8539,7 @@ }, "node_modules/npm/node_modules/read-cmd-shim": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8313,6 +8548,7 @@ }, "node_modules/npm/node_modules/read-package-json-fast": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8325,6 +8561,7 @@ }, "node_modules/npm/node_modules/retry": { "version": "0.12.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8333,6 +8570,7 @@ }, "node_modules/npm/node_modules/rimraf": { "version": "5.0.10", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8347,12 +8585,14 @@ }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", + "dev": true, "inBundle": true, "license": "MIT", "optional": true }, "node_modules/npm/node_modules/semver": { "version": "7.6.3", + "dev": true, "inBundle": true, "license": "ISC", "bin": { @@ -8364,6 +8604,7 @@ }, "node_modules/npm/node_modules/shebang-command": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8375,6 +8616,7 @@ }, "node_modules/npm/node_modules/shebang-regex": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8383,6 +8625,7 @@ }, "node_modules/npm/node_modules/signal-exit": { "version": "4.1.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8394,6 +8637,7 @@ }, "node_modules/npm/node_modules/sigstore": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -8410,6 +8654,7 @@ }, "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -8421,6 +8666,7 @@ }, "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { @@ -8429,6 +8675,7 @@ }, "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -8445,6 +8692,7 @@ }, "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -8458,6 +8706,7 @@ }, "node_modules/npm/node_modules/smart-buffer": { "version": "4.2.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8467,6 +8716,7 @@ }, "node_modules/npm/node_modules/socks": { "version": "2.8.3", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8480,6 +8730,7 @@ }, "node_modules/npm/node_modules/socks-proxy-agent": { "version": "8.0.4", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8493,6 +8744,7 @@ }, "node_modules/npm/node_modules/spdx-correct": { "version": "3.2.0", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -8502,6 +8754,7 @@ }, "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8511,11 +8764,13 @@ }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.5.0", + "dev": true, "inBundle": true, "license": "CC-BY-3.0" }, "node_modules/npm/node_modules/spdx-expression-parse": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8525,16 +8780,19 @@ }, "node_modules/npm/node_modules/spdx-license-ids": { "version": "3.0.20", + "dev": true, "inBundle": true, "license": "CC0-1.0" }, "node_modules/npm/node_modules/sprintf-js": { "version": "1.1.3", + "dev": true, "inBundle": true, "license": "BSD-3-Clause" }, "node_modules/npm/node_modules/ssri": { "version": "12.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8546,6 +8804,7 @@ }, "node_modules/npm/node_modules/string-width": { "version": "4.2.3", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8560,6 +8819,7 @@ "node_modules/npm/node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8573,6 +8833,7 @@ }, "node_modules/npm/node_modules/strip-ansi": { "version": "6.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8585,6 +8846,7 @@ "node_modules/npm/node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8596,6 +8858,7 @@ }, "node_modules/npm/node_modules/supports-color": { "version": "9.4.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8607,6 +8870,7 @@ }, "node_modules/npm/node_modules/tar": { "version": "6.2.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8623,6 +8887,7 @@ }, "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { "version": "2.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8634,6 +8899,7 @@ }, "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { "version": "3.3.6", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8645,6 +8911,7 @@ }, "node_modules/npm/node_modules/tar/node_modules/minipass": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8653,16 +8920,19 @@ }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/tiny-relative-date": { "version": "1.3.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/treeverse": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8671,6 +8941,7 @@ }, "node_modules/npm/node_modules/tuf-js": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8684,6 +8955,7 @@ }, "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8696,6 +8968,7 @@ }, "node_modules/npm/node_modules/unique-filename": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8707,6 +8980,7 @@ }, "node_modules/npm/node_modules/unique-slug": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8718,11 +8992,13 @@ }, "node_modules/npm/node_modules/util-deprecate": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/validate-npm-package-license": { "version": "3.0.4", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -8732,6 +9008,7 @@ }, "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8741,6 +9018,7 @@ }, "node_modules/npm/node_modules/validate-npm-package-name": { "version": "6.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8749,11 +9027,13 @@ }, "node_modules/npm/node_modules/walk-up-path": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/which": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8768,6 +9048,7 @@ }, "node_modules/npm/node_modules/which/node_modules/isexe": { "version": "3.1.1", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8776,6 +9057,7 @@ }, "node_modules/npm/node_modules/wrap-ansi": { "version": "8.1.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8793,6 +9075,7 @@ "node_modules/npm/node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8809,6 +9092,7 @@ }, "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8823,6 +9107,7 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.1.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8834,11 +9119,13 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { "version": "9.2.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { "version": "5.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8855,6 +9142,7 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "7.1.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8869,6 +9157,7 @@ }, "node_modules/npm/node_modules/write-file-atomic": { "version": "6.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8881,6 +9170,7 @@ }, "node_modules/npm/node_modules/yallist": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, @@ -8918,6 +9208,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ox": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.5.tgz", + "integrity": "sha512-HgmHmBveYO40H/R3K6TMrwYtHsx/u6TAB+GpZlgJCoW0Sq5Ttpjih0IZZiwGQw7T6vdW4IAyobYrE2mdAvyF8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/oxc-resolver": { "version": "11.2.1", "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.2.1.tgz", @@ -10615,51 +10936,6 @@ } } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -10827,14 +11103,6 @@ "dev": true, "license": "MIT" }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -10860,6 +11128,37 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/viem": { + "version": "2.47.4", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.47.4.tgz", + "integrity": "sha512-h0Wp/SYmJO/HB4B/em1OZ3W1LaKrmr7jzaN7talSlZpo0LCn0V6rZ5g923j6sf4VUSrqp/gUuWuHFc7UcoIp8A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.14.5", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/walk-up-path": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", @@ -10937,6 +11236,28 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -10989,17 +11310,6 @@ "node": ">=12" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index f1a4fc3..617516d 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,16 @@ "version": "0.0.0", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "default": "./dist/src/index.js" + }, + "./x402": { + "types": "./dist/src/x402/index.d.ts", + "default": "./dist/src/x402/index.js" + } + }, "scripts": { "build": "tsc", "commitlint": "commitlint --edit", @@ -43,18 +53,37 @@ "@commitlint/config-conventional": "^19.8.1", "@types/jest": "^29.5.14", "@types/node": "^24.0.4", + "@x402/core":"^2.6.0", + "@x402/evm": "^2.7.0", "husky": "^9.1.7", "jest": "^29.7.0", "knip": "^5.61.2", "semantic-release": "^24.2.5", "ts-jest": "^29.2.5", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "viem": "^2.47.4" }, "dependencies": { "axios": "^1.8.2", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.1" }, + "peerDependencies": { + "@x402/core": "^2.6.0", + "@x402/evm": "^2.6.0", + "viem": "^2.0.0" + }, + "peerDependenciesMeta": { + "@x402/core": { + "optional": true + }, + "@x402/evm": { + "optional": true + }, + "viem": { + "optional": true + } + }, "release": { "branches": [ "main" @@ -74,9 +103,18 @@ ] ] }, + "knip": { + "entry": [ + "src/index.ts", + "src/x402/index.ts" + ], + "exclude": [ + "optionalPeerDependencies" + ] + }, "commitlint": { "extends": [ "@commitlint/config-conventional" ] } -} \ No newline at end of file +} diff --git a/src/__tests__/linkup-client.test.ts b/src/__tests__/linkup-client.test.ts index 618f7d0..6bb774d 100644 --- a/src/__tests__/linkup-client.test.ts +++ b/src/__tests__/linkup-client.test.ts @@ -8,14 +8,21 @@ import { LinkupInsufficientCreditError, LinkupInvalidRequestError, LinkupNoResultError, + LinkupPaymentRequiredError, LinkupTooManyRequestsError, LinkupUnknownError, } from '../errors'; import { LinkupClient } from '../linkup-client'; import type { ImageSearchResult, SearchParams, Source, TextSearchResult } from '../types'; import { refineError } from '../utils/refine-error.utils'; +import type { X402Signer } from '../x402/types'; jest.mock('axios'); + +jest.mock('@x402/core/http', () => ({ + decodePaymentRequiredHeader: jest.fn((header: string) => ({ decoded: header })), + encodePaymentSignatureHeader: jest.fn((_payload: unknown) => 'signed-payment'), +})); const maxios = axios as jest.Mocked; const mockAxiosInstance = { @@ -25,6 +32,7 @@ const mockAxiosInstance = { }, }, post: jest.fn(), + request: jest.fn(), // biome-ignore lint/suspicious/noExplicitAny: testing purpose } as any; @@ -423,6 +431,15 @@ describe('LinkupClient', () => { statusCode: 400, }, }, + { + description: '402 PAYMENT_REQUIRED', + ErrorClass: LinkupPaymentRequiredError, + expectedMessage: 'Payment required', + input: { + error: { code: 'PAYMENT_REQUIRED', details: [], message: 'Payment required' }, + statusCode: 402, + }, + }, { description: '401', ErrorClass: LinkupAuthenticationError, @@ -499,4 +516,173 @@ describe('LinkupClient', () => { expect(error.message).toContain('An unknown error occurred'); }); }); + + describe('x402 mode', () => { + const mockSigner: X402Signer = { + createPaymentPayload: jest.fn().mockResolvedValue({ signed: true }), + }; + + const { decodePaymentRequiredHeader, encodePaymentSignatureHeader } = + // biome-ignore lint/suspicious/noExplicitAny: testing purpose + jest.requireMock('@x402/core/http') as any; + + const createX402Client = () => { + maxios.create = jest.fn(() => mockAxiosInstance); + mockAxiosInstance.interceptors.response.use.mockClear(); + mockAxiosInstance.request.mockClear(); + (mockSigner.createPaymentPayload as jest.Mock).mockClear(); + (decodePaymentRequiredHeader as jest.Mock).mockClear(); + (encodePaymentSignatureHeader as jest.Mock).mockClear(); + return new LinkupClient({ signer: mockSigner }); + }; + + const getX402ErrorHandler = () => { + // The first call to interceptors.response.use is from setupX402Interceptor + return mockAxiosInstance.interceptors.response.use.mock.calls[0][1]; + }; + + it('should not set Authorization header in x402 mode', () => { + createX402Client(); + + expect(maxios.create).toHaveBeenCalledWith({ + baseURL: 'https://api.linkup.so/v1', + headers: { + 'User-Agent': expect.stringContaining('Linkup-JS-SDK/'), + }, + }); + }); + + it('should register two response interceptors in x402 mode', () => { + createX402Client(); + + expect(mockAxiosInstance.interceptors.response.use).toHaveBeenCalledTimes(2); + }); + + it('should handle 402 → sign → retry → success', async () => { + createX402Client(); + const x402ErrorHandler = getX402ErrorHandler(); + + const error402 = { + config: { + headers: { 'User-Agent': 'Linkup-JS-SDK/0.0.0' }, + method: 'post', + url: '/search', + }, + response: { + headers: { 'payment-required': 'encoded-payment-data' }, + status: 402, + }, + }; + + const successResponse = { data: { answer: 'ok' } }; + mockAxiosInstance.request.mockResolvedValueOnce(successResponse); + + const result = await x402ErrorHandler(error402); + + expect(decodePaymentRequiredHeader).toHaveBeenCalledWith('encoded-payment-data'); + expect(mockSigner.createPaymentPayload).toHaveBeenCalledWith({ + decoded: 'encoded-payment-data', + }); + expect(encodePaymentSignatureHeader).toHaveBeenCalledWith({ signed: true }); + expect(mockAxiosInstance.request).toHaveBeenCalledWith( + expect.objectContaining({ + _x402Retried: true, + headers: expect.objectContaining({ + 'payment-signature': 'signed-payment', + }), + }), + ); + expect(result).toEqual(successResponse); + }); + + it('should reject with retry error when retried request fails', async () => { + createX402Client(); + const x402ErrorHandler = getX402ErrorHandler(); + + const error402 = { + config: { + headers: {}, + method: 'post', + url: '/search', + }, + response: { + headers: { 'payment-required': 'encoded-payment-data' }, + status: 402, + }, + }; + + const retryError = new Error('retry failed'); + mockAxiosInstance.request.mockRejectedValueOnce(retryError); + + await expect(x402ErrorHandler(error402)).rejects.toBe(retryError); + }); + + it('should throw LinkupPaymentRequiredError when payment-required header is missing', async () => { + createX402Client(); + const x402ErrorHandler = getX402ErrorHandler(); + + const error402NoHeader = { + config: { + headers: {}, + }, + response: { + headers: {}, + status: 402, + }, + }; + + await expect(x402ErrorHandler(error402NoHeader)).rejects.toBeInstanceOf( + LinkupPaymentRequiredError, + ); + expect(decodePaymentRequiredHeader).not.toHaveBeenCalled(); + }); + + it('should not retry when _x402Retried is already true', async () => { + createX402Client(); + const x402ErrorHandler = getX402ErrorHandler(); + + const retriedError = { + config: { + _x402Retried: true, + headers: {}, + }, + response: { + headers: { 'payment-required': 'encoded-payment-data' }, + status: 402, + }, + }; + + await expect(x402ErrorHandler(retriedError)).rejects.toBe(retriedError); + expect(mockAxiosInstance.request).not.toHaveBeenCalled(); + }); + + it('should pass through non-402 errors', async () => { + createX402Client(); + const x402ErrorHandler = getX402ErrorHandler(); + + const error500 = { + config: { + headers: {}, + }, + response: { + status: 500, + }, + }; + + await expect(x402ErrorHandler(error500)).rejects.toBe(error500); + expect(mockAxiosInstance.request).not.toHaveBeenCalled(); + expect(decodePaymentRequiredHeader).not.toHaveBeenCalled(); + }); + + it('should pass through errors without response (network error)', async () => { + createX402Client(); + const x402ErrorHandler = getX402ErrorHandler(); + + const networkError = { code: 'ECONNREFUSED', message: 'connect ECONNREFUSED' }; + + await expect(x402ErrorHandler(networkError)).rejects.toBe(networkError); + expect(mockAxiosInstance.request).not.toHaveBeenCalled(); + expect(decodePaymentRequiredHeader).not.toHaveBeenCalled(); + }); + }); }); diff --git a/src/errors.ts b/src/errors.ts index e589019..4b8ef40 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -108,6 +108,18 @@ export class LinkupFetchResponseTooLargeError extends LinkupError { } } +// Payment required error, raised when x402 payment signing fails or the 402 persists after retry. +export class LinkupPaymentRequiredError extends LinkupError { + constructor(message?: string) { + super(message); + this.name = LinkupPaymentRequiredError.name; + + if ('captureStackTrace' in Error) { + Error.captureStackTrace(this, LinkupPaymentRequiredError); + } + } +} + export class FetchUrlIsFileError extends LinkupError { constructor(message?: string) { super(message); diff --git a/src/linkup-client.ts b/src/linkup-client.ts index 8da7800..120f66f 100644 --- a/src/linkup-client.ts +++ b/src/linkup-client.ts @@ -2,6 +2,7 @@ import axios, { AxiosInstance } from 'axios'; import { ZodObject, ZodRawShape } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; import { version } from '../package.json'; +import { LinkupPaymentRequiredError } from './errors'; import { ApiConfig, FetchParams, @@ -11,6 +12,7 @@ import { } from './types'; import { refineError } from './utils/refine-error.utils'; import { isZodObject } from './utils/schema.utils'; +import type { X402Signer } from './x402/types'; export class LinkupClient { private readonly USER_AGENT = `Linkup-JS-SDK/${version}`; @@ -18,14 +20,16 @@ export class LinkupClient { constructor(config: ApiConfig) { const baseURL = config.baseUrl || 'https://api.linkup.so/v1'; + const headers = { + 'User-Agent': this.USER_AGENT, + ...('apiKey' in config ? { Authorization: `Bearer ${config.apiKey}` } : {}), + }; - this.client = axios.create({ - baseURL, - headers: { - Authorization: `Bearer ${config.apiKey}`, - 'User-Agent': this.USER_AGENT, - }, - }); + this.client = axios.create({ baseURL, headers }); + + if ('signer' in config) { + this.setupX402Interceptor(config.signer); + } this.client.interceptors.response.use( response => response, @@ -46,6 +50,47 @@ export class LinkupClient { return this.client.post('/fetch', params).then(response => response.data); } + private setupX402Interceptor(signer: X402Signer): void { + this.client.interceptors.response.use( + response => response, + async error => { + if (!error.response || error.response.status !== 402 || error.config?._x402Retried) { + return Promise.reject(error); + } + + const paymentRequiredHeader = error.response.headers['payment-required']; + + if (!paymentRequiredHeader) { + return Promise.reject( + new LinkupPaymentRequiredError('Received 402 but no payment-required header'), + ); + } + + try { + const { decodePaymentRequiredHeader, encodePaymentSignatureHeader } = await import( + '@x402/core/http' + ); + + const paymentRequired = decodePaymentRequiredHeader(paymentRequiredHeader); + const paymentPayload = await signer.createPaymentPayload(paymentRequired); + const paymentSignature = encodePaymentSignatureHeader(paymentPayload); + + const originalRequest = error.config; + originalRequest.headers['payment-signature'] = paymentSignature; + originalRequest._x402Retried = true; + + return this.client.request(originalRequest); + } catch (error) { + return Promise.reject( + new LinkupPaymentRequiredError( + `X402 payment failed: ${error instanceof Error ? error.message : error}`, + ), + ); + } + }, + ); + } + private sanitizeParams( params: T, ): Record { diff --git a/src/types.ts b/src/types.ts index 1189216..134bcb0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,12 +1,14 @@ /** biome-ignore-all lint/complexity/noBannedTypes: needed for conditional props */ import { ZodObject, ZodRawShape } from 'zod'; +import type { X402Signer } from './x402/types'; export type SearchDepth = 'standard' | 'deep'; -export type ApiConfig = { - apiKey: string; - baseUrl?: string; -}; +export type ApiKeyConfig = { apiKey: string; baseUrl?: string }; + +export type X402Config = { signer: X402Signer; baseUrl?: string }; + +export type ApiConfig = ApiKeyConfig | X402Config; export type Structured = Record; diff --git a/src/utils/refine-error.utils.ts b/src/utils/refine-error.utils.ts index d33a592..fb0ee07 100644 --- a/src/utils/refine-error.utils.ts +++ b/src/utils/refine-error.utils.ts @@ -7,6 +7,7 @@ import { LinkupInsufficientCreditError, LinkupInvalidRequestError, LinkupNoResultError, + LinkupPaymentRequiredError, LinkupTooManyRequestsError, LinkupUnknownError, } from '../errors'; @@ -45,6 +46,8 @@ export const refineError = (e: LinkupApiError): LinkupError => { default: return new LinkupInvalidRequestError(concatErrorAndDetails(e)); } + case 402: + return new LinkupPaymentRequiredError(message); case 401: case 403: return new LinkupAuthenticationError(message); diff --git a/src/x402/__tests__/create-x402-signer.test.ts b/src/x402/__tests__/create-x402-signer.test.ts new file mode 100644 index 0000000..c3b3404 --- /dev/null +++ b/src/x402/__tests__/create-x402-signer.test.ts @@ -0,0 +1,87 @@ +import { ExactEvmScheme, toClientEvmSigner } from '@x402/evm'; +import { createPublicClient, http, type LocalAccount } from 'viem'; +import { createX402Signer } from '../create-x402-signer'; + +const mockAccount = { address: '0xmockAccount' }; +const mockPublicClient = { chain: { id: 8453 } }; +const mockHttpTransport = { type: 'http' }; +const mockEvmSigner = { sign: 'mockEvmSigner' }; +const mockExactEvmScheme = { scheme: 'mockExactEvmScheme' }; +const mockClientInstance = { + createPaymentPayload: jest.fn(), + register: jest.fn(), +}; +mockClientInstance.register.mockReturnValue(mockClientInstance); + +jest.mock('viem', () => ({ + createPublicClient: jest.fn(() => mockPublicClient), + http: jest.fn(() => mockHttpTransport), +})); + +jest.mock('viem/chains', () => ({ + base: { id: 8453, name: 'Base' }, +})); + +jest.mock('@x402/evm', () => ({ + ExactEvmScheme: jest.fn(() => mockExactEvmScheme), + toClientEvmSigner: jest.fn(() => mockEvmSigner), +})); + +jest.mock('@x402/core/client', () => ({ + x402Client: jest.fn(() => mockClientInstance), +})); + +describe('createX402Signer', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockClientInstance.register.mockReturnValue(mockClientInstance); + }); + + it('should create a public client with Base chain and http transport', () => { + createX402Signer(mockAccount as unknown as LocalAccount); + + expect(http).toHaveBeenCalled(); + expect(createPublicClient).toHaveBeenCalledWith({ + chain: { id: 8453, name: 'Base' }, + transport: mockHttpTransport, + }); + }); + + it('should create an EVM signer', () => { + createX402Signer(mockAccount as unknown as LocalAccount); + + expect(toClientEvmSigner).toHaveBeenCalledWith(mockAccount, mockPublicClient); + }); + + it('should create an ExactEvmScheme', () => { + createX402Signer(mockAccount as unknown as LocalAccount); + + expect(ExactEvmScheme).toHaveBeenCalledWith(mockEvmSigner); + }); + + it('should register with the correct chain ID', () => { + createX402Signer(mockAccount as unknown as LocalAccount); + + expect(mockClientInstance.register).toHaveBeenCalledWith('eip155:8453', mockExactEvmScheme); + }); + + it('should delegate createPaymentPayload to x402Client', async () => { + const mockPayload = { signed: true }; + mockClientInstance.createPaymentPayload.mockResolvedValueOnce(mockPayload); + + const signer = createX402Signer(mockAccount as unknown as LocalAccount); + const result = await signer.createPaymentPayload({ amount: 100 }); + + expect(mockClientInstance.createPaymentPayload).toHaveBeenCalledWith({ amount: 100 }); + expect(result).toEqual(mockPayload); + }); + + it('should propagate errors from createPaymentPayload', async () => { + const error = new Error('payment failed'); + mockClientInstance.createPaymentPayload.mockRejectedValueOnce(error); + + const signer = createX402Signer(mockAccount as unknown as LocalAccount); + + await expect(signer.createPaymentPayload({ amount: 100 })).rejects.toThrow('payment failed'); + }); +}); diff --git a/src/x402/create-x402-signer.ts b/src/x402/create-x402-signer.ts new file mode 100644 index 0000000..f48170e --- /dev/null +++ b/src/x402/create-x402-signer.ts @@ -0,0 +1,17 @@ +import { x402Client } from '@x402/core/client'; +import { ExactEvmScheme, toClientEvmSigner } from '@x402/evm'; +import { createPublicClient, http, type LocalAccount } from 'viem'; +import { base } from 'viem/chains'; +import type { X402Signer } from './types'; + +const BASE_CHAIN_ID = 'eip155:8453'; + +export function createX402Signer(account: LocalAccount): X402Signer { + const publicClient = createPublicClient({ chain: base, transport: http() }); + const signer = toClientEvmSigner(account, publicClient); + const client = new x402Client().register(BASE_CHAIN_ID, new ExactEvmScheme(signer)); + + return { + createPaymentPayload: paymentRequired => client.createPaymentPayload(paymentRequired), + }; +} diff --git a/src/x402/index.ts b/src/x402/index.ts new file mode 100644 index 0000000..f4aec11 --- /dev/null +++ b/src/x402/index.ts @@ -0,0 +1,2 @@ +export { createX402Signer } from './create-x402-signer'; +export type { X402Signer } from './types'; diff --git a/src/x402/types.ts b/src/x402/types.ts new file mode 100644 index 0000000..fe25cbe --- /dev/null +++ b/src/x402/types.ts @@ -0,0 +1,3 @@ +export interface X402Signer { + createPaymentPayload(paymentRequired: unknown): Promise; +} diff --git a/tsconfig.json b/tsconfig.json index 151d131..038cf37 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,6 @@ "sourceMap": true, "resolveJsonModule": true }, - "include": ["src"], + "include": ["src", "x402-core.d.ts"], "exclude": ["node_modules", "**/__tests__/*"] } \ No newline at end of file diff --git a/x402-core.d.ts b/x402-core.d.ts new file mode 100644 index 0000000..c4cd165 --- /dev/null +++ b/x402-core.d.ts @@ -0,0 +1,11 @@ +declare module '@x402/core/http' { + export function decodePaymentRequiredHeader(header: string): unknown; + export function encodePaymentSignatureHeader(payload: unknown): string; +} + +declare module '@x402/core/client' { + export class x402Client { + register(chainId: string, scheme: unknown): this; + createPaymentPayload(paymentRequired: unknown): Promise; + } +}