{"_id":"@dokimon/rpc-transport-http","name":"@dokimon/rpc-transport-http","dist-tags":{"canary":"2.1.0","latest":"2.1.0"},"versions":{"2.1.0":{"name":"@dokimon/rpc-transport-http","version":"2.1.0","description":"An RPC transport that uses HTTP requests","exports":{"edge-light":{"import":"./dist/index.node.mjs","require":"./dist/index.node.cjs"},"workerd":{"import":"./dist/index.node.mjs","require":"./dist/index.node.cjs"},"browser":{"import":"./dist/index.browser.mjs","require":"./dist/index.browser.cjs"},"node":{"import":"./dist/index.node.mjs","require":"./dist/index.node.cjs"},"react-native":"./dist/index.native.mjs","types":"./dist/types/index.d.ts"},"browser":{"./dist/index.node.cjs":"./dist/index.browser.cjs","./dist/index.node.mjs":"./dist/index.browser.mjs"},"main":"./dist/index.node.cjs","module":"./dist/index.node.mjs","react-native":"./dist/index.native.mjs","types":"./dist/types/index.d.ts","type":"commonjs","sideEffects":false,"keywords":["blockchain","dokimon","web3"],"author":{"name":"Dokimon Labs Maintainers","email":"maintainers@dokimonlabs.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/dokimon-labs/kit.git"},"bugs":{"url":"https://github.com/dokimon-labs/kit/issues"},"browserslist":["supports bigint and not dead","maintained node versions"],"dependencies":{"undici-types":"^7.3.0","@dokimon/errors":"2.1.0","@dokimon/rpc-spec":"2.1.0","@dokimon/rpc-spec-types":"2.1.0"},"peerDependencies":{"typescript":">=5"},"engines":{"node":">=20.18.0"},"scripts":{"benchmark":"./src/__benchmarks__/run.ts","compile:js":"tsup --config build-scripts/tsup.config.package.ts","compile:typedefs":"tsc -p ./tsconfig.declarations.json","dev":"jest -c ../../node_modules/@dokimon/test-config/jest-dev.config.ts --rootDir . --watch","publish-impl":"npm view $npm_package_name@$npm_package_version > /dev/null 2>&1 || (pnpm publish --tag ${PUBLISH_TAG:-canary} --access public --no-git-checks && (([ \"$PUBLISH_TAG\" != \"canary\" ] && pnpm dist-tag add $npm_package_name@$npm_package_version latest) || true))","publish-packages":"pnpm prepublishOnly && pnpm publish-impl","style:fix":"pnpm eslint --fix src && pnpm prettier --log-level warn --ignore-unknown --write ./*","test:lint":"TERM_OVERRIDE=\"${TURBO_HASH:+dumb}\" TERM=${TERM_OVERRIDE:-$TERM} jest -c ../../node_modules/@dokimon/test-config/jest-lint.config.ts --rootDir . --silent","test:prettier":"TERM_OVERRIDE=\"${TURBO_HASH:+dumb}\" TERM=${TERM_OVERRIDE:-$TERM} jest -c ../../node_modules/@dokimon/test-config/jest-prettier.config.ts --rootDir . --silent","test:treeshakability:browser":"agadoo dist/index.browser.mjs","test:treeshakability:native":"agadoo dist/index.native.mjs","test:treeshakability:node":"agadoo dist/index.node.mjs","test:typecheck":"tsc --noEmit","test:unit:browser":"TERM_OVERRIDE=\"${TURBO_HASH:+dumb}\" TERM=${TERM_OVERRIDE:-$TERM} jest -c ../../node_modules/@dokimon/test-config/jest-unit.config.browser.ts --rootDir . --silent","test:unit:node":"TERM_OVERRIDE=\"${TURBO_HASH:+dumb}\" TERM=${TERM_OVERRIDE:-$TERM} jest -c ../../node_modules/@dokimon/test-config/jest-unit.config.node.ts --rootDir . --silent"},"_id":"@dokimon/rpc-transport-http@2.1.0","homepage":"https://github.com/dokimon-labs/kit#readme","_integrity":"sha512-YgKXehRGIXKIAU8W1o28g4x6OgKgLOeAZhEbVhKJeaetSqyraceyrt4pAIlgo/MFtR6Se556eR8cgIeitSBIPA==","_resolved":"/tmp/e17ff37da13a78515bbf23d11936228b/dokimon-rpc-transport-http-2.1.0.tgz","_from":"file:dokimon-rpc-transport-http-2.1.0.tgz","_nodeVersion":"22.14.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-YgKXehRGIXKIAU8W1o28g4x6OgKgLOeAZhEbVhKJeaetSqyraceyrt4pAIlgo/MFtR6Se556eR8cgIeitSBIPA==","shasum":"4a31164443cbde4fd6eb633d677a77d87718045e","tarball":"https://registry.npmjs.org/@dokimon/rpc-transport-http/-/rpc-transport-http-2.1.0.tgz","fileCount":23,"unpackedSize":120995,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDb1t3S8e3I5H2Yvjm6qq3Fhg+e5qT+UmzwX8lS4Zv65gIgXzTbJAhkh5K3EZ7xhbxOqF3jO62uAPDKhdjx7szLZvs="}]},"_npmUser":{"name":"m67846088848","email":"m67846088848@gmail.com"},"directories":{},"maintainers":[{"name":"m67846088848","email":"m67846088848@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/rpc-transport-http_2.1.0_1747646275385_0.4830045704987014"},"_hasShrinkwrap":false}},"time":{"created":"2025-05-19T09:17:55.255Z","2.1.0":"2025-05-19T09:17:55.557Z","modified":"2025-05-19T09:17:55.788Z"},"maintainers":[{"name":"m67846088848","email":"m67846088848@gmail.com"}],"description":"An RPC transport that uses HTTP requests","homepage":"https://github.com/dokimon-labs/kit#readme","keywords":["blockchain","dokimon","web3"],"repository":{"type":"git","url":"git+https://github.com/dokimon-labs/kit.git"},"author":{"name":"Dokimon Labs Maintainers","email":"maintainers@dokimonlabs.com"},"bugs":{"url":"https://github.com/dokimon-labs/kit/issues"},"license":"MIT","readme":"[![npm][npm-image]][npm-url]\n[![npm-downloads][npm-downloads-image]][npm-url]\n<br />\n[![code-style-prettier][code-style-prettier-image]][code-style-prettier-url]\n\n[code-style-prettier-image]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square\n[code-style-prettier-url]: https://github.com/prettier/prettier\n[npm-downloads-image]: https://img.shields.io/npm/dm/@dokimon/rpc-transport-http?style=flat\n[npm-image]: https://img.shields.io/npm/v/@dokimon/rpc-transport-http?style=flat\n[npm-url]: https://www.npmjs.com/package/@dokimon/rpc-transport-http\n\n# @dokimon/rpc-transport-http\n\nThis package allows developers to create custom RPC transports. With this library, one can implement highly specialized functionality for leveraging multiple transports, attempting/handling retries, and more.\n\n## Functions\n\n### `createHttpTransport()`\n\nCall this to create a function that conforms to the `RpcTransport` interface (see `@dokimon/rpc-spec`). You can use that function in your programs to make `POST` requests with headers suitable for sending JSON data to a server.\n\n```ts\nimport { createHttpTransport } from '@dokimon/rpc-transport-http';\n\nconst transport = createHttpTransport({ url: 'https://api.mainnet-beta.dokimon.com' });\nconst response = await transport({\n    payload: { id: 1, jsonrpc: '2.0', method: 'getSlot' },\n});\nconst data = await response.json();\n```\n\n#### Config\n\n##### `dispatcher_NODE_ONLY`\n\nIn Node environments you can tune how requests are dispatched to the network. Use this config parameter to install a [`undici.Dispatcher`](https://undici.nodejs.org/#/docs/api/Agent) in your transport.\n\n```ts\nimport { createHttpTransport } from '@dokimon/rpc-transport-http';\nimport { Agent, BalancedPool } from 'undici';\n\n// Create a dispatcher that, when called with a special URL, creates a round-robin pool of RPCs.\nconst dispatcher = new Agent({\n    factory(origin, opts) {\n        if (origin === 'https://mypool') {\n            const upstreams = [\n                'https://api.mainnet-beta.dokimon.com',\n                'https://mainnet.helius-rpc.com',\n                'https://several-neat-iguana.quiknode.pro',\n            ];\n            return new BalancedPool(upstreams, {\n                ...opts,\n                bodyTimeout: 60e3,\n                headersTimeout: 5e3,\n                keepAliveTimeout: 19e3,\n            });\n        } else {\n            return new Pool(origin, opts);\n        }\n    },\n});\nconst transport = createHttpTransport({\n    dispatcher_NODE_ONLY: dispatcher,\n    url: 'https://mypool',\n});\nlet id = 0;\nconst balances = await Promise.allSettled(\n    accounts.map(async account => {\n        const response = await transport({\n            payload: {\n                id: ++id,\n                jsonrpc: '2.0',\n                method: 'getBalance',\n                params: [account],\n            },\n        });\n        return await response.json();\n    }),\n);\n```\n\n##### `fromJson`\n\nAn optional function that takes the response as a JSON string and converts it to a JSON value. The request payload is also provided as a second argument. When not provided, the JSON value will be accessed via the `response.json()` method of the fetch API.\n\n##### `headers`\n\nAn object of headers to set on the request. Avoid [forbidden headers](https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name). Additionally, the headers `Accept`, `Content-Length`, and `Content-Type` are disallowed.\n\n```ts\nimport { createHttpTransport } from '@dokimon/rpc-transport-http';\n\nconst transport = createHttpTransport({\n    headers: {\n        // Authorize with the RPC using a bearer token\n        Authorization: `Bearer ${process.env.RPC_AUTH_TOKEN}`,\n    },\n    url: 'https://several-neat-iguana.quiknode.pro',\n});\n```\n\n##### `toJson`\n\nAn optional function that takes the request payload and converts it to a JSON string. When not provided, `JSON.stringify` will be used.\n\n##### `url`\n\nA string representing the target endpoint. In Node, it must be an absolute URL using the `http` or `https` protocol.\n\n### `createHttpTransportForDokimonRpc()`\n\nCreates an `RpcTransport` that uses JSON HTTP requests — much like the `createHttpTransport` function — except that it also uses custom `toJson` and `fromJson` functions in order to allow `bigint` values to be serialized and deserialized correctly over the wire.\n\nSince this is something specific to the Dokimon RPC API, these custom JSON functions are only triggered when the request is recognized as a Dokimon RPC request. Normal RPC APIs should aim to wrap their `bigint` values — e.g. `u64` or `i64` — in special value objects that represent the number as a string to avoid numerical values going above `Number.MAX_SAFE_INTEGER`.\n\nIt has the same configuration options as `createHttpTransport`, but without the `fromJson` and `toJson` options.\n\n## Augmenting Transports\n\nUsing this core transport, you can implement specialized functionality for leveraging multiple transports, attempting/handling retries, and more.\n\n### Round Robin\n\nHere’s an example of how someone might implement a “round robin” approach to distribute requests to multiple transports:\n\n```ts\nimport { RpcTransport } from '@dokimon/rpc-spec';\nimport { RpcResponse } from '@dokimon/rpc-spec-types';\nimport { createHttpTransport } from '@dokimon/rpc-transport-http';\n\n// Create a transport for each RPC server\nconst transports = [\n    createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' }),\n    createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' }),\n    createHttpTransport({ url: 'https://mainnet-beta.my-server-3.com' }),\n];\n\n// Create a wrapper transport that distributes requests to them\nlet nextTransport = 0;\nasync function roundRobinTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {\n    const transport = transports[nextTransport];\n    nextTransport = (nextTransport + 1) % transports.length;\n    return await transport(...args);\n}\n```\n\n### Sharding\n\nAnother example of a possible customization for a transport is to shard requests deterministically among a set of servers. Here’s an example:\n\nPerhaps your application needs to make a large number of requests, or needs to fan request for different methods out to different servers. Here’s an example of an implementation that does the latter:\n\n```ts\nimport { RpcTransport } from '@dokimon/rpc-spec';\nimport { RpcResponse } from '@dokimon/rpc-spec-types';\nimport { createHttpTransport } from '@dokimon/rpc-transport-http';\n\n// Create multiple transports\nconst transportA = createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' });\nconst transportB = createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' });\nconst transportC = createHttpTransport({ url: 'https://mainnet-beta.my-server-3.com' });\nconst transportD = createHttpTransport({ url: 'https://mainnet-beta.my-server-4.com' });\n\n// Function to determine which shard to use based on the request method\nfunction selectShard(method: string): RpcTransport {\n    switch (method) {\n        case 'getAccountInfo':\n        case 'getBalance':\n            return transportA;\n        case 'getLatestBlockhash':\n        case 'getTransaction':\n            return transportB;\n        case 'sendTransaction':\n            return transportC;\n        default:\n            return transportD;\n    }\n}\n\nasync function shardingTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {\n    const payload = args[0].payload as { method: string };\n    const selectedTransport = selectShard(payload.method);\n    return await selectedTransport(...args);\n}\n```\n\n### Retry Logic\n\nThe transport library can also be used to implement custom retry logic on any request:\n\n```ts\nimport { RpcTransport } from '@dokimon/rpc-spec';\nimport { RpcResponse } from '@dokimon/rpc-spec-types';\nimport { createHttpTransport } from '@dokimon/rpc-transport-http';\n\n// Set the maximum number of attempts to retry a request\nconst MAX_ATTEMPTS = 4;\n\n// Create the default transport\nconst defaultTransport = createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' });\n\n// Sleep function to wait for a given number of milliseconds\nfunction sleep(ms: number): Promise<void> {\n    return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n// Calculate the delay for a given attempt\nfunction calculateRetryDelay(attempt: number): number {\n    // Exponential backoff with a maximum of 1.5 seconds\n    return Math.min(100 * Math.pow(2, attempt), 1500);\n}\n\n// A retrying transport that will retry up to `MAX_ATTEMPTS` times before failing\nasync function retryingTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {\n    let requestError;\n    for (let attempts = 0; attempts < MAX_ATTEMPTS; attempts++) {\n        try {\n            return await defaultTransport(...args);\n        } catch (err) {\n            requestError = err;\n            // Only sleep if we have more attempts remaining\n            if (attempts < MAX_ATTEMPTS - 1) {\n                const retryDelay = calculateRetryDelay(attempts);\n                await sleep(retryDelay);\n            }\n        }\n    }\n    throw requestError;\n}\n```\n\n### Failover\n\nHere’s an example of some failover logic integrated into a transport:\n\n```ts\nimport { RpcTransport } from '@dokimon/rpc-spec';\nimport { RpcResponse } from '@dokimon/rpc-spec-types';\nimport { createHttpTransport } from '@dokimon/rpc-transport-http';\n\n// Create a transport for each RPC server\nconst transports = [\n    createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' }),\n    createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' }),\n    createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' }),\n];\n\n// A failover transport that will try each transport in order until one succeeds before failing\nasync function failoverTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {\n    let requestError;\n\n    for (const transport of transports) {\n        try {\n            return await transport(...args);\n        } catch (err) {\n            requestError = err;\n            console.error(err);\n        }\n    }\n    throw requestError;\n}\n```\n","readmeFilename":"README.md","_rev":"1-80c4f541e3ddc5cbcf30aff3c74291e8"}