Skip to content

feat(perps): perps-controller: make Hyperliquid deposit and withdrawal testable on testnet - #10991

Merged
abretonc7s merged 3 commits into
mainfrom
TAT-4204-feat-perps-controller-make-hyperliq
Oct 22, 2026
Merged

abretonc7s merged 3 commits into
mainfrom
TAT-4204-feat-perps-controller-make-hyperliq

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Explanation

Perps deposits couldn't be tested on Hyperliquid testnet. Every end-to-end check needed real mainnet funds.

There were two problems:

  • DepositService.prepareTransaction called provider.getDepositRoutes({ isTestnet: false }). On a testnet session, depositWithConfirmation therefore asked the wallet for an Arbitrum One USDC transfer to the mainnet bridge.
  • The testnet USDC asset (USDC_ARBITRUM_TESTNET_ADDRESS, HYPERLIQUID_ASSET_CONFIGS.usdc.testnet) pointed at 0x75faf114…AA4d. That isn't the token the testnet bridge uses: usdcToken() on the Arbitrum Sepolia bridge 0x08cfc1B6…6f89 returns USDC2 0x1baAbB04…4d5. Withdrawal routes advertised the same wrong token, even though withdraw3 pays out in USDC2.

The fix:

  • DepositService calls provider.getDepositRoutes() with no argument. HyperLiquidProvider already falls back to its own isTestnetMode(), AggregatedPerpsProvider forwards the call to its default provider, and Lighter still returns no deposit route.
  • The testnet USDC asset is now USDC2. Testnet deposit and withdrawal routes now name the token that is actually deposited and paid out.

Mainnet doesn't change: Arbitrum One USDC 0xaf88…5831 goes to bridge 0x2df1…3df7, as before.

Before this change, I checked that testnet credits a full deposit. A real 29.84 USDC2 transfer to the testnet bridge (0xb53a3e01…6daf16, 2026-10-12) showed up on the sender's Hyperliquid testnet ledger 5 s later, under the same hash. The Hyperliquid bridge2 docs list the same bridge and USDC2 as the testnet permit domain.

Validation (testnet, Trading fixture account, real PerpsController and messenger; the wallet side signs with the fixture key on Arbitrum Sepolia):

Flow Transaction Hyperliquid testnet Arbitrum Sepolia USDC2
Deposit 6 0xdfe2eda2e73edff52f6de95a3db22605bcb67cc91f13971efbcb06fa20aa9a2c (ledger deposit 6.0, same hash) 327.884162 → 333.884162 610.65 → 604.65
Withdraw 3 payout 0xbeca9f1a0209f1bd3e7e308f6781dfb23f5b7453580a99bc7f2e54623cc3dd7d (ledger withdraw 2.0 + fee 1.0, same hash) 333.884162 → 330.884162 604.65 → 606.65
Mainnet deposit, prepared and declined none, 0 signatures n/a n/a

Before the fix, the same recipe fails at the testnet route check: the testnet session prepares chain 0xa4b1, Arbitrum One USDC, mainnet bridge. The mainnet guard passes both before and after.

Things reviewers might not expect:

  • Breaking: USDC_ARBITRUM_TESTNET_ADDRESS is exported with a literal type, so its declared type changes to the USDC2 address, and on testnet withdraw now rejects the old asset ID 0x75faf114…AA4d. Code that reads the constant or getWithdrawalRoutes() follows the change automatically; anything that hardcodes the old address or types against the old literal has to switch.
  • To deposit on testnet, a client wallet needs an Arbitrum Sepolia network client, and its confirmation flow (Transaction Pay) has to support USDC2. Without the network client, the controller throws its existing No network client found for chain 0x66eee error instead of quietly preparing a mainnet transfer.

References

Validation Recipe

