Skip to content
This repository was archived by the owner on Mar 11, 2026. It is now read-only.

Commit 28996ff

Browse files
committed
more cosmetics
1 parent 1f4ecc7 commit 28996ff

7 files changed

Lines changed: 136 additions & 150 deletions

File tree

packages/sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hyperbridge/sdk",
3-
"version": "1.6.3",
3+
"version": "1.6.4",
44
"description": "The hyperclient SDK provides utilities for querying proofs and statuses for cross-chain requests from HyperBridge.",
55
"type": "module",
66
"types": "./dist/node/index.d.ts",

packages/sdk/src/chains/intentsCoprocessor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ export class IntentsCoprocessor {
157157
if (this.substratePrivateKey.includes(" ")) {
158158
return keyring.addFromMnemonic(this.substratePrivateKey)
159159
}
160-
const seedBytes = Buffer.from(this.substratePrivateKey, "hex")
160+
const hex = this.substratePrivateKey.startsWith("0x") ? this.substratePrivateKey.slice(2) : this.substratePrivateKey
161+
const seedBytes = Buffer.from(hex, "hex")
161162
return keyring.addFromSeed(seedBytes)
162163
}
163164

packages/simplex/scripts/Dockerfile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ COPY --chown=simplex:simplex packages/sdk/package.json ./packages/sdk/
2020
COPY --chown=simplex:simplex packages/simplex/package.json ./packages/simplex/
2121

2222
# Install dependencies for sdk and simplex only
23-
RUN pnpm install --frozen-lockfile --filter @hyperbridge/sdk --filter @hyperbridge/simplex && \
24-
chown -R simplex:simplex /app/node_modules
23+
RUN pnpm install --frozen-lockfile --filter @hyperbridge/sdk --filter @hyperbridge/simplex
2524

2625
# Copy source files
2726
COPY --chown=simplex:simplex packages/sdk/ ./packages/sdk/
@@ -36,8 +35,7 @@ RUN cd packages/simplex && pnpm build
3635
# Create a healthcheck script
3736
RUN echo '#!/bin/sh' > /app/healthcheck.sh && \
3837
echo 'ps -ef | grep "node" | grep -v grep > /dev/null || exit 1' >> /app/healthcheck.sh && \
39-
chmod +x /app/healthcheck.sh && \
40-
chown simplex:simplex /app/healthcheck.sh
38+
chmod +x /app/healthcheck.sh
4139

4240
USER simplex
4341

packages/simplex/src/bin/simplex.ts

