Quickstart
The path below has no venue-specific branches: the server says what needs signing, activate() signs it. A compiled, CI-checked version ships in the SDK as examples/multi-venue.ts.
Install
The SDK is published to GitHub Packages. Add to your project's .npmrc:
@zifrovoy-wallet:registry=https://npm.pkg.github.com
Then install (requires a GitHub token with read:packages):
npm install @zifrovoy-wallet/copy-trading
The five calls
import { CopyTradingClient, createWalletAuthHeaders } from '@zifrovoy-wallet/copy-trading'
const client = new CopyTradingClient({
baseUrl: API_URL,
apiKey: PARTNER_KEY,
// Owner-scoped routes need the user's signature too. Build this ONCE and
// reuse it β it holds the monotonic timestamp counter that prevents replay
// rejections.
getAuthHeaders: createWalletAuthHeaders({ address: userAddress, signer: personalSigner }),
})
// 1. What can you offer? Entitlements are per-partner, so ask rather than assume.
const venues = await client.listVenues()
// 2. Who is worth copying? Graded, not ranked by PnL.
const leaders = await client.listLeaders({ venue: venues[0].id, mirrorableOnly: true })
// 3. Create the agent.
const { agentId } = await client.createAgent({
venue: venues[0].id,
smartAccountAddress: userAddress,
leaderWalletAddress: leaders[0].address,
riskConfig: { maxUsd: '500', copyPercent: 10 },
})
// 4. One call, every venue. Walks whatever steps that venue needs.
await client.activate(agentId, signer)
// 5. Same feed shape everywhere.
const { executions } = await client.listExecutions(agentId)
Skipped copies are part of the feed, not an error: entries with
status: 'skipped' carry a machine-readable skipReason
(slippage_exceeded, edge_exhausted, too_close_to_resolution, β¦).
Render them β a silent gap in the feed reads as a bug to your users.
What signer must implement
Only signTypedData is required everywhere. The rest are requested per venue, and a venue that needs one you did not provide fails immediately with a message naming the method β not with a signature error from inside an adapter.
- Name
signTypedData- Type
- required
- Description
Needed by all venues.
- Name
signMessage- Type
- per venue
- Description
Needed by Polymarket (proxy delegation).
- Name
sendTransaction- Type
- per venue
- Description
Needed by spot (enabling the smart session on-chain).
Perps in three calls
Hyperliquid needs no smart-account infrastructure β any EOA that can signTypedData, one signature loop, no gas. Hyperliquid is not an EVM chain, so its strategies are catalogued under chainId: 0.
import { CopyTradingClient, PerpsCopyClient } from '@zifrovoy-wallet/copy-trading'
const perps = new PerpsCopyClient({
api: client,
signer: { signTypedData: (payload) => wallet.signTypedData(payload) },
})
// 1. pick a leader
const strategies = await client.listStrategies(0)
// 2. create the agent, sign the Hyperliquid approvals, submit them
const { agentId } = await perps.activate({
smartAccountAddress: userAddress,
strategyId: 'perps-copytrade-hyperliquid',
leaderWalletAddress: '0xβ¦',
maxUsd: '1000', // per-trade cap
copyPercent: '100', // share of the leader's size, whole percent 0β100
maxLeverage: 3,
})
// 3. show the user what it did
const { executions } = await client.listExecutions(agentId)
Spot
Spot copying installs an ERC-7579 smart session: a scoped, expiring permission that lets the backend's session key call swap routers on the user's behalf and nothing else. It cannot transfer tokens out, and it stops working on its own at validUntil.
Activation is wallet-specific. The SDK handles API calls, session policy shapes and the enable/revoke sequence; you provide a SpotSessionAdapter (build/install/revoke sessions in your wallet stack), a TransactionSender, and your actionPolicies.
Start from examples/spot-session-adapter.ts in the SDK β a working
reference extracted from the production Unhosted wallet, typechecked in CI
against @rhinestone/module-sdk and viem. If you don't run an ERC-7579
wallet stack, start with perps instead.