Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions sdk/packages/simplex/filler-config-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@

# Filler configuration
[simplex]
privateKey = "" # Replace with your actual EVM private key (not required if all chains are watchOnly)
# Choose ONE signer mode:
# 1) Private key signer
privateKey = "" # Replace with your EVM private key (not required if all chains are watchOnly)

# 2) MPCVault signer (alternative to privateKey)
# [simplex.mpcVault]
# apiToken = ""
# vaultUuid = ""
# accountAddress = "0x0000000000000000000000000000000000000000"
# callbackClientSignerPublicKey = "ssh-ed25519 AAAA..."
# baseUrl = "https://api.mpcvault.com" # optional

maxConcurrentOrders = 5

# ===== Solver Selection Mode Configuration =====
Expand Down Expand Up @@ -75,7 +86,7 @@ triggerPercentage = 0.5

# Strategy configuration
# You can configure multiple strategies
# All strategies use the simplex.privateKey from above
# All strategies use the configured simplex signer (privateKey or mpcVault)
[[strategies]]
type = "basic"
# Filler BPS (basis points) curve based on order value
Expand Down
68 changes: 52 additions & 16 deletions sdk/packages/simplex/src/bin/simplex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import { RebalancingService } from "@/services/RebalancingService"
import { getLogger, configureLogger } from "@/services/Logger"
import { CacheService } from "@/services/CacheService"
import { BidStorageService } from "@/services/BidStorageService"
import { createSimplexSigner } from "@/services/wallet"
import type { BinanceCexConfig } from "@/services/rebalancers/index"
import { Decimal } from "decimal.js"
import type { Account } from "viem/accounts"