recipe.json (17 nodes — Hyperliquid deposit and withdrawal on testnet - Core)
{
  "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
  "title": "Hyperliquid deposit and withdrawal on testnet - Core",
  "paramsSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "account": {
        "type": "string",
        "default": "Trading",
        "description": "Wallet-fixture private-key account that owns the testnet funds."
      },
      "deposit_amount": {
        "type": "string",
        "default": "6",
        "description": "USDC2 the confirming wallet deposits; at least 5, the bridge minimum."
      },
      "withdraw_amount": {
        "type": "string",
        "default": "3",
        "description": "USDC withdrawn through the controller; above the 1.01 route minimum."
      }
    }
  },
  "proofTargets": [
    {
      "id": "AC-1",
      "claim": "[TAT-4204] With a testnet session, depositWithConfirmation builds an Arbitrum Sepolia transfer of the token the testnet bridge credits, to the testnet bridge, and the Hyperliquid testnet balance increases after confirmation."
    },
    {
      "id": "AC-2",
      "claim": "[TAT-4204] With a mainnet session, the deposit route and token are unchanged."
    },
    {
      "id": "AC-3",
      "claim": "[TAT-4204] A testnet withdrawal through the controller lands on Arbitrum Sepolia and the Hyperliquid testnet balance drops by the amount plus fee."
    },
    {
      "id": "AC-4",
      "claim": "[TAT-4204] Both flows are proven end to end with a recipe on core (testnet, tx hashes and balances before and after recorded), not only unit tests."
    },
    {
      "id": "AC-5",
      "claim": "[TAT-4204] If testnet cannot support a full deposit, the PR is replaced by a written finding with evidence and a recommended cheapest mainnet validation path."
    }
  ],
  "workflow": {
    "entry": "ac2-mainnet-prepare",
    "nodes": {
      "ac2-mainnet-prepare": {
        "action": "command",
        "timeout_ms": 180000,
        "intent": "Start a deposit on a mainnet session and decline it at the wallet confirmation",
        "next": "ac2-mainnet-prepare-exit"
      },
      "ac2-mainnet-prepare-exit": {
        "action": "assert_exit_code",
        "source": "ac2-mainnet-prepare",
        "expected": 0,
        "intent": "The mainnet deposit driver finished without an error",
        "next": "ac2-mainnet-prepare-evidence"
      },
      "ac2-mainnet-prepare-evidence": {
        "action": "index_artifacts",
        "artifacts": [
          {
            "type": "json",
            "category": "proof",
            "label": "ac2-mainnet-prepare driver record",
            "proofTarget": "AC-2"
          },
          {
            "type": "log",
            "category": "proof",
            "label": "ac2-mainnet-prepare controller debug log",
            "proofTarget": "AC-2"
          }
        ],
        "intent": "Keep the ac2 mainnet prepare record and controller log in the run package",
        "next": "ac2-mainnet-route-unchanged"
      },
      "ac2-mainnet-route-unchanged": {
        "action": "assert_json",
        "assert": {
          "all": [
            {
              "path": "$.network",
              "operator": "eq",
              "value": "mainnet"
            },
            {
              "path": "$.networkLookup.chainId",
              "operator": "eq",
              "value": "0xa4b1"
            },
            {
              "path": "$.request.chainId",
              "operator": "eq",
              "value": "0xa4b1"
            },
            {
              "path": "$.request.type",
              "operator": "eq",
              "value": "perpsDeposit"
            },
            {
              "path": "$.request.functionName",
              "operator": "eq",
              "value": "transfer"
            },
            {
              "path": "$.request.to",
              "operator": "eq",
              "value": "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
            },
            {
              "path": "$.request.recipient",
              "operator": "eq",
              "value": "0x2df1c51e09aecf9cacb7bc98cb1742757f163df7"
            },
            {
              "path": "$.wallet.broadcast",
              "operator": "eq",
              "value": false
            },
            {
              "path": "$.signatures",
              "operator": "length_eq",
              "value": 0
            },
            {
              "path": "$.depositRequest.status",
              "operator": "eq",
              "value": "cancelled"
            }
          ]
        },
        "intent": "Mainnet deposits still send Arbitrum One USDC to the mainnet bridge, and nothing was signed or sent",
        "proves": [
          "AC-2"
        ],
        "next": "ac1-testnet-prepare"
      },
      "ac1-testnet-prepare": {
        "action": "command",
        "timeout_ms": 180000,
        "intent": "Start a deposit on a testnet session and inspect the transaction the wallet is asked to confirm",
        "next": "ac1-testnet-prepare-exit"
      },
      "ac1-testnet-prepare-exit": {
        "action": "assert_exit_code",
        "source": "ac1-testnet-prepare",
        "expected": 0,
        "intent": "The testnet deposit preparation finished without an error",
        "next": "ac1-testnet-prepare-evidence"
      },
      "ac1-testnet-prepare-evidence": {
        "action": "index_artifacts",
        "artifacts": [
          {
            "type": "json",
            "category": "proof",
            "label": "ac1-testnet-prepare driver record",
            "proofTarget": "AC-1"
          },
          {
            "type": "log",
            "category": "proof",
            "label": "ac1-testnet-prepare controller debug log",
            "proofTarget": "AC-1"
          }
        ],
        "intent": "Keep the ac1 testnet prepare record and controller log in the run package",
        "next": "ac1-testnet-route"
      },
      "ac1-testnet-route": {
        "action": "assert_json",
        "assert": {
          "all": [
            {
              "path": "$.network",
              "operator": "eq",
              "value": "testnet"
            },
            {
              "path": "$.bridge.usdcToken",
              "operator": "eq",
              "value": "0x1baabb04529d43a73232b713c0fe471f7c7334d5"
            },
            {
              "path": "$.networkLookup.chainId",
              "operator": "eq",
              "value": "0x66eee"
            },
            {
              "path": "$.request.chainId",
              "operator": "eq",
              "value": "0x66eee"
            },
            {
              "path": "$.request.functionName",
              "operator": "eq",
              "value": "transfer"
            },
            {
              "path": "$.request.to",
              "operator": "eq",
              "value": "0x1baabb04529d43a73232b713c0fe471f7c7334d5"
            },
            {
              "path": "$.request.recipient",
              "operator": "eq",
              "value": "0x08cfc1b6b2dcf36a1480b99353a354aa8ac56f89"
            },
            {
              "path": "$.wallet.broadcast",
              "operator": "eq",
              "value": false
            }
          ]
        },
        "intent": "A testnet deposit asks for an Arbitrum Sepolia transfer of the bridge's own token to the testnet bridge",
        "proves": [
          "AC-1"
        ],
        "next": "ac1-testnet-deposit"
      },
      "ac1-testnet-deposit": {
        "action": "command",
        "timeout_ms": 360000,
        "intent": "Deposit on testnet and confirm it in the wallet, then wait for Hyperliquid testnet to credit it",
        "next": "ac1-testnet-deposit-exit"
      },
      "ac1-testnet-deposit-exit": {
        "action": "assert_exit_code",
        "source": "ac1-testnet-deposit",
        "expected": 0,
        "intent": "The confirmed testnet deposit finished without an error",
        "next": "ac1-testnet-deposit-evidence"
      },
      "ac1-testnet-deposit-evidence": {
        "action": "index_artifacts",
        "artifacts": [
          {
            "type": "json",
            "category": "proof",
            "label": "ac1-testnet-deposit driver record",
            "proofTarget": "AC-1"
          },
          {
            "type": "log",
            "category": "proof",
            "label": "ac1-testnet-deposit controller debug log",
            "proofTarget": "AC-1"
          }
        ],
        "intent": "Keep the ac1 testnet deposit record and controller log in the run package",
        "next": "ac1-deposit-credited"
      },
      "ac1-deposit-credited": {
        "action": "assert_json",
        "assert": {
          "all": [
            {
              "path": "$.request.chainId",
              "operator": "eq",
              "value": "0x66eee"
            },
            {
              "path": "$.request.to",
              "operator": "eq",
              "value": "0x1baabb04529d43a73232b713c0fe471f7c7334d5"
            },
            {
              "path": "$.request.recipient",
              "operator": "eq",
              "value": "0x08cfc1b6b2dcf36a1480b99353a354aa8ac56f89"
            },
            {
              "path": "$.wallet.decision",
              "operator": "eq",
              "value": "confirmed"
            },
            {
              "path": "$.wallet.receipt.status",
              "operator": "eq",
              "value": "success"
            },
            {
              "path": "$.txHash",
              "operator": "matches",
              "value": "^0x[0-9a-f]{64}$"
            },
            {
              "path": "$.depositRequest.status",
              "operator": "eq",
              "value": "completed"
            },
            {
              "path": "$.depositRequest.txHash",
              "operator": "matches",
              "value": "^0x[0-9a-f]{64}$"
            },
            {
              "path": "$.arbitrumSepoliaSpentMicros",
              "operator": "eq",
              "value": 6000000
            },
            {
              "path": "$.before.controller.totalMicros",
              "operator": "gte",
              "value": 0
            },
            {
              "path": "$.after.controller.totalMicros",
              "operator": "gt",
              "value": 0
            },
            {
              "path": "$.creditedMicros",
              "operator": "eq",
              "value": 6000000
            },
            {
              "path": "$.ledger.hash",
              "operator": "matches",
              "value": "^0x[0-9a-f]{64}$"
            },
            {
              "path": "$.ledger.usdcMicros",
              "operator": "eq",
              "value": 6000000
            }
          ]
        },
        "intent": "The confirmed deposit left Arbitrum Sepolia, Hyperliquid testnet recorded it under the same transaction hash, and the balance rose by the same 6 USDC",
        "proves": [
          "AC-1",
          "AC-4",
          "AC-5"
        ],
        "next": "ac3-testnet-withdraw"
      },
      "ac3-testnet-withdraw": {
        "action": "command",
        "timeout_ms": 900000,
        "intent": "Withdraw on testnet through the controller and wait for the payout on Arbitrum Sepolia",
        "next": "ac3-testnet-withdraw-exit"
      },
      "ac3-testnet-withdraw-exit": {
        "action": "assert_exit_code",
        "source": "ac3-testnet-withdraw",
        "expected": 0,
        "intent": "The testnet withdrawal finished without an error",
        "next": "ac3-testnet-withdraw-evidence"
      },
      "ac3-testnet-withdraw-evidence": {
        "action": "index_artifacts",
        "artifacts": [
          {
            "type": "json",
            "category": "proof",
            "label": "ac3-testnet-withdraw driver record",
            "proofTarget": "AC-3"
          },
          {
            "type": "log",
            "category": "proof",
            "label": "ac3-testnet-withdraw controller debug log",
            "proofTarget": "AC-3"
          }
        ],
        "intent": "Keep the ac3 testnet withdraw record and controller log in the run package",
        "next": "ac3-withdraw-landed"
      },
      "ac3-withdraw-landed": {
        "action": "assert_json",
        "assert": {
          "all": [
            {
              "path": "$.route.caipChainId",
              "operator": "eq",
              "value": "eip155:421614"
            },
            {
              "path": "$.route.token",
              "operator": "eq",
              "value": "0x1baabb04529d43a73232b713c0fe471f7c7334d5"
            },
            {
              "path": "$.result.success",
              "operator": "eq",
              "value": true
            },
            {
              "path": "$.ledger.hash",
              "operator": "matches",
              "value": "^0x[0-9a-f]{64}$"
            },
            {
              "path": "$.ledger.usdcMicros",
              "operator": "gt",
              "value": 0
            },
            {
              "path": "$.arrival.chainId",
              "operator": "eq",
              "value": 421614
            },
            {
              "path": "$.arrival.token",
              "operator": "eq",
              "value": "0x1baabb04529d43a73232b713c0fe471f7c7334d5"
            },
            {
              "path": "$.arrival.txHash",
              "operator": "matches",
              "value": "^0x[0-9a-f]{64}$"
            },
            {
              "path": "$.arrival.amountMicros",
              "operator": "gt",
              "value": 0
            },
            {
              "path": "$.reconciliation.feeMicros",
              "operator": "eq",
              "value": 1000000
            },
            {
              "path": "$.reconciliation.hlDropMicros",
              "operator": "gt",
              "value": 0
            },
            {
              "path": "$.reconciliation.unexplainedMicros",
              "operator": "eq",
              "value": 0
            },
            {
              "path": "$.before.controller.totalMicros",
              "operator": "gt",
              "value": 0
            },
            {
              "path": "$.after.controller.totalMicros",
              "operator": "gte",
              "value": 0
            }
          ]
        },
        "intent": "The withdrawal landed on Arbitrum Sepolia in the advertised token, Hyperliquid testnet recorded it under the payout hash, and the balance fell by the landed amount plus the fee",
        "proves": [
          "AC-3",
          "AC-4"
        ],
        "next": "done"
      },
      "done": {
        "action": "end",
        "status": "pass"
      }
    }
  }
}

