Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
25 changes: 22 additions & 3 deletions docs/content/developers/sdk/api/simplex.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -426,14 +426,33 @@ door there: filling would burn bids from an address nobody controls.
list(): VaultToml[]
preflight(vaults: VaultToml[]): Promise<void>
set(vaults: VaultToml[], options?: { sweepIntervalMs?: number }): Promise<void>
sweepNow(): Promise<void>
sweepNow(): Promise<VaultSweepResult>
redeemAll(): Promise<void>
```

`preflight` hydrates a prospective set against chain state without touching the live venue, so
unconfigured chains, same-asset duplicates and non-vault addresses surface before you commit to
`set`. `sweepNow` deposits wallet balance above each vault's threshold down to its floor;
`redeemAll` unwinds every position flagged `redeemOnShutdown` back to its underlying asset; positions without the flag are left in place.
`set`. `sweepNow` deposits wallet balance above each vault's threshold down to its floor and
returns what happened: `submitted` lists each batch with its deposits, and `skipped` names every
vault the pass left alone with a reason — `below-threshold` (the wallet has not reached its trigger),
`sweeping-disabled` (no threshold configured) or `deposits-closed` (the wallet is over its trigger
but the vault reports `maxDeposit(solver) == 0`, as a `StreamingYieldVault` does while a tranche
vests). The periodic sweep uses the same pass and logs a warning the first time a vault turns a
due deposit away. `redeemAll` unwinds every position flagged `redeemOnShutdown` back to its
underlying asset; positions without the flag are left in place.

```typescript lineNumbers
interface VaultSweepResult {
submitted: { chain: string; txHash: HexString; sponsored: boolean; deposits: VaultSweepDeposit[] }[]
skipped: VaultSweepSkip[]
}
interface VaultSweepDeposit { vault: HexString; asset: HexString; symbol: string; decimals: number; amount: bigint }
interface VaultSweepSkip {
chain: string; vault: HexString; asset: HexString; symbol: string; decimals: number
reason: "sweeping-disabled" | "below-threshold" | "deposits-closed"
walletBalance?: bigint; threshold?: bigint; maxDeposit?: bigint // base units
}
```

**Throws:** `set`, `sweepNow` and `redeemAll` throw if the solver was started without a vault treasury. `list` returns `[]` and `preflight` does not require one.

Expand Down
4 changes: 3 additions & 1 deletion docs/content/developers/sdk/simplex.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,9 @@ drains in-flight fills first, then stops scanning the chain.
// Validate against chain state, change nothing.
await simplex.vaults.preflight(vaults)
await simplex.vaults.set(vaults)
await simplex.vaults.sweepNow()
// Reports what was deposited and, per vault, why nothing was — e.g. "deposits-closed"
// when the wallet is over its threshold but the vault's maxDeposit is 0.
const sweep = await simplex.vaults.sweepNow()
await simplex.vaults.redeemAll()

await simplex.assets.add({ BRZ: { "EVM-137": "0x…" } })
Expand Down
294 changes: 290 additions & 4 deletions sdk/packages/simplex/docs/ai/ChangeLog.md

Large diffs are not rendered by default.

369 changes: 338 additions & 31 deletions sdk/packages/simplex/docs/ai/Decisions.md

Large diffs are not rendered by default.

212 changes: 205 additions & 7 deletions sdk/packages/simplex/docs/ai/Flow.md

Large diffs are not rendered by default.

16 changes: 11 additions & 5 deletions sdk/packages/simplex/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@hyperbridge/simplex",
"version": "0.12.4",
"license": "Apache-2.0",
"description": "Automated intent filler for the Hyperbridge IntentGateway \u2014 run it as a binary or embed it as a library",
"description": "Automated intent filler for the Hyperbridge IntentGateway run it as a binary or embed it as a library",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
Expand Down Expand Up @@ -55,6 +55,7 @@
"lint:fix": "pnpm run codegen && biome lint --write src/bin src/cli src/config src/core src/data src/services src/strategies src/tests src/index.ts src/simplex.ts",
"format": "pnpm run codegen && prettier --write \"src/**/*.ts\"",
"ui:build": "vite build --config ui/vite.config.ts",
"ui:check": "biome check ui/src && tsc --noEmit -p ui/tsconfig.json && vite build --config ui/vite.config.ts",
"ui:dev": "vite --config ui/vite.config.ts",
"cli": "pnpm run codegen && NODE_NO_WARNINGS=1 tsx src/bin/simplex.ts",
"cli:dev": "pnpm run codegen && nodemon --watch src --ext ts --exec tsx src/bin/simplex.ts",
Expand All @@ -68,6 +69,7 @@
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"@vitest/coverage-v8": "^3.0.7",
"better-sqlite3": "^13.0.3",
"nodemon": "^3.1.0",
"react": "^19.2.7",
"react-dom": "^19.2.7",
Expand All @@ -78,8 +80,7 @@
"typescript": "^5.7.3",
"vite": "^8.0.16",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.7",
"better-sqlite3": "^13.0.3"
"vitest": "^3.0.7"
},
"publishConfig": {
"access": "public"
Expand All @@ -95,9 +96,13 @@
"@clack/prompts": "^1.5.1",
"@grpc/grpc-js": "^1.12.5",
"@hyperbridge/sdk": "workspace:*",
"@hyperbridge/ui": "^0.0.33",
"@polkadot/api": "^16.5.6",
"@polkadot/util": "^14.0.3",
"@polkadot/util-crypto": "^14.0.3",
"@radix-ui/react-collapsible": "^1.1.20",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-select": "^2.3.7",
"@safe-global/api-kit": "^3.0.1",
"@safe-global/protocol-kit": "^6.0.4",
"@safe-global/types-kit": "^2.0.1",
Expand All @@ -115,6 +120,7 @@
"pino": "~10.3.1",
"pino-pretty": "^11.3.0",
"scale-ts": "^1.6.1",
"sonner": "^2.0.7",
"toml": "^3.0.0",
"viem": "2.47.6"
},
Expand All @@ -141,7 +147,7 @@
"src/bin/quiet.ts"
],
"optionalDependencies": {
"better-sqlite3": "^13.0.3",
"@types/better-sqlite3": "^7.6.13"
"@types/better-sqlite3": "^7.6.13",
"better-sqlite3": "^13.0.3"
}
}
4 changes: 1 addition & 3 deletions sdk/packages/simplex/src/cli/init/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface InitChainMeta {
alchemySubdomain?: string
/** Block explorer base URL (no trailing slash) for transaction links. */
explorerUrl?: string
/** Extra caveat surfaced next to the chain in prompts. */
/** Short operator-facing caveat shown beside the chain in the wizards (e.g. a paymaster limitation). */
note?: string
}

Expand Down Expand Up @@ -53,7 +53,6 @@ export const INIT_CHAINS: InitChainMeta[] = [
network: "mainnet",
alchemySubdomain: "bnb-mainnet",
explorerUrl: "https://bscscan.com",
note: "no Circle paymaster — the filler wallet also needs native BNB for gas",
},
{
chainId: 11155111,
Expand Down Expand Up @@ -94,7 +93,6 @@ export const INIT_CHAINS: InitChainMeta[] = [
network: "testnet",
alchemySubdomain: "bnb-testnet",
explorerUrl: "https://testnet.bscscan.com",
note: "no Circle paymaster — the filler wallet also needs native tBNB for gas",
},
]

Expand Down
2 changes: 1 addition & 1 deletion sdk/packages/simplex/src/cli/init/steps/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export async function stepChains(state: WizardState, prefill?: Prefill): Promise
initialValue: prefillNetwork ?? "mainnet",
options: [
{ value: "mainnet", label: "Mainnet", hint: "real funds, real orders" },
{ value: "testnet", label: "Testnet", hint: "Sepolia-family chains, for trying things out" },
{ value: "testnet", label: "Testnet", hint: "EVM test networks, for trying things out" },
],
}),
)
Expand Down
4 changes: 2 additions & 2 deletions sdk/packages/simplex/src/cli/init/steps/finetune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,11 @@ async function tuneVault(state: WizardState): Promise<void> {
// notation (1e21) would not survive as a valid TOML decimal.
const plainDecimal = (n: number): string | undefined =>
/^\d+(\.\d+)?$/.test(String(n)) ? undefined : "Enter a plain decimal number"
const threshold = await askNumber("Sweep when wallet balance reaches (USD)", 5000, (n) =>
const threshold = await askNumber("Sweep when wallet balance reaches (underlying token units)", 5000, (n) =>
n > 0 ? plainDecimal(n) : "Must be positive",
)
const minBalance = await askNumber(
"Sweep down to (USD; cover fill float + gas spend)",
"Sweep down to (underlying token units; cover fill float + gas spend)",
Math.max(1, Math.min(3000, threshold - 1)),
(n) => (n > 0 && n < threshold ? plainDecimal(n) : `Must be positive and below ${threshold}`),
)
Expand Down
2 changes: 1 addition & 1 deletion sdk/packages/simplex/src/cli/init/steps/strategies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ async function buildMarketPair(
why(WHY.maxOrderSize)
const maxOrderSize = await askNumber(
`Maximum ${token0} per order`,
Number(existing?.maxOrderSize ?? 5000),
Number(existing?.maxOrderSize ?? 50000),
(n) => (n > 0 ? undefined : "Enter a positive number"),
)

Expand Down
3 changes: 2 additions & 1 deletion sdk/packages/simplex/src/core/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ export async function bootFiller(config: FillerTomlConfig, options: BootOptions)
configService,
fillerAddress: runtimeSigner.address,
token1,
vaultBalances: vaultVenue,
hyperbridge: intentFiller.hyperbridgeConnection,
substratePrivateKey: config.simplex.substratePrivateKey,
})
Expand Down Expand Up @@ -622,7 +623,7 @@ export async function bootFiller(config: FillerTomlConfig, options: BootOptions)
vaultVenue?.startSweeping()

started.push(() => balanceProvider.stop())
balanceProvider.start()
await balanceProvider.start()

const watchOnlyChains = watchOnlyConfig
? Object.entries(watchOnlyConfig)
Expand Down
29 changes: 29 additions & 0 deletions sdk/packages/simplex/src/funding/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ export interface HydratedVault {
vault: HexString
/** Underlying asset from `vault.asset()`. */
asset: HexString
/** Underlying asset symbol, resolved on-chain during hydration. */
symbol: string
/** Underlying asset decimals. */
decimals: number
/** High-water sweep trigger scaled to token units, or null when sweeping is disabled. */
Expand All @@ -178,10 +180,37 @@ export interface HydratedVault {
positionAssets: bigint
/** Vault's authoritative withdraw cap (`maxWithdraw(solver)`). */
maxWithdrawable: bigint
/**
* Vault's deposit cap for the solver (`maxDeposit(solver)`). Zero while the vault refuses new
* capital — a streaming-yield vault mid-tranche, a supply cap, a paused market — which is when a
* sweep has nothing it can do.
*/
maxDeposit: bigint
/** Sourceable amount after consume() accounting for pending fills this round. */
remaining: bigint
}

/**
* Read-only vault balance projection consumed by operator-facing services.
* Amounts remain in base units so formatting and aggregation happen once at
* the API boundary, with no loss of precision inside the funding domain.
*/
export interface VaultBalancePosition {
chain: string
vault: HexString
asset: HexString
symbol: string
decimals: number
/** Solver-owned vault position in underlying-asset terms. */
positionAssets: bigint
/** Immediately withdrawable amount after pending-fill reservations. */
availableAssets: bigint
/** Underlying amount deliberately kept liquid in the solver wallet. */
walletReserve: bigint
/** False while `maxDeposit(solver)` is zero, so a dashboard can say why sweeps are idle. */
acceptsDeposits: boolean
}

/**
* Top-level vault funding config.
*/
Expand Down
Loading
Loading