Lines changed: 77 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,20 @@ interface BasicStrategyConfig {
7070
interface FxStrategyConfig {
7171
type: "hyperfx"
7272
/**
73-
* Array of (amount, price) coordinates defining the exotic token price curve.
74-
* price = exotic tokens per 1 USD at that order amount (e.g. 1600 for cNGN at 1600 NGN/USD).
73+
* Bid price curve: exotic tokens per 1 USD when the filler *buys* exotic from a user
74+
* (exotic→stable leg). Should have a higher exotic-per-USD rate than the ask curve so
75+
* the filler pays out fewer stablecoins per exotic token received.
7576
*/
76-
priceCurve: Array<{
77+
bidPriceCurve: Array<{
78+
amount: string
79+
price: string
80+
}>
81+
/**
82+
* Ask price curve: exotic tokens per 1 USD when the filler *sells* exotic to a user
83+
* (stable→exotic leg). Should have a lower exotic-per-USD rate than the bid curve so
84+
* the filler sends fewer exotic tokens per stablecoin received.
85+
*/
86+
askPriceCurve: Array<{
7787
amount: string
7888
price: string
7989
}>
@@ -95,61 +105,65 @@ function fmtNum(v: number): string {
95105
}
96106

97107
// Render a 2D point set as ASCII chart rows (chart body + axis row)
108+
// Axes: vertical = amount ($, high at top), horizontal = price/bps (low left, high right)
98109
function renderCurveAscii(points: Array<{ x: number; y: number }>, yLabel: string, chartWidth = 30, chartHeight = 5): string[] {
99110
const sorted = [...points].sort((a, b) => a.x - b.x)
100-
const minX = sorted[0].x
101-
const maxX = sorted[sorted.length - 1].x
102-
const minY = Math.min(...sorted.map((p) => p.y))
103-
const maxY = Math.max(...sorted.map((p) => p.y))
104-
const xRange = maxX - minX || 1
105-
const yRange = maxY - minY || 1
111+
// After flipping: horizontal = y (price/bps), vertical = x (amount)
112+
const minH = Math.min(...sorted.map((p) => p.y))
113+
const maxH = Math.max(...sorted.map((p) => p.y))
114+
const minV = sorted[0].x
115+
const maxV = sorted[sorted.length - 1].x
116+
const hRange = maxH - minH || 1
117+
const vRange = maxV - minV || 1
106118

107119
const grid: string[][] = Array.from({ length: chartHeight }, () => Array(chartWidth).fill(" "))
108120
const plotted: Array<{ col: number; row: number }> = []
109121

110122
for (const p of sorted) {
111-
const col = Math.round(((p.x - minX) / xRange) * (chartWidth - 1))
112-
const row = Math.round((1 - (p.y - minY) / yRange) * (chartHeight - 1))
123+
const col = Math.round(((p.y - minH) / hRange) * (chartWidth - 1))
124+
const row = Math.round((1 - (p.x - minV) / vRange) * (chartHeight - 1))
113125
grid[row][col] = "●"
114126
plotted.push({ col, row })
115127
}
116128

117-
// Connect consecutive points with dashes
118-
for (let i = 0; i < plotted.length - 1; i++) {
119-
const a = plotted[i],
120-
b = plotted[i + 1]
121-
for (let c = a.col + 1; c < b.col; c++) {
122-
const row = Math.round(a.row + ((c - a.col) / (b.col - a.col)) * (b.row - a.row))
123-
if (grid[row][c] === " ") grid[row][c] = "─"
129+
// Connect consecutive points with vertical connectors (sorted by row now)
130+
const byRow = [...plotted].sort((a, b) => a.row - b.row)
131+
for (let i = 0; i < byRow.length - 1; i++) {
132+
const a = byRow[i],
133+
b = byRow[i + 1]
134+
for (let r = a.row + 1; r < b.row; r++) {
135+
const col = Math.round(a.col + ((r - a.row) / (b.row - a.row)) * (b.col - a.col))
136+
if (grid[r][col] === " ") grid[r][col] = "│"
124137
}
125138
}
126139

127-
// Extend last point flat to right edge
128-
const last = plotted[plotted.length - 1]
129-
for (let c = last.col + 1; c < chartWidth; c++) {
130-
if (grid[last.row][c] === " ") grid[last.row][c] = "─"
140+
// Extend top point flat to left edge (lowest price, highest amount extends left)
141+
const top = byRow[0]
142+
for (let c = 0; c < top.col; c++) {
143+
if (grid[top.row][c] === " ") grid[top.row][c] = "─"
131144
}
132145

133-
// Y-axis prefix: show max at top row, label at mid, min at bottom row
134-
const maxYStr = fmtNum(maxY).padStart(4)
135-
const minYStr = fmtNum(minY).padStart(4)
136-
const midLabel = ` ${yLabel.trim().slice(0, 3).padEnd(3)}`
146+
// Y-axis prefix: vertical axis = amount ($), show maxV at top, label at mid, minV at bottom
147+
const maxVStr = ("$" + fmtNum(maxV)).padStart(5)
148+
const minVStr = ("$" + fmtNum(minV)).padStart(5)
149+
const midLabel = ` ${yLabel.trim().slice(0, 4).padEnd(4)}`
137150
const midRow = Math.floor(chartHeight / 2)
138151
const rows: string[] = grid.map((row, i) => {
139152
let prefix: string
140-
if (i === 0) prefix = `${maxYStr}│`
141-
else if (i === chartHeight - 1) prefix = minY !== maxY ? `${minYStr}│` : ` │`
153+
if (i === 0) prefix = `${maxVStr}│`
154+
else if (i === chartHeight - 1) prefix = minV !== maxV ? `${minVStr}│` : ` │`
142155
else if (i === midRow) prefix = `${midLabel}│`
143-
else prefix = ` │`
156+
else prefix = ` │`
144157
return prefix + row.join("")
145158
})
146159

147-
// X-axis row: embed min/max amounts within the fixed chartWidth region
148-
const minXStr = "$" + fmtNum(minX)
149-
const maxXStr = "$" + fmtNum(maxX)
150-
const dashes = chartWidth - minXStr.length - maxXStr.length
151-
const axisContent = dashes >= 1 ? minXStr + "─".repeat(dashes) + maxXStr : "─".repeat(chartWidth)
152-
rows.push(` └${axisContent}`)
160+
// X-axis row: horizontal axis = price/bps, show min left and max right
161+
const minHStr = fmtNum(minH)
162+
const maxHStr = fmtNum(maxH)
163+
const dashes = chartWidth - minHStr.length - maxHStr.length
164+
const axisContent = dashes >= 1 ? minHStr + "─".repeat(dashes) + maxHStr : "─".repeat(chartWidth)
165+
rows.push(` └${axisContent}`)
166+
rows.push(` ${" ".repeat(Math.floor(chartWidth / 2) - 1)}${yLabel.trim()}`)
153167
return rows
154168
}
155169

@@ -165,10 +179,15 @@ function getStrategyBanner(config: StrategyConfig): string {
165179
title = "BASIC STRATEGY ACTIVE"
166180
subtitle = "adaptive BPS spread curve"
167181
} else {
168-
const points = config.priceCurve.map((p) => ({ x: parseFloat(p.amount), y: parseFloat(p.price) }))
169-
chartRows = renderCurveAscii(points, " tok")
182+
const bidPoints = config.bidPriceCurve.map((p) => ({ x: parseFloat(p.amount), y: parseFloat(p.price) }))
183+
const askPoints = config.askPriceCurve.map((p) => ({ x: parseFloat(p.amount), y: parseFloat(p.price) }))
184+
chartRows = [
185+
...renderCurveAscii(bidPoints, " bid"),
186+
"",
187+
...renderCurveAscii(askPoints, " ask"),
188+
]
170189
title = "HYPERFX STRATEGY ACTIVE"
171-
subtitle = "adaptive FX price curve routing"
190+
subtitle = "adaptive bid/ask FX price curve routing"
172191
}
173192

174193
// innerWidth = 44 accommodates the longest chart row: " └" + 30×"─" + " order($)" = 44 chars
@@ -370,13 +389,15 @@ program
370389
)
371390
}
372391
case "hyperfx": {
373-
const pricePolicy = new FillerPricePolicy({ points: strategyConfig.priceCurve })
392+
const bidPricePolicy = new FillerPricePolicy({ points: strategyConfig.bidPriceCurve })
393+
const askPricePolicy = new FillerPricePolicy({ points: strategyConfig.askPriceCurve })
374394
return new FXFiller(
375395
privateKey,
376396
configService,
377397
chainClientManager,
378398
contractService,
379-
pricePolicy,
399+
bidPricePolicy,
400+
askPricePolicy,
380401
strategyConfig.maxOrderUsd,
381402
strategyConfig.exoticTokenAddresses,
382403
bidStorageService,
@@ -553,14 +574,25 @@ function validateConfig(config: FillerTomlConfig): void {
553574
}
554575

555576
if (strategy.type === "hyperfx") {
556-
// Validate price curve
557-
if (!strategy.priceCurve || !Array.isArray(strategy.priceCurve) || strategy.priceCurve.length < 2) {
558-
throw new Error("FX strategy must have a 'priceCurve' array with at least 2 points")
577+
// Validate bid price curve
578+
if (!strategy.bidPriceCurve || !Array.isArray(strategy.bidPriceCurve) || strategy.bidPriceCurve.length < 2) {
579+
throw new Error("FX strategy must have a 'bidPriceCurve' array with at least 2 points")
580+
}
581+
582+
for (const point of strategy.bidPriceCurve) {
583+
if (point.amount === undefined || point.price === undefined) {
584+
throw new Error("Each FX bidPriceCurve point must have 'amount' and 'price'")
585+
}
586+
}
587+
588+
// Validate ask price curve
589+
if (!strategy.askPriceCurve || !Array.isArray(strategy.askPriceCurve) || strategy.askPriceCurve.length < 2) {
590+
throw new Error("FX strategy must have an 'askPriceCurve' array with at least 2 points")
559591
}
560592

561-
for (const point of strategy.priceCurve) {
593+
for (const point of strategy.askPriceCurve) {
562594
if (point.amount === undefined || point.price === undefined) {
563-
throw new Error("Each FX price curve point must have 'amount' and 'price'")
595+
throw new Error("Each FX askPriceCurve point must have 'amount' and 'price'")
564596
}
565597
}
566598

packages/simplex/src/core/filler.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ export class IntentFiller {
221221

222222
// Strategy layer: first strategy that can price the order wins
223223
let inputUsdValue = baseInputUsd
224-
const canFillCache = new Set<FillerStrategy>()
224+
const canFillCache = new Map<FillerStrategy, boolean>()
225225
for (const strategy of this.strategies) {
226226
// Skip strategies that cannot price inputs
227227
if (typeof strategy.getOrderUsdValue !== "function") continue
@@ -235,8 +235,8 @@ export class IntentFiller {
235235
"Error checking canFill during inputUsdValue computation",
236236
)
237237
}
238+
canFillCache.set(strategy, canFill)
238239
if (!canFill) continue
239-
canFillCache.add(strategy)
240240
try {
241241
const stratValue = await strategy.getOrderUsdValue(order)
242242
if (stratValue != null) {
@@ -253,16 +253,15 @@ export class IntentFiller {
253253

254254
// Derive required confirmations from whichever matched strategy has a policy
255255
let requiredConfirmations = 0
256-
for (const strategy of canFillCache) {
257-
if (strategy.confirmationPolicy) {
258-
requiredConfirmations = Math.max(
259-
requiredConfirmations,
260-
strategy.confirmationPolicy.getConfirmationBlocks(
261-
getChainId(order.source)!,
262-
inputUsdValue.toNumber(),
263-
),
264-
)
265-
}
256+
for (const [strategy, canFill] of canFillCache) {
257+
if (!canFill || !strategy.confirmationPolicy) continue
258+
requiredConfirmations = Math.max(
259+
requiredConfirmations,
260+
strategy.confirmationPolicy.getConfirmationBlocks(
261+
getChainId(order.source)!,
262+
inputUsdValue.toNumber(),
263+
),
264+
)
266265
}
267266

268267
// Run confirmation waiting and evaluation in parallel.
@@ -330,7 +329,7 @@ export class IntentFiller {
330329

331330
private async evaluateOrder(
332331
order: OrderV2,
333-
canFillCache: Set<FillerStrategy>,
332+
canFillCache: Map<FillerStrategy, boolean>,
334333
): Promise<{ strategy: FillerStrategy; profitability: number } | null> {
335334
// Check if watch-only mode is enabled for the destination chain
336335
const destChainId = getChainId(order.destination)
@@ -360,7 +359,7 @@ export class IntentFiller {
360359

361360
const eligibleStrategies = await Promise.all(
362361
this.strategies.map(async (strategy) => {
363-
const canFill = canFillCache.has(strategy) || (await strategy.canFill(order))
362+
const canFill = canFillCache.has(strategy) ? canFillCache.get(strategy)! : (await strategy.canFill(order))
364363
if (!canFill) return null
365364

366365
const profitability = await strategy.calculateProfitability(order)

packages/simplex/src/strategies/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface FillerStrategy {
1717
* Optional hook for strategies to provide a USD value for the full input basket.
1818
* Returns null when the strategy cannot or does not want to price the order.
1919
*/
20-
getOrderUsdValue?(order: OrderV2): Promise<{ inputUsd: Decimal; outputUsd: Decimal } | null>
20+
getOrderUsdValue?(order: OrderV2): Promise<{ inputUsd: Decimal } | null>
2121

2222
/**
2323
* Optional confirmation policy for this strategy.

0 commit comments

Comments
 (0)