Validation Logs

Run: 43bb42c0-5714-4333-9c00-59326eaeeb18

Runner: @metamask/recipe-runner

Runner revision: unknown

Recipe digest: sha256:f2f74c0b58774eb4713fd5e6023123f5d36bf09f3dcbdb5efe56c0ba64cb52ad

Full output (17/17 passed, pass)
# MetaMask Recipe Run

Status: pass
Duration: 361s
Nodes: 17/17 passed

## Steps

- PASS ac2-mainnet-prepare-exit (assert_exit_code, 2ms): source=ac2-mainnet-prepare, expected=0, actual=0
- PASS ac2-mainnet-prepare-evidence (index_artifacts, 10ms)

- PASS ac1-testnet-prepare-exit (assert_exit_code, 1ms): source=ac1-testnet-prepare, expected=0, actual=0
- PASS ac1-testnet-prepare-evidence (index_artifacts, 3ms)

- PASS ac1-testnet-deposit-exit (assert_exit_code, 1ms): source=ac1-testnet-deposit, expected=0, actual=0
- PASS ac1-testnet-deposit-evidence (index_artifacts, 6ms)

- PASS ac3-testnet-withdraw-exit (assert_exit_code, 2ms): source=ac3-testnet-withdraw, expected=0, actual=0
- PASS ac3-testnet-withdraw-evidence (index_artifacts, 6ms)
- PASS done (end, 0ms)

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them
    • Breaking: the USDC_ARBITRUM_TESTNET_ADDRESS literal type and the testnet withdrawal asset ID change (CHANGELOG entry prefixed **BREAKING:**). No draft client PRs yet.
    • Consumer audit: no other core package uses these constants. Mobile (perps-transaction.ts, useWithdrawValidation.ts) and Extension (perps-withdraw-page.tsx) production code imports them from @metamask/perps-controller, so it needs no source change. Tests that pin the old testnet asset ID need updating on upgrade: Mobile useWithdrawValidation.test.ts and hyperLiquidValidation.test.ts, Extension perps-withdraw-page.test.tsx.
