@ophis/sdk
v0.4.2
Published
Integration helpers for Ophis (a CoW Protocol fork): correct orderbook hosts, the CIP-75 partner-fee appData fragment, the per-chain EIP-712 signing domain, and order receiver-safety guards.
Maintainers
Readme
@ophis/sdk
Integration helpers for Ophis — a CoW Protocol fork with a natural-language intent layer.
Non-custodial. These helpers build and guard order parameters. They never hold keys or sign on your behalf. See Security before wiring up an automated signer.
Install
npm install @ophis/sdkWhat's in it
getOphisOrderbookUrl(chainId)— the correct orderbook host per chain. Optimism is self-hosted atoptimism-mainnet.ophis.fi, notapi.cow.fi; getting this wrong silently bypasses the Ophis solver and partner fee.getOphisOrderDomain(chainId)/getOphisSettlementAddress(chainId)— the EIP-712 signing domain with the correct per-chainverifyingContract(the OP settlement is non-canonical, so the cow-sdk default is wrong there).buildOphisAppDataPartnerFee(chainId, isStablePair)— the exact CIP-75 fee config. Operated chains return the 1 bp base (their backend adds improvement capture); hosted chains return base + pair-aware capped improvement entries.ophisOrderReceiver/assertReceiverIsOwner— pin a CoW order'sreceiverto the owner. An unpinned receiver is the #1 drain vector for an automated signer.buildOphisOrderMetadata/enrollOphisTrader/buildOphisOrderCreation— the high-level order-flow helpers that collapse the integration footguns into one call each:appCodeis always'ophis'(a custom one silently forfeits the rebate), each trader wallet is enrolled with the rebate indexer, the receiver is asserted, and thesendOrderwire shape (fullappDatastring +appDataHash) is correct.getOphisVaultRelayer(chainId)— the correctapprovespender for the one-time sell-token approval. On Optimism the Ophis relayer is not cow-sdk's canonical one, so resolve it here.buildOphisEthFlowOrder/getOphisEthFlowAddress/isOphisEthFlowChain— sell native ETH through Ophis via the on-chain eth-flowcreateOrder, with the Ophis partner-fee appData embedded. Without this an integrator has to wrap to WETH first and Ophis is unavailable on native-ETH sells. The builder pins the receiver to the taker, hardcodes the eth-flowfeeAmount/msg.valuecorrectly, and (optionally) verifies the committed appData hash binds to the JSON you upload.parseOphisApiError/withOphisRetry/isUnroutable/isRetryable: typed errors for the orderbook API's numeric code bands (1xxx-5xxx) withtraceIdcapture (X-Trace-Idheader + error body). "No route" is a typed answer (OphisUnroutableError), not a failure, and is never retried; 429 is never retried in-call (both hold even under a customshouldRetry: the terminal classes are rethrown before the predicate runs; for every other error a custom predicate replaces the default policy entirely and can narrow or broaden it, so broadening callers must retry only transient failures); only the 3xxx upstream band (503 +Retry-After) is. Unknown codes degrade gracefully and preserve the raw payload.ophisPreflight/isPreflightReady/approvalNeeded: one batched Multicall3balanceOf+allowanceread answering "can this order settle?" before the user signs. Takes any viemPublicClient(structural interface, no viem runtime dependency), defaults the spender to the correct per-chain vault relayer, and fails closed: an RPC failure throws (including an outage that viem surfaces as an all-failure batch), it never reports ready, the batch is one un-chunkedeth_call(single block snapshot) on clients that honorbatchSize: 0(viemPublicClients do unless constructed with an explicit client-levelbatch.multicall.batchSize, which viem lets override the per-call value; clients that chunk anyway are covered by pair-consistency and transport-shape guards that throw instead of zeroing), and when the client exposesgetChainId(viem's does) a client connected to a different chain than the requestedchainIdthrows instead of returning plausible wrong-network balances.assignTier,ophisDefaults, and the partner-fee constants.
Example
import {
getOphisOrderbookUrl,
getOphisOrderDomain,
buildOphisAppDataPartnerFee,
assertReceiverIsOwner,
} from '@ophis/sdk';
const orderbook = getOphisOrderbookUrl(10); // https://optimism-mainnet.ophis.fi
const domain = getOphisOrderDomain(10); // { name, version, chainId, verifyingContract }
const partnerFee = buildOphisAppDataPartnerFee(chainId, isStablePair);
assertReceiverIsOwner(owner, order.receiver); // throws if proceeds would leave the accountSecurity
These are off-chain misuse guards, not an authorization boundary — they make the safe path the easy path, but a caller can ignore them. For an agent that signs without a human in the loop, enforce policy on-chain (a Safe + an EIP-1271 policy validator: pinned receiver, pinned appData/hooks, an oracle-bounded limit price, spend caps, and a guardian). See the AI agent integration guide.
