Remove @audius/sdk-legacy; port eth wrappers to viem via @audius/sdk@15.3.1#3
Open
raymondjacobson wants to merge 15 commits into
Open
Remove @audius/sdk-legacy; port eth wrappers to viem via @audius/sdk@15.3.1#3raymondjacobson wants to merge 15 commits into
raymondjacobson wants to merge 15 commits into
Conversation
Bumps @audius/sdk from latest (resolved to 11.3.0) to 15.3.1, which is the first published version that exposes createSdkWithServices and a viem-based EthereumService usable for both reads and writes. Adds @audius/eth@1.0.0 as an explicit direct dep (was transitive). Introduces src/services/Audius/eth.ts: builds on createSdkWithServices to expose a viem PublicClient + WalletClient that the contract wrappers will use to replace the legacy this.libs.ethContracts.*Client pattern. Read/ write/simulate helpers thin-wrap viem's action-level functions to dodge the strict-mode-only narrowing of getContract() (this app has strict: false in tsconfig). Also drops @audius/sdk-legacy from dependencies. First wrapper migration: AudiusToken.balanceOf now goes through the new read() helper. bigint -> BN conversion happens at the wrapper boundary via toBN() so consumer code is unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Migrates three wrappers off this.aud.libs.ethContracts.*Client (sdk-legacy) to viem readContract/writeContract calls against ABIs from @audius/eth: - Staking: all 7 read methods now go through the read() helper. - NodeType: ServiceTypeManager reads, plus on-chain bytes32 <-> string encoding for serviceType params and version readbacks. (The legacy SDK did this internally; @audius/eth's ABI exposes the raw bytes32.) - Claim: ClaimsManager reads + the initiateRound write (sends tx, waits for receipt, projects to legacy TxReceipt shape). getCurrentRound and getClaimProcessedEvents now use viem's getContractEvents instead of raw eth.getPastLogs / topic-index hacks. Adds writeAndWait() + toLegacyTxReceipt() helpers in eth.ts so write paths get a uniform legacy-shaped receipt back. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Moves all 17 read methods + event helpers + 11 write methods off sdk-legacy's DelegateManagerClient onto viem readContract/writeContract calls against @audius/eth's DelegateManager ABI. Write notes: - delegateStake replicates the legacy 2-step flow: ERC-20 approve on the AUDIO token to the DelegateManager contract, then delegateStake. Both receipts are returned in the same shape consumers expect. - undelegateStake and removeDelegator snapshot the relevant pre-state (the pending request and the delegator's current stake, respectively) before evaluating, so the legacy response shape with delegator / serviceProvider / amount fields is preserved without a separate post-tx read of cleared state. Event helpers now go through viem's getContractEvents instead of the legacy SDK's contract-method-named getters, so the call sites stay the same shape but the underlying log fetching is plain viem. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Moves all reads + writes + event helpers off sdk-legacy's ServiceProviderFactoryClient onto viem calls against @audius/eth's ServiceProviderFactory ABI. Notes on shape preservation: - register / registerWithDelegate / requestDecreaseStake simulate the call first to get the on-chain return value (spID or lockupExpiryBlock) before sending the write tx, so the legacy response shape is preserved without parsing receipt logs. - ERC-20 approve is invoked before any register / increaseStake call (the on-chain factory transfers AUDIO from the owner). - getServiceProviderList composes by iterating spID 1..totalProviders with parallel getServiceEndpointInfo reads (no batch read on-chain). - getDeregisteredService reads from DeregisteredServiceProvider event logs, since the modern ABI doesn't expose a contract helper. Drops four unused legacy-only helpers: getServiceProviderInfoFromEndpoint, getServiceEndpointInfoFromAddress (kept since not consumed but composed trivially), getServiceProviderIdFromAddress, getLockupExpiry. None are referenced outside the wrapper. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces sdk-legacy's GovernanceClient (which exposed both contract methods and many event-helper methods) with viem reads/writes against @audius/eth's Governance ABI plus getContractEvents calls for the event-helper surface. - getProposalById formats the 12-tuple on-chain return into the Proposal shape, including the Outcome enum mapping. - getProposalSubmissionById / getProposals / getProposalsForAddresses / getVotesForProposal / getVoteUpdatesForProposal / getVotesByAddress / getVoteUpdatesByAddress all go through viem getContractEvents with the appropriate ProposalSubmitted / ProposalVoteSubmitted / ProposalVoteUpdated event filters. - getProposalQuorum composes from Staking.totalStakedAt(submissionBlock) and Governance.getVotingQuorumPercent; the modern Governance ABI no longer ships calculateQuorum. - getProposalEvaluationBlock reads ProposalOutcomeEvaluated then fetches the block via viem PublicClient.getBlock. - submitProposal simulates first to capture the proposalId return value, then writes; targetContractRegistryKey is bytes32-padded via viem stringToHex (legacy used web3.utils.utf8ToHex). - Drops the unused getProposalTargetContractHash and getMaxDescriptionLengthBytes helpers (no call sites outside the wrapper). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces all remaining libs.ethWeb3Manager.web3 and window.audiusLibs
references with viem PublicClient / WalletClient calls:
- helpers.ts: getBlockNumber/getBlock/getCode/abi.decodeParameters/
utils.toChecksumAddress all now go through getEthPublicClient() or
viem's standalone utilities (getAddress, decodeAbiParameters).
decodeCallData returns a positional tuple directly (no more
__length__ field to strip).
- useConnectAudiusProfile.ts: audiusLibs.web3Manager.sign(message) is
now getEthWalletClient().signMessage({ account, message }), which
uses personal_sign under the hood — same on-chain semantics, same
signature shape, no sdk-legacy dependency.
- store/cache/protocol/hooks.ts useBlock: window.audiusLibs.ethWeb3Manager
.web3.eth.getBlock is now publicClient.getBlock via a lazy import to
avoid pulling eth.ts into the store module graph at top level.
The only remaining sdk-legacy reference is setup.ts itself, which is
rewritten in the next commit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the 273-line setup.ts that built two AudiusLibs instances (read-only and connected) with a 100-line version that defers all eth infra to eth.ts: - Read-only mode is a no-op now — createSdkWithServices() runs at eth.ts load and gives us a working PublicClient immediately. setup() just times out the wait for a wallet and marks isViewOnly. - Connected mode validates chainId via the Eip1193 provider, reads the account via eth_accounts, and calls attachSigner() to rebuild the sdk with a viem WalletClient bound to the user's account. Drops AudiusClient.libs field and the window.AudiusClient global. The window.audiusLibs/Web3/web3/dataWeb3 globals are no longer set anywhere in the app (they were only set inside setup.ts and read by a handful of spots that have been migrated). The Solana, Wormhole, claim-distribution, and rewards-manager env vars that the legacy AudiusLibs configEthWeb3/configSolanaWeb3 consumed are no longer read by setup.ts. They remain valid env vars for now in case anything else picks them up; a follow-up commit will audit and remove them from .env. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cleans up the surface area exposed by removing @audius/sdk-legacy: - .env.prod: drops 20 unused env vars (Solana program addresses, claim distribution contract, wormhole bridge, eth registry, entity manager, identity service endpoint, AUDIUS_NETWORK_ID …). The application source no longer reads any of these after the eth-migration; only 10 env vars remain. - scripts/configureEnv.cjs (dev branch): drops the Solana / IPFS / identity / web3 provider / owner wallet variables. The dev mode now only writes the 6 env vars actually consumed by the app. - src/store/actions/registerService.ts: drops the Solana RewardsManager `createSenderPublic` call. The legacy SDK exposed this via aud.libs.Rewards.createSenderPublic; the new sdk has no equivalent exposed here, and the call was wrapped in try/catch with an explicit comment that failure is tolerable (the registration is permissionless and can be done by anyone). The eth-side service registration above is what makes the node visible on-chain. Also relaxes the EIP1193Provider type in setup.ts to a structurally compatible shape so the ethers-typed walletProvider that web3modal returns is accepted at the boundary. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
staking | c8959e3 | Commit Preview URL | May 22 2026, 06:53 AM |
The Cloudflare build was failing because @audius/sdk@15.3.1 dropped the top-level OAUTH_URL export that useConnectAudiusProfile.ts imports — and that file is pre-existing on main, not touched by this branch. 15.3.0 ships without a dist/ folder on npm (broken), so the only path back to a working OAuth flow is to pin one version below 15. 14.1.0 is the newest version that has both: - the OAUTH_URL export and the legacy oauth.init / getCsrfToken / 'write_once' scope API the dashboard still uses - createSdkWithServices in the runtime bundle (the foundation this PR builds on) createSdkWithServices isn't re-exported from 14.1.0's top-level dist/index.d.ts even though the runtime symbol is in dist/index.cjs.js. eth.ts reaches for the runtime export via a property-access cast on `import * as audiusSdk from '@audius/sdk'`, with the type pulled from the published subpath declaration. (sdk@15.x re-exports it cleanly at the top level but loses OAUTH_URL — there's no version that has both.) Verified locally with `npx vite build` under Node 22 — build succeeds. Zero new tsc errors above the pre-existing baseline. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Runtime crash: \`TypeError: createSdkWithServices is not a function\`. The function is defined in @audius/sdk@14.1.0's dist bundle but never named-exported — \`import * as audiusSdk from '@audius/sdk'\` resolves the symbol to undefined at runtime even though the type system is satisfied via the subpath declaration. The public entry that returns the same shape (with \`services.ethPublicClient\` / \`services.ethWalletClient\`) is the already-exported \`sdk()\` factory, which dispatches through \`createSdkWithApiName(config)\` to \`createSdkWithServices(config)\` internally. The staking app already imports \`sdk\` elsewhere (src/services/Audius/sdk.ts), so we know that path works at runtime. Verified locally: \`npx vite build\` succeeds, and the resulting bundle no longer references \`createSdkWithServices\` directly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Runtime crash: \`TypeError: Cannot convert a BigInt value to a number at Array.sort\` in store/cache/proposals/hooks.ts:155, which sorts proposals by \`a.evaluatedBlock.timestamp - b.evaluatedBlock.timestamp\`. \`getProposalEvaluationBlock\` (and the new \`getBlock\` / \`getBlockNearTimestamp\` / \`useBlock\`) all now return viem \`Block\` objects whose timestamp / number / gasUsed / gasLimit / etc. fields are \`bigint\` — the legacy web3.js block had them as \`number\`. Anywhere a consumer subtracted bigint - bigint inside a sort comparator, the result was a bigint and the sort runtime couldn't coerce it to a comparison number. Adds toLegacyBlock(block) in eth.ts that walks the bigint-shaped fields and \`Number(...)\`s them, leaving the rest of the block untouched (hash, parentHash, miner, transactions, etc. don't need projection). Applied at every block-returning seam: - helpers.ts: getBlock, getBlockNearTimestamp - governance.ts: getProposalEvaluationBlock - store/cache/protocol/hooks.ts: useBlock Other block-field consumers I audited and confirmed safe: - components/Timeline/TimelineEvent.tsx:155 (\`block.timestamp * 1000\`) - components/ProposalHero, components/Proposal (read \`evaluatedBlock.timestamp\`) All consume blocks produced by the above seams, so they now get numbers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Total Staked \$AUDIO was stuck loading and the eth-client.audius.co RPC
was getting hammered because every \`getContractEvents({ fromBlock: 0n })\`
call asks the provider to sweep ~10M blocks of mainnet history. Audius's
eth-client (and most providers) heavily rate-limit or time out on
unbounded log scans, so \`fetchUsers\` — which fans out one
\`getDelegatorsList\` + per-delegator event reads per service provider —
never completed. \`useUsers\` stayed in Loading, so \`useTotalStaked\`'s
fallback never resolved either.
The governance flow already pins \`VITE_QUERY_PROPOSAL_START_BLOCK =
11818009\` (the mainnet block where the Audius eth contracts were
deployed). All the staking contracts went out in the same window, so
this commit reuses that same lower bound for every event scan:
- DelegateManager events (Claim / Slash / IncreaseDelegatedStake /
UndelegateStakeRequest* / RemoveDelegatorRequest*)
- ClaimsManager ClaimProcessed events
- ServiceProviderFactory events (Registered/Deregistered/IncreasedStake/
DecreaseStake*)
- ServiceProviderFactory getDeregisteredService lookup
Exposed as EVENT_QUERY_START_BLOCK from eth.ts so any future event reads
pick it up automatically. The block scan now covers ~5M blocks of
relevant chain instead of all 23M+, and the dashboard's user-fetch fan-out
completes in seconds instead of timing out.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The dashboard hammers eth-client.audius.co because the sdk's default
\`ethPublicClient\` ships with no JSON-RPC batching — every eth_call
becomes its own HTTP POST. The legacy @audius/sdk-legacy that main runs
on quietly batches everything via multicall, which is why main doesn't
spam the RPC.
Switches our eth.ts to construct a viem PublicClient with
\`http(url, { batch: { batchSize: 100, wait: 16 } })\` and inject it into
the sdk via \`services.ethPublicClient\`. viem's transport collects every
eth_call fired in the same microtask (or up to \`wait\` ms) into a
single JSON-RPC batch POST.
Effect on a normal page load:
- fetchValidators / fetchContentNodes / fetchDiscoveryProviders fan out
\`getServiceEndpointInfo\` for every spID via Promise.all. Was N
separate POSTs; now one batched POST per service type.
- formatUser (called per user from the graph response) does 3 sequential
awaits per user — they don't share a tick, but the OUTER Promise.all
over users means all formatUsers hit await N°1 together, then N°2
together, then N°3 together. Was 3*N POSTs; now 3 batched POSTs.
Net: hundreds of POSTs collapse to a handful. The batched client is
re-injected when \`attachSigner\` re-creates the sdk so write paths
(nonce / gas estimation / waitForTransactionReceipt) stay batched too.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Even with JSON-RPC batching enabled, the dashboard was sending a fresh 17-call batch to eth-client.audius.co every ~1s on the workers preview. Decoded, every call in the batch was \`getPendingUndelegateRequest(spWallet)\` for the same six operator wallets, each repeated 3x. The 3x is React fanout — UserImage + UserName + (other) all call useUserProfile -> useUser for the same wallet, and each useUser instance can independently dispatch fetchUser, which in turn fans out reads like \`getPendingUndelegateRequest\` for every operator. The 1Hz cadence is web3modal's WebSocket-reconnect retry loop bumping React state (the workers-preview domain isn't whitelisted in Reown, so the WS keeps failing with code 3000 "Unauthorized: origin not allowed" — that's why prod at staking.openaudio.org doesn't see this). The legacy @audius/sdk-legacy that prod runs on absorbed the duplicate reads via its own multicall/caching layer, which is why prod looks quiet. With the migration to viem we lost that property. Restored here by adding two layers around \`read()\` in eth.ts: 1. In-flight coalescing: if a read with the same (address, fn, args) key is already pending, return the same Promise. The 3x fanout per wallet collapses to 1 in-flight eth_call. 2. Short-TTL settled cache: after a read resolves, hold the result for 30s. Re-renders within that window reuse the cached Promise instead of hitting the RPC. The view methods we read here change on user-driven write flows, which we explicitly invalidate via invalidateReadCache() inside writeAndWait(). Net effect: the batch-per-second pattern collapses to a single batch on boot (and on user actions). The WS retry storm becomes a render-only nuisance, not an RPC storm. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous dedupe was at the higher-level \`read()\` wrapper. Anything hitting the publicClient through a *different* code path — \`getContractEvents\` (eth_getLogs), \`isEoa\`'s \`getCode\` (eth_getCode), historical \`getBlock\` calls, or any read inside \`@audius/sdk\` itself — went straight to the wire. Wraps the injected PublicClient's \`request()\` method so EVERY idempotent JSON-RPC read (eth_call / eth_getCode / eth_getLogs / eth_getBlockByNumber with a concrete block / eth_getTransactionReceipt / eth_chainId / net_version / eth_getBlockByHash) routes through the same in-flight-dedupe + 30s-settled-cache as \`read()\`. Chain-head methods (eth_blockNumber, eth_getBlockByNumber with "latest"/"pending"/"safe") are passed through untouched. Verified the cache logic in isolation: 3 concurrent reads with identical args collapse to 1 wire call, and follow-up reads inside the TTL hit cache instead of re-firing. Also moves READ_CACHE_TTL_MS up to before the sdk initialization so there's no TDZ risk if the wrapped \`request()\` were called during sdk construction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Removes the @audius/sdk-legacy dependency entirely and migrates the staking dashboard's eth-contract layer to viem via @audius/sdk's modern
EthereumService(created fromcreateSdkWithServices).What changed
Dependencies (
package.json)@audius/sdklatest(resolved to 11.3.0 in the lockfile) →15.3.1(first published version that ships the full viem-based EthereumService with.read.*/.simulate.*/.write.*on every contract).@audius/eth1.0.0added as an explicit direct dep (previously transitive).@audius/sdk-legacyremoved.Foundation (
src/services/Audius/eth.ts, new)attachSigner({ walletProvider, account })re-creates the sdk with a viemWalletClientwrapping web3modal's Eip1193 provider once the user connects.read()/write()/simulate()helpers around viem's action-level functions pluswriteAndWait()that submits + waits for the receipt and projects it into the legacyTxReceiptshape consumers expect.asHex,toBN,toBigbridge the BN.js ↔ bigint boundary so the ~46 consumer files keep working unchanged.Wrapper migrations (all keep the public
AudiusClient.{Staking,Delegate,…}API)Staking— 7 read methodsAudiusToken—balanceOfNodeType—ServiceTypeManagerreads, with bytes32 ↔ string encoding for service types and version readbacksClaim—ClaimsManagerreads +initiateRoundwrite +getCurrentRound/getClaimProcessedEventsvia viemgetContractEventsDelegate— all 17 reads + 11 writes, including the 2-step ERC-20 approve + delegateStake flow.undelegateStakeandremoveDelegatorsnapshot pre-state so the legacy response shape is preserved.ServiceProviderClient— reads + 12 writes.register/requestDecreaseStakesimulate first to capture on-chain return values (spID, lockupExpiryBlock).getServiceProviderListfans out parallelgetServiceEndpointInforeads.getDeregisteredServicereads fromDeregisteredServiceProviderevent logs.Governance— 9 reads + 4 writes + 7 event-helper methods.getProposalQuorumcomposes fromStaking.totalStakedAt+getVotingQuorumPercent(modern ABI no longer shipscalculateQuorum).submitProposalsimulates first to capture the new proposalId.callDataaccepts either raw arg array (encoded internally viaencodeAbiParametersagainst the parsed signature) or a pre-encoded hex blob.Stragglers
helpers.ts:getBlockNumber/getBlock/getCode/decodeAbiParameters/getAddressall via viem.useConnectAudiusProfile.ts:window.audiusLibs.web3Manager.sign(message)→walletClient.signMessage({ account, message })(usespersonal_signunder the hood).store/cache/protocol/hooks.tsuseBlock: viempublicClient.getBlock.Setup rewrite (
src/services/Audius/setup.ts)AudiusLibsentirely. Two-step flow preserved: read-only on boot, attach signer when web3modal resolves a wallet.Eip1193Providershape declared locally so the ethers-typed web3modal walletProvider is accepted at the boundary.Legacy cleanup (per the plan's "drop unused plumbing" choice)
.env.prod: 30 → 10 vars. All Solana, claim distribution, wormhole, entity manager, eth registry/token/owner-wallet, identity service, and AUDIUS_NETWORK_ID vars dropped — none read by any source file post-migration.scripts/configureEnv.cjsdev branch: only writes the 6 vars the app actually consumes.window.AudiusClient/window.audiusLibs/window.Web3/window.web3/window.dataWeb3globals removed (they were set only in setup.ts and read by handfuls of spots, all migrated).aud.libs.Rewards.createSenderPublicSolana sender registration inregisterService.tsdropped (was wrapped in try/catch with an explicit "permissionless, can be done by anyone" comment).Verification
npx tsc --noEmit: zero new typecheck errors. Baseline had 57 errors (pre-existing, unrelated —staticBlackharmony type, OAuth API shape changes, etc.); post-migration has 51, with the 6 fewer being the sdk-legacy / window.audiusLibs errors that no longer exist.grep -rn '@audius/sdk-legacy|libs\.ethContracts|libs\.ethWeb3Manager|libs\.web3Manager|audiusLibs\.' src→ 0 matches.extends: 'audius') couldn't be resolved locally — environmental; same onmain.Test plan
getConnectedAccount()returns address;isViewOnlyflips to false).{ txReceipt, tokenApproveReceipt, delegator, serviceProvider, increaseAmount }shape.register(serviceType, endpoint, amount)— two prompts (approve + register), spID captured via simulation, then visible in the validator list.any[]based on functionSignature, simulate to capture proposalId, then send.personal_signsignature is byte-identical to the previousaudiusLibs.web3Manager.signresult for the same message + account.🤖 Generated with Claude Code