Full evidence gallery and publication receipt

Publication approval

Arthur · person · arthur · 2026-10-22T07:06:55.652Z
Package: c051188d3e3d29cd104fe8829356aa3b94fd5469e5293a1427778c14ae348cba. Visual receipts: none.


Note

Medium Risk
Breaking exported testnet USDC constants and deposit route selection affect real fund flows on testnet; mainnet paths are intentionally unchanged but financial routing code was modified.

Overview
Hyperliquid testnet deposits and withdrawals now use the token and chain the testnet bridge actually credits and pays out, instead of mainnet Arbitrum One routes and a wrong testnet USDC address.

Breaking: USDC_ARBITRUM_TESTNET_ADDRESS and HYPERLIQUID_ASSET_CONFIGS.usdc.testnet switch to USDC2 (0x1baAbB04529D43a73232B713C0FE471f7c7334d5). Testnet withdraw rejects the previous asset ID; consumers must not hardcode 0x75faf114….

Deposit routing: DepositService.prepareTransaction calls provider.getDepositRoutes() with no isTestnet: false override, so a testnet session prepares an Arbitrum Sepolia transfer of USDC2 to the testnet bridge. Mainnet behavior is unchanged.

Changelog and unit tests (getSupportedAssets, DepositService) document and lock in the new addresses and route selection.

