{"_id":"@arcus-xyz/arcus-spot-sdk","_rev":"3-0771c411f5e71861c4966fe6d4aeed43","name":"@arcus-xyz/arcus-spot-sdk","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@arcus-xyz/arcus-spot-sdk","version":"1.0.0","_id":"@arcus-xyz/arcus-spot-sdk@1.0.0","maintainers":[{"name":"k-dydx","email":"ken@dydx.exchange"},{"name":"luka-arcus","email":"luka@dydx.exchange"}],"homepage":"https://github.com/arcus-xyz/arcus-spot-sdk#readme","bugs":{"url":"https://github.com/arcus-xyz/arcus-spot-sdk/issues"},"dist":{"shasum":"7c457c1e977f0cd33885bdbd5e92ceb9b412470c","tarball":"https://registry.npmjs.org/@arcus-xyz/arcus-spot-sdk/-/arcus-spot-sdk-1.0.0.tgz","fileCount":42,"integrity":"sha512-W1TofEuIJCavq+c7H04HHvAYqelr3XqoUmRmCSldBZ6aWg486iZozx+kPiLutFBWB4tDj4MDn6V+cMUUeF9wog==","signatures":[{"sig":"MEYCIQDKxp7LrGSpVIFaPvJXKfFLA/yIcS9MYiL20/MDU2IRTgIhALKFyLCWOExhtt5wEqJ15yWWzbvsKe4ZVR0RocHJGnez","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":124985},"type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"de32aba188020d9dcf948000cea3871c71cd95d6","scripts":{"demo":"vite examples/demo --host 127.0.0.1","test":"bun test","build":"tsc -p tsconfig.build.json","prettify":"prettier --write \"src/**/*.{ts,json}\" \"examples/**/*.{ts,json,css,md}\" \"*.{json,md}\"","typecheck":"tsc --noEmit -p tsconfig.json && tsc --noEmit -p examples/demo/tsconfig.json","demo:build":"vite build examples/demo","logs:lookup":"bun examples/logs-lookup.ts","prepublishOnly":"bun run build","prettify:check":"prettier --check \"src/**/*.{ts,json}\" \"examples/**/*.{ts,json,css,md}\" \"*.{json,md}\""},"_npmUser":{"name":"luka-arcus","email":"luka@dydx.exchange"},"repository":{"url":"git+https://github.com/arcus-xyz/arcus-spot-sdk.git","type":"git"},"_npmVersion":"10.8.2","description":"Browser TypeScript SDK for the Arcus / dYdX v5 spot router server.","directories":{},"sideEffects":false,"_nodeVersion":"20.20.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"viem":"^2.51.3","vite":"^5.4.21","prettier":"^3.8.3","typescript":"^5.9.3"},"peerDependencies":{"viem":"^2.49.0"},"_npmOperationalInternal":{"tmp":"tmp/arcus-spot-sdk_1.0.0_1785361436967_0.365209402779721","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@arcus-xyz/arcus-spot-sdk","version":"1.0.1","_id":"@arcus-xyz/arcus-spot-sdk@1.0.1","maintainers":[{"name":"k-dydx","email":"ken@dydx.exchange"},{"name":"luka-arcus","email":"luka@dydx.exchange"}],"homepage":"https://github.com/arcus-xyz/arcus-spot-sdk#readme","bugs":{"url":"https://github.com/arcus-xyz/arcus-spot-sdk/issues"},"dist":{"shasum":"d003b7eb0c8221075fe1947fcd883b87eeffd452","tarball":"https://registry.npmjs.org/@arcus-xyz/arcus-spot-sdk/-/arcus-spot-sdk-1.0.1.tgz","fileCount":39,"integrity":"sha512-zcgCvcHTIRi1MoAvsR+A+t5q06CaKL4xORWljzXDFl9Buvvy1kKm+3py7dQGPX2uNa1OY6e5nqEeOlXegMWPuA==","signatures":[{"sig":"MEQCIFlEle0slgvQYmcl0v7Yzw2zwpHdQURG9jIK1sSTk15hAiAq70drw6zWFVvkmR5onXlrnCmLeeLw/2rOpUWCmQk1Yw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arcus-xyz%2farcus-spot-sdk@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":135709},"type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"de9906b26b7456ca4c85fc8521a6aa7cc7ce1912","scripts":{"demo":"vite examples/demo --host 127.0.0.1","test":"bun test","build":"tsc -p tsconfig.build.json","prettify":"prettier --write \"src/**/*.{ts,json}\" \"examples/**/*.{ts,json,css,md}\" \"*.{json,md}\"","typecheck":"tsc --noEmit -p tsconfig.json && tsc --noEmit -p examples/demo/tsconfig.json","demo:build":"vite build examples/demo","logs:lookup":"bun examples/logs-lookup.ts","prepublishOnly":"bun run build","prettify:check":"prettier --check \"src/**/*.{ts,json}\" \"examples/**/*.{ts,json,css,md}\" \"*.{json,md}\""},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:ba383a11-c5b0-4940-ab65-46c3b764d1e8"}},"repository":{"url":"git+https://github.com/arcus-xyz/arcus-spot-sdk.git","type":"git"},"_npmVersion":"12.0.2","description":"Browser TypeScript SDK for the Arcus / dYdX v5 spot router server.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"viem":"^2.51.3","vite":"^5.4.21","prettier":"^3.8.3","typescript":"^5.9.3"},"peerDependencies":{"viem":"^2.49.0"},"_npmOperationalInternal":{"tmp":"tmp/arcus-spot-sdk_1.0.1_1785370520076_0.1742394016406208","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@arcus-xyz/arcus-spot-sdk","version":"1.0.2","description":"TypeScript SDK for the Arcus spot router server.","license":"Apache-2.0","type":"module","sideEffects":false,"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"repository":{"type":"git","url":"git+https://github.com/arcus-xyz/arcus-spot-sdk.git"},"homepage":"https://github.com/arcus-xyz/arcus-spot-sdk#readme","bugs":{"url":"https://github.com/arcus-xyz/arcus-spot-sdk/issues"},"types":"./dist/index.d.ts","publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json && tsc --noEmit -p examples/demo/tsconfig.json","test":"bun test","demo":"vite examples/demo --host 127.0.0.1","demo:build":"vite build examples/demo","logs:lookup":"bun examples/logs-lookup.ts","prettify":"prettier --write \"src/**/*.{ts,json}\" \"examples/**/*.{ts,json,css,md}\" \"*.{json,md}\"","prettify:check":"prettier --check \"src/**/*.{ts,json}\" \"examples/**/*.{ts,json,css,md}\" \"*.{json,md}\"","prepublishOnly":"bun run build"},"peerDependencies":{"viem":"^2.49.0"},"devDependencies":{"prettier":"^3.8.3","typescript":"^5.9.3","viem":"^2.51.3","vite":"^5.4.21"},"gitHead":"189dd1e8c674c322784ba6d78b28056c7265bd34","_id":"@arcus-xyz/arcus-spot-sdk@1.0.2","_nodeVersion":"24.18.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-xom7jUy9V0SlFMBGyV7BtwYk/GKZtPB/+8W1SaUT3mh9Plp3p/Nt6k7BiAGtS9XUq8VnmJEazVHd4HWwsMzNMg==","shasum":"89dd1fe473629498a18458775fb85af428228a8a","tarball":"https://registry.npmjs.org/@arcus-xyz/arcus-spot-sdk/-/arcus-spot-sdk-1.0.2.tgz","fileCount":39,"unpackedSize":135697,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arcus-xyz%2farcus-spot-sdk@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDLcTC2e5MlOOVPObXEfUDc/hjuRWxuutqEx87ImVBTsAIgBgZAQcvv5UQaM7B9xgHEpF9XGxbgPgAUzYR+Byew+W4="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:ba383a11-c5b0-4940-ab65-46c3b764d1e8"}},"directories":{},"maintainers":[{"name":"k-dydx","email":"ken@dydx.exchange"},{"name":"luka-arcus","email":"luka@dydx.exchange"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/arcus-spot-sdk_1.0.2_1785433561614_0.7166337929780118"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-29T21:43:56.854Z","modified":"2026-07-30T17:46:02.109Z","1.0.0":"2026-07-29T21:43:57.125Z","1.0.1":"2026-07-30T00:15:20.255Z","1.0.2":"2026-07-30T17:46:01.759Z"},"bugs":{"url":"https://github.com/arcus-xyz/arcus-spot-sdk/issues"},"homepage":"https://github.com/arcus-xyz/arcus-spot-sdk#readme","repository":{"type":"git","url":"git+https://github.com/arcus-xyz/arcus-spot-sdk.git"},"description":"TypeScript SDK for the Arcus spot router server.","maintainers":[{"name":"k-dydx","email":"ken@dydx.exchange"},{"name":"luka-arcus","email":"luka@dydx.exchange"}],"readme":"# @arcus-xyz/arcus-spot-sdk\n\nTypeScript SDK for the Arcus spot router server. It wraps the router HTTP API and provides a viem-first signing flow for firm quotes routed through SwapShell — **Arcus RFQ**, **Rialto**, and **LI.FI** venues on Robinhood mainnet (4663) and testnet (46630).\n\n`viem` is the only runtime dependency (a peer dependency); the SDK ships no other runtime deps.\n\n## Install\n\nPublished to the public npm registry — no registry configuration or token required:\n\n```bash\nnpm install @arcus-xyz/arcus-spot-sdk viem\n# or: bun add @arcus-xyz/arcus-spot-sdk viem\n```\n\n## Hosted routers\n\nPublic router deployments are available — no local setup or API key is needed to fetch quotes:\n\n| Environment       | Base URL                                   | Chain ID | Venues              |\n| ----------------- | ------------------------------------------ | -------- | ------------------- |\n| Robinhood mainnet | `https://router.spot.arcus.xyz/v1`         | 4663     | arcus, rialto, lifi |\n| Robinhood testnet | `https://router.spot.testnet.arcus.xyz/v1` | 46630    | arcus               |\n\nVerify either with the unversioned health endpoint, e.g. `curl https://router.spot.arcus.xyz/health` → `{\"ok\":true,\"chainId\":4663,...}`. Self-hosted routers (e.g. `http://localhost:8787/v1`) work the same way — every example below accepts either base URL.\n\n## Usage\n\n### Robinhood testnet (Arcus)\n\nPoint the client at a router serving chain **46630** — the hosted testnet router above, or a locally-run router (`http://localhost:8787/v1`). The wallet must be on chain **46630**. Default mock trade pair: **mUSDG → mTSLA** (fetch token addresses via [`getTokenList()`](#chain-deployments-and-token-list) below).\n\n```ts\nimport {\n  ROBINHOOD_TESTNET_CHAIN_ID,\n  SpotRouterClient,\n  buildArcusSellTokenPermitIfNeeded,\n  erc20ApproveAbi,\n  MAX_UINT256,\n  PermitUnsupportedError,\n  signQuote,\n} from \"@arcus-xyz/arcus-spot-sdk\";\nimport { createPublicClient, createWalletClient, custom, http } from \"viem\";\n\nconst client = new SpotRouterClient({ baseUrl: \"https://router.spot.testnet.arcus.xyz/v1\" });\nconst publicClient = createPublicClient({\n  chain: { id: ROBINHOOD_TESTNET_CHAIN_ID, name: \"robinhood-testnet\" },\n  transport: http(\"https://rpc.testnet.chain.robinhood.com\"),\n});\nconst walletClient = createWalletClient({\n  account: \"0xYourWallet\",\n  transport: custom(window.ethereum),\n});\n\nconst quotes = await client.getQuote({\n  chainId: ROBINHOOD_TESTNET_CHAIN_ID,\n  sellToken: \"0xf64780eAE9CFe162EF38f5224459a014a1007cd5\", // mUSDG\n  buyToken: \"0x01206fc62E2e88df71cE4b591e93Bb203383482B\", // mTSLA\n  sellAmount: \"10000000\",\n  taker: \"0xYourWallet\",\n  slippageBps: 50,\n});\n\nconst quote = quotes.all.find((q) => q.venue === \"arcus\");\nif (!quote || quote.venue !== \"arcus\") throw new Error(\"No Arcus quote\");\n\n// Optional: one-time EIP-2612 permit when sellToken→Permit2 allowance is missing\n// (returns undefined when the allowance is already set — no signature prompt).\n// Non-EIP-2612 tokens can't permit: PermitUnsupportedError describes the one-time\n// on-chain approve to Permit2 to send instead; retry after it mines.\nlet permit;\ntry {\n  permit = await buildArcusSellTokenPermitIfNeeded({ quote, publicClient, walletClient });\n} catch (error) {\n  if (!(error instanceof PermitUnsupportedError)) throw error;\n  const hash = await walletClient.writeContract({\n    address: error.token,\n    abi: erc20ApproveAbi,\n    functionName: \"approve\",\n    args: [error.spender, MAX_UINT256], // spender is always the canonical Permit2\n    chain: null,\n  });\n  await publicClient.waitForTransactionReceipt({ hash });\n  // Retry re-reads the allowance on-chain instead of trusting the receipt.\n  permit = await buildArcusSellTokenPermitIfNeeded({ quote, publicClient, walletClient });\n}\n\nconst signed = await signQuote(quote, walletClient, {\n  permits: permit ? [permit] : undefined,\n});\nconst submitResponse = await client.submitSignedQuote(signed);\nif (submitResponse.venue === \"arcus\") {\n  console.log(submitResponse.txHash, submitResponse.status, submitResponse.orderId);\n}\n```\n\n`PermitUnsupportedError` also exposes `sellAmount` and `currentAllowance` — USDT-style tokens revert on a nonzero→nonzero approve, so send `approve(0)` first when `currentAllowance` is nonzero. Only contract-shaped failures (missing/reverting `nonces()`) classify a token as non-EIP-2612; transport errors are rethrown so a flaky RPC never downgrades a gasless flow to a gas-costing tx. The rialto and lifi builders share the same behavior.\n\nThe SDK accepts the versioned API base URL, for example `http://localhost:8787/v1`, and calls endpoints like `/quote`, `/price`, `/submit`, `/status`, and `/tokens` relative to it. `health()` remains unversioned at `/health`.\n\nEvery firm quote includes `fees`, a normalized route-fee array with `amount` in atoms and `token` as the fee token address. Venues with no reported fee return an empty array; Bebop gas/native fees may include `amountUsd` to show the USD value of the fees.\n\nExample `quote.fees` from a firm quote:\n\n```json\n[\n  {\n    \"amount\": \"1500\",\n    \"token\": \"0xaf88d065e77c8cc2239327c5edb3a432268e5831\",\n    \"type\": \"volume\"\n  },\n  {\n    \"amount\": \"16826\",\n    \"token\": \"0xaf88d065e77c8cc2239327c5edb3a432268e5831\",\n    \"type\": \"gas\"\n  }\n]\n```\n\n## Chain deployments and token list\n\nOnchain addresses are bundled per chain (published with ABIs in [`arcus-xyz/spot-contracts-abis`](https://github.com/arcus-xyz/spot-contracts-abis)):\n\n```ts\nimport {\n  getChainDeployments,\n  getSwapShellAddress,\n  ROBINHOOD_TESTNET_CHAIN_ID,\n  ROBINHOOD_TESTNET_DEPLOYMENTS,\n  SpotRouterClient,\n} from \"@arcus-xyz/arcus-spot-sdk\";\n\nconst deployments = getChainDeployments(ROBINHOOD_TESTNET_CHAIN_ID);\n// deployments.swapShell, .arcusSettlement,\n// .arcusWrappedTokenFactory, .arcusWrappedTokenBeacon, ...\n\ngetSwapShellAddress(46630); // => ROBINHOOD_TESTNET_DEPLOYMENTS.swapShell\n\nconst client = new SpotRouterClient({ baseUrl: \"https://router.spot.arcus.xyz/v1\" });\nconst tokens = await client.getTokenList();\n// [{ chainId, symbol, name, address, decimals, source, wrappedTokenAddress? }, ...]\n```\n\n| Chain             | ID    | Key exports                                             |\n| ----------------- | ----- | ------------------------------------------------------- |\n| Arbitrum One      | 42161 | `ARBITRUM_SWAP_SHELL`                                   |\n| Robinhood mainnet | 4663  | `ROBINHOOD_MAINNET_DEPLOYMENTS` (no public RPC default) |\n| Robinhood testnet | 46630 | `ROBINHOOD_TESTNET_DEPLOYMENTS`                         |\n\nAll chains are also available via `getChainDeployments(chainId)` and `CHAIN_DEPLOYMENTS_BY_ID`.\n\nUse `client.getTokenList()` to fetch the router's supported tokens for the configured chain.\n\nUse `getSwapShellTradeHistory()` with `chainId: 46630` (or pass `swapShell` explicitly) to read `SwapExecuted` logs for a taker.\n\n## Wrapped token address prediction\n\n`predictWrappedToken` derives the canonical wrapped representation of an underlying token (e.g. `mTSLA` → wrapped `mTSLA`, or mainnet `WEEK` → `wWEEK`) entirely offline — a pure CREATE2 derivation mirroring `WrappedTokenFactory.predictWrappedToken` on-chain. No RPC calls are made. The address is well-defined whether or not the wrapped token has been deployed yet (the escrow/factory deploys it on the first fill), so treat the result as the canonical address, not proof of deployment.\n\n`predictWrappedTokenForChain` is the convenience form: it reads the configured `WrappedTokenFactory` and beacon for a chain and throws if that chain has no wrapped-token deployment.\n\n```ts\nimport { predictWrappedTokenForChain, ROBINHOOD_TESTNET_CHAIN_ID } from \"@arcus-xyz/arcus-spot-sdk\";\n\nconst wrappedTsla = predictWrappedTokenForChain({\n  chainId: ROBINHOOD_TESTNET_CHAIN_ID,\n  underlying: \"0x01206fc62E2e88df71cE4b591e93Bb203383482B\", // mTSLA\n});\n```\n\nUse the lower-level `predictWrappedToken` when you need to pass factory/beacon addresses explicitly (e.g. a deployment not bundled in the SDK). Robinhood mainnet (4663) `WEEK` → `wWEEK`:\n\n```ts\nimport { ROBINHOOD_MAINNET_DEPLOYMENTS, predictWrappedToken } from \"@arcus-xyz/arcus-spot-sdk\";\n\nconst wWeek = predictWrappedToken({\n  wrappedTokenFactory: ROBINHOOD_MAINNET_DEPLOYMENTS.arcusWrappedTokenFactory!,\n  wrappedTokenBeacon: ROBINHOOD_MAINNET_DEPLOYMENTS.arcusWrappedTokenBeacon!,\n  underlying: \"0xc93a8c440CEa26D7445dF01729f193b27965099f\",\n});\n// => 0x4B17e556568bB02709a50cA67db7F4DBD46E3d17\n```\n\nPass the `WrappedTokenFactory` proxy and `WrappedToken` beacon for your deployment. Wrong inputs yield a deterministic but incorrect address — the function does not validate chain wiring.\n\n## Local demo\n\n![Demo UI showing aggregate quote selection, signing, submission, and status panels](./docs/assets/demo-screenshot.png)\n\nThe demo ships presets for the [hosted routers](#hosted-routers), so no router setup is required. Start it with:\n\n```bash\nbun install\nbun run demo\n```\n\nOpen http://127.0.0.1:5173, connect an injected wallet, fetch firm quotes, choose one, sign, submit, and poll status.\n\nFor Robinhood testnet: select the **testnet** router preset (sets chain ID to **46630** and fills the testnet SwapShell address), connect a wallet on RH testnet, and trade **mUSDG → mTSLA** with the `arcus` venue. The **local** preset (`http://localhost:8787/v1`) remains for self-hosted routers.\n\n## Develop\n\n```sh\nbun install        # install dependencies\nbun run build      # emit dist/ (tsc)\nbun run typecheck  # typecheck src + demo\nbun run test       # run the bun test suite\nbun run demo       # run the browser demo\n```\n\n## Releasing\n\nPublishing is automated by\n[`.github/workflows/release.yml`](./.github/workflows/release.yml) using **npm\ntrusted publishing (OIDC)** — no long-lived npm token is stored in the repo. The\nworkflow runs on every published GitHub Release (and can be triggered manually\nfrom the Actions tab). It typechecks, tests, builds `dist/`, and publishes to the\npublic npm registry with provenance attached automatically.\n\nTo cut a release:\n\n1. Bump `version` in `package.json` and merge it to `main`.\n2. Create a GitHub Release whose tag matches that version. Tags `v0.2.0`,\n   `sdk-v0.2.0`, and `arcus-spot-sdk@0.2.0` are all accepted and resolve to\n   `0.2.0`; the workflow verifies the resolved version matches `package.json`.\n\nRe-publishing an existing version fails by design, so always bump the version\nfirst.\n\n### One-time setup for OIDC publishing\n\nOIDC trusted publishing cannot perform the **first** publish of a brand-new\npackage (npm requires the package to exist before its trusted publisher can be\nconfigured). So the very first release is a manual step:\n\n1. **Initial manual publish** (once), from a maintainer's machine with an npm\n   login that can publish to the `@arcus-xyz` scope:\n\n   ```sh\n   bun install && bun run build\n   npm publish --access public\n   ```\n\n2. **Configure the trusted publisher** on npmjs.com: open the package →\n   **Settings → Trusted Publisher → GitHub Actions**, and set:\n   - Organization or user: `arcus-xyz`\n   - Repository: `arcus-spot-sdk`\n   - Workflow filename: `release.yml`\n   - Environment: leave blank (unless you add a GitHub environment gate)\n\nAfter that, every subsequent release publishes automatically via OIDC — no token,\nno manual step.\n","readmeFilename":"README.md","license":"Apache-2.0"}