// ASCII art header
const ASCII_HEADER = `
Expand Down Expand Up @@ -237,9 +239,18 @@ interface BinanceConfig {
withdrawTimeoutMs?: number
}

interface MpcVaultTomlConfig {
apiToken: string
vaultUuid: string
accountAddress: HexString
callbackClientSignerPublicKey: string
baseUrl?: string
}

interface FillerTomlConfig {
simplex: {
privateKey: string
privateKey?: string
mpcVault?: MpcVaultTomlConfig
maxConcurrentOrders: number
pendingQueue: PendingQueueConfig
logging?: LoggingConfig
Expand Down Expand Up @@ -303,6 +314,7 @@ program

const fillerConfigForService: FillerServiceConfig = {
privateKey: config.simplex.privateKey,
mpcVault: config.simplex.mpcVault,
maxConcurrentOrders: config.simplex.maxConcurrentOrders,
logging: config.simplex.logging,
substratePrivateKey: config.simplex.substratePrivateKey,
Expand Down Expand Up @@ -356,12 +368,17 @@ program

// Create shared services to avoid duplicate RPC calls and reuse connections
const sharedCacheService = new CacheService()
const privateKey = config.simplex.privateKey as HexString
const chainClientManager = new ChainClientManager(configService, privateKey)
const configuredSigner =
config.simplex.privateKey || config.simplex.mpcVault
? createSimplexSigner(fillerConfigForService)
: undefined
const chainClientManager = new ChainClientManager(configService, configuredSigner?.account)
const signerAccount: Account = configuredSigner?.account ?? chainClientManager.getAccount()
const contractService = new ContractInteractionService(
chainClientManager,
privateKey,
signerAccount,
configService,
configuredSigner?.signBidMessage,
sharedCacheService,
)

Expand All @@ -381,7 +398,7 @@ program
const bpsPolicy = new FillerBpsPolicy({ points: strategyConfig.bpsCurve })
const confirmationPolicy = new ConfirmationPolicy(strategyConfig.confirmationPolicies)
return new BasicFiller(
privateKey,
signerAccount,
configService,
chainClientManager,
contractService,
Expand All @@ -392,14 +409,16 @@ program
case "hyperfx": {
const bidPricePolicy = new FillerPricePolicy({ points: strategyConfig.bidPriceCurve })
const askPricePolicy = new FillerPricePolicy({ points: strategyConfig.askPriceCurve })
const fxConfirmationPolicy = strategyConfig.confirmationPolicies
? new ConfirmationPolicy(strategyConfig.confirmationPolicies)
: undefined
if (!fxConfirmationPolicy) {
logger.warn("No confirmationPolicies configured for hyperfx strategy; cross-chain orders will be skipped")
}
const fxConfirmationPolicy = strategyConfig.confirmationPolicies
? new ConfirmationPolicy(strategyConfig.confirmationPolicies)
: undefined
if (!fxConfirmationPolicy) {
logger.warn(
"No confirmationPolicies configured for hyperfx strategy; cross-chain orders will be skipped",
)
}
return new FXFiller(
privateKey,
signerAccount,
configService,
chainClientManager,
contractService,
Expand Down Expand Up @@ -434,7 +453,7 @@ program
rebalancingService = new RebalancingService(
chainClientManager,
configService,
privateKey,
signerAccount,
binanceConfig,
)
logger.info("Rebalancing service initialized")
Expand All @@ -449,7 +468,7 @@ program
configService,
chainClientManager,
contractService,
privateKey,
signerAccount,
rebalancingService,
bidStorageService,
)
Expand Down Expand Up @@ -512,8 +531,25 @@ function validateConfig(config: FillerTomlConfig): void {
})
const allChainsWatchOnly = isWatchOnlyGlobal || isWatchOnlyPerChain

if (!config.simplex?.privateKey && !allChainsWatchOnly) {
throw new Error("Filler private key is required (unless all chains are in watchOnly mode)")
const hasPrivateKey = Boolean(config.simplex?.privateKey)
const hasMpcVault = Boolean(config.simplex?.mpcVault)

if (hasPrivateKey && hasMpcVault) {
throw new Error("Configure only one signer: either simplex.privateKey or simplex.mpcVault")
}

if (!hasPrivateKey && !hasMpcVault && !allChainsWatchOnly) {
throw new Error("Signer configuration is required (simplex.privateKey or simplex.mpcVault)")
}

if (hasMpcVault) {
const mpcVault = config.simplex.mpcVault!
if (!mpcVault.apiToken) throw new Error("simplex.mpcVault.apiToken is required")
if (!mpcVault.vaultUuid) throw new Error("simplex.mpcVault.vaultUuid is required")
if (!mpcVault.accountAddress) throw new Error("simplex.mpcVault.accountAddress is required")
if (!mpcVault.callbackClientSignerPublicKey) {
throw new Error("simplex.mpcVault.callbackClientSignerPublicKey is required")
}
}

if ((!config.strategies || config.strategies.length === 0) && !allChainsWatchOnly) {
Expand Down
15 changes: 8 additions & 7 deletions sdk/packages/simplex/src/core/filler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
IntentsCoprocessor,
} from "@hyperbridge/sdk"
import pQueue from "p-queue"
import { privateKeyToAddress } from "viem/accounts"
import { privateKeyToAddress, type Account } from "viem/accounts"
import {
BidStorageService,
ChainClientManager,
Expand Down Expand Up @@ -39,7 +39,8 @@ export class IntentFiller {
private hyperbridge: Promise<IntentsCoprocessor> | undefined = undefined
private config: FillerConfig
private configService: FillerConfigService
private privateKey: HexString
private account: Account
private fillerAddress: HexString
private logger = getLogger("intent-filler")

constructor(
Expand All @@ -49,18 +50,18 @@ export class IntentFiller {
configService: FillerConfigService,
chainClientManager: ChainClientManager,
contractService: ContractInteractionService,
privateKey: HexString,
accountOrPrivateKey: Account | HexString,
rebalancingService?: RebalancingService,
bidStorage?: BidStorageService,
) {
this.configService = configService
this.privateKey = privateKey
this.account = typeof accountOrPrivateKey === "string" ? chainClientManager.getAccount() : accountOrPrivateKey
this.fillerAddress = this.account.address
this.chainClientManager = chainClientManager
this.contractService = contractService
this.rebalancingService = rebalancingService
this.bidStorage = bidStorage
const fillerAddress = privateKeyToAddress(privateKey) as HexString
this.monitor = new EventMonitor(chainConfigs, configService, this.chainClientManager, fillerAddress)
this.monitor = new EventMonitor(chainConfigs, configService, this.chainClientManager, this.fillerAddress)
this.strategies = strategies
this.config = config

Expand Down Expand Up @@ -113,7 +114,7 @@ export class IntentFiller {

// Set up delegation service on chains where solver selection is active
if (chainsWithSolverSelection.length > 0 && this.hyperbridge) {
this.delegationService = new DelegationService(this.chainClientManager, this.configService, this.privateKey)
this.delegationService = new DelegationService(this.chainClientManager, this.configService, this.account)
this.logger.info(
{ chains: chainsWithSolverSelection },
"Setting up EIP-7702 delegation on chains with solver selection",
Expand Down
22 changes: 16 additions & 6 deletions sdk/packages/simplex/src/services/ChainClientManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PublicClient, WalletClient, createPublicClient, createWalletClient, http, type Chain } from "viem"
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"
import { Order, HexString, ChainConfig, getViemChain } from "@hyperbridge/sdk"
import type { Account } from "viem/accounts"
import { FillerConfigService } from "./FillerConfigService"

/**
Expand Down Expand Up @@ -29,10 +30,9 @@ class ViemClientFactoryImpl {
return this.publicClients.get(chainConfig.chainId)!
}

public getWalletClient(chainConfig: ChainConfig, privateKey: string): WalletClient {
public getWalletClient(chainConfig: ChainConfig, account: Account): WalletClient {
if (!this.walletClients.has(chainConfig.chainId)) {
const chain = getViemChain(chainConfig.chainId) as Chain
const account = privateKeyToAccount(privateKey as `0x${string}`)

const walletClient = createWalletClient({
chain,
Expand All @@ -58,14 +58,20 @@ export const ViemClientFactory = new ViemClientFactoryImpl()
* Manages chain clients for different operations
*/
export class ChainClientManager {
private privateKey: HexString
private account: Account
private configService: FillerConfigService
private clientFactory: ViemClientFactoryImpl

constructor(configService: FillerConfigService, privateKey?: HexString) {
this.privateKey = privateKey || generatePrivateKey()
constructor(configService: FillerConfigService, accountOrPrivateKey?: Account | HexString) {
this.configService = configService
this.clientFactory = ViemClientFactory
if (!accountOrPrivateKey) {
this.account = privateKeyToAccount(generatePrivateKey())
} else if (typeof accountOrPrivateKey === "string") {
this.account = privateKeyToAccount(accountOrPrivateKey as `0x${string}`)
} else {
this.account = accountOrPrivateKey
}
}

getPublicClient(chain: string): PublicClient {
Expand All @@ -75,7 +81,11 @@ export class ChainClientManager {

getWalletClient(chain: string): WalletClient {
const config = this.configService.getChainConfig(chain)
return this.clientFactory.getWalletClient(config, this.privateKey)
return this.clientFactory.getWalletClient(config, this.account)
}

getAccount(): Account {
return this.account
}

getClientsForOrder(order: Order): {
Expand Down
26 changes: 19 additions & 7 deletions sdk/packages/simplex/src/services/ContractInteractionService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { toHex, formatUnits, encodeFunctionData, maxUint256, formatEther } from "viem"
import { privateKeyToAccount, privateKeyToAddress } from "viem/accounts"
import { privateKeyToAccount, privateKeyToAddress, type Account } from "viem/accounts"
import {
ADDRESS_ZERO,
HexString,
Expand Down Expand Up @@ -39,18 +39,29 @@ export class ContractInteractionService {
private logger = getLogger("contract-service")
private sdkHelperCache: Map<string, IntentGateway> = new Map()
private solverAccountAddress: HexString
private account: ReturnType<typeof privateKeyToAccount>
private account: Account
private signBidMessage: (messageHash: HexString, chainId: number) => Promise<HexString>

constructor(
private clientManager: ChainClientManager,
private privateKey: HexString,
accountOrPrivateKey: Account | HexString,
configService: FillerConfigService,
signBidMessage?: (messageHash: HexString, chainId: number) => Promise<HexString>,
sharedCacheService?: CacheService,
) {
this.configService = configService
this.cacheService = sharedCacheService || new CacheService()
this.solverAccountAddress = privateKeyToAddress(this.privateKey)
this.account = privateKeyToAccount(this.privateKey)
this.account =
typeof accountOrPrivateKey === "string" ? privateKeyToAccount(accountOrPrivateKey) : accountOrPrivateKey
this.solverAccountAddress = this.account.address
this.signBidMessage =
signBidMessage ??
((messageHash: HexString) => {
if (!this.account.signMessage) {
throw new Error("Configured account does not support signMessage")
}
return this.account.signMessage({ message: { raw: messageHash } })
})
this.initCache()
}

Expand Down Expand Up @@ -572,7 +583,8 @@ export class ContractInteractionService {
order,
fillOptions,
solverAccount: solverAccountAddress,
solverPrivateKey: this.privateKey,
solverSignMessage: (messageHash: HexString) =>
this.signBidMessage(messageHash, Number.parseInt(order.destination.split("-")[1] ?? "1", 10) || 1),
nonce: cachedEstimate.nonce,
entryPointAddress,
callGasLimit: cachedEstimate.callGasLimit,
Expand All @@ -581,7 +593,7 @@ export class ContractInteractionService {
maxFeePerGas: cachedEstimate.maxFeePerGas,
maxPriorityFeePerGas: cachedEstimate.maxPriorityFeePerGas,
callData,
})
} as any)

// Encode the UserOp as bytes for submission to Hyperbridge
const encodedUserOp = encodeUserOpScale(userOp)
Expand Down
10 changes: 5 additions & 5 deletions sdk/packages/simplex/src/services/DelegationService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { privateKeyToAccount } from "viem/accounts"
import { type HexString } from "@hyperbridge/sdk"
import { privateKeyToAccount, type Account } from "viem/accounts"
import type { HexString } from "@hyperbridge/sdk"
import { ChainClientManager } from "./ChainClientManager"
import { FillerConfigService } from "./FillerConfigService"
import { getLogger } from "./Logger"
Expand All @@ -13,14 +13,14 @@ const DELEGATION_INDICATOR_PREFIX = "0xef0100"
*/
export class DelegationService {
private logger = getLogger("delegation-service")
private account: ReturnType<typeof privateKeyToAccount>
private account: Account

constructor(
private clientManager: ChainClientManager,
private configService: FillerConfigService,
private privateKey: HexString,
accountOrPrivateKey: Account | `0x${string}`,
) {
this.account = privateKeyToAccount(privateKey)
this.account = typeof accountOrPrivateKey === "string" ? privateKeyToAccount(accountOrPrivateKey) : accountOrPrivateKey
}

/**
Expand Down
15 changes: 14 additions & 1 deletion sdk/packages/simplex/src/services/FillerConfigService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,17 @@ export interface RebalancingConfig {
}
}

export interface MpcVaultConfig {
apiToken: string
vaultUuid: string
accountAddress: `0x${string}`
callbackClientSignerPublicKey: string
baseUrl?: string
}

export interface FillerConfig {
privateKey: string
privateKey?: string
mpcVault?: MpcVaultConfig
maxConcurrentOrders: number
logging?: LoggingConfig
hyperbridgeWsUrl?: string
Expand Down Expand Up @@ -211,6 +220,10 @@ export class FillerConfigService {
return this.fillerConfig?.substratePrivateKey
}

getMpcVaultConfig(): MpcVaultConfig | undefined {
return this.fillerConfig?.mpcVault
}

getEntryPointAddress(chain: string): HexString | undefined {
return this.chainConfigService.getEntryPointV08Address(chain) as HexString | undefined
}
Expand Down
Loading
Loading