Reviewed by Cursor Bugbot for commit e86e862. Bugbot is set up for automated code reviews on this repo. Configure here.

@abretonc7s abretonc7s changed the title chore: prepare farmslot publication pkg-4efa176e-mvj6xl2i feat(perps): perps-controller: make Hyperliquid deposit and withdrawal testable on testnet Oct 22, 2026
@abretonc7s
abretonc7s marked this pull request as ready for review October 22, 2026 07:08
@abretonc7s
abretonc7s requested review from a team as code owners October 22, 2026 07:08
…trum Sepolia bridge

DepositService forced mainnet deposit routes, so a testnet session prepared
an Arbitrum One USDC transfer to the mainnet bridge. Take routes for the
provider's own network instead.

Point the testnet USDC asset at USDC2 (0x1baAbB04529D43a73232B713C0FE471f7c7334d5),
the token the Arbitrum Sepolia bridge credits and pays out, so testnet
deposit and withdrawal routes name the right token.

Refs: TAT-4204
@abretonc7s
abretonc7s force-pushed the TAT-4204-feat-perps-controller-make-hyperliq branch from a6d58de to e86e862 Compare October 22, 2026 08:08
@abretonc7s
abretonc7s enabled auto-merge October 22, 2026 08:47
@abretonc7s
abretonc7s added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit e9ff361 Oct 22, 2026
48 checks passed
@abretonc7s
abretonc7s deleted the TAT-4204-feat-perps-controller-make-hyperliq branch October 22, 2026 08:52
This was referenced Oct 23, 2026
SnowingFox pushed a commit to SnowingFox/core-1 that referenced this pull request Oct 24, 2026
## Explanation

Major release of `@metamask/perps-controller` (`21.0.0` → `22.0.0`).

- **Breaking:** testnet deposit and withdrawal routes use Arbitrum
Sepolia USDC2 (`0x1baAbB04529D43a73232B713C0FE471f7c7334d5`).
`USDC_ARBITRUM_TESTNET_ADDRESS` and
`HYPERLIQUID_ASSET_CONFIGS.usdc.testnet` change to that address, and
`withdraw` rejects the previous testnet asset ID.
`depositWithConfirmation` prepares an Arbitrum Sepolia transfer to the
Hyperliquid testnet bridge.
([MetaMask#10991](MetaMask#10991))
- `TrackingData`, `TPSLTrackingData`, and `WithdrawParams` accept an
optional `environmentType`. Events that already copy attribution include
`environment_type` when it is set.
([MetaMask#10899](MetaMask#10899))
- New export `@metamask/perps-controller/providers/lighter`, plus
optional `LighterCredentials.loadProvider`, so a web client can bundle
Lighter itself. `switchProvider('lighter')` reports why the provider
failed to load. A missing Lighter module is debug-logged instead of sent
through `logger.error`.
([MetaMask#10996](MetaMask#10996))
- The `closePositions` summary event includes `order_type: 'market'`.
([MetaMask#11027](MetaMask#11027))
- Engines allow Node.js 26.
([MetaMask#10981](MetaMask#10981))

## References

- [MetaMask#10991](MetaMask#10991)
- [MetaMask#10899](MetaMask#10899)
- [MetaMask#10996](MetaMask#10996)
- [MetaMask#11027](MetaMask#11027)
- [MetaMask#10981](MetaMask#10981)

## Checklist

- [ ] I've updated the test suite for new or updated code as appropriate
- [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Major perps-controller release with breaking testnet token/bridge
behavior; this PR only bumps versions but consumers picking up 22.0.0
inherit that risk.
> 
> **Overview**
> **Monorepo release 1359.0.0** that publishes
**`@metamask/perps-controller` 22.0.0** and bumps
**`@metamask/transaction-pay-middleware`** to depend on `^22.0.0` (plus
`yarn.lock` and root package version).
> 
> The diff is mostly versioning and changelog housekeeping; the shipped
**22.0.0** behavior (already merged) includes a **breaking testnet USDC2
/ Arbitrum Sepolia deposit path** change, optional **`environmentType`**
on perps tracking/withdraw params, **Lighter** `loadProvider` + public
provider export, **`closePositions`** analytics `order_type: 'market'`,
and **Node.js 26** engine support.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
8356d7d. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants