@lifi/perps-sdk 0.0.0-preview-5cdfc44 → 0.0.0-preview-b49efcc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +22 -0
  2. package/dist/cjs/client/PerpsClient.d.ts +7 -3
  3. package/dist/cjs/client/PerpsClient.d.ts.map +1 -1
  4. package/dist/cjs/client/PerpsClient.js +43 -47
  5. package/dist/cjs/client/PerpsClient.js.map +1 -1
  6. package/dist/cjs/index.d.ts +2 -2
  7. package/dist/cjs/index.d.ts.map +1 -1
  8. package/dist/cjs/index.js +4 -3
  9. package/dist/cjs/index.js.map +1 -1
  10. package/dist/cjs/registry/assetRegistry.d.ts +2 -1
  11. package/dist/cjs/registry/assetRegistry.d.ts.map +1 -1
  12. package/dist/cjs/registry/assetRegistry.js +11 -1
  13. package/dist/cjs/registry/assetRegistry.js.map +1 -1
  14. package/dist/cjs/types/api.d.ts +2 -2
  15. package/dist/cjs/types/api.d.ts.map +1 -1
  16. package/dist/cjs/types/provider.d.ts +8 -2
  17. package/dist/cjs/types/provider.d.ts.map +1 -1
  18. package/dist/cjs/version.d.ts +1 -1
  19. package/dist/cjs/version.js +1 -1
  20. package/dist/esm/client/PerpsClient.d.ts +54 -47
  21. package/dist/esm/client/PerpsClient.d.ts.map +1 -1
  22. package/dist/esm/client/PerpsClient.js +92 -102
  23. package/dist/esm/client/PerpsClient.js.map +1 -1
  24. package/dist/esm/index.d.ts +2 -2
  25. package/dist/esm/index.d.ts.map +1 -1
  26. package/dist/esm/index.js +1 -1
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/registry/assetRegistry.d.ts +7 -1
  29. package/dist/esm/registry/assetRegistry.d.ts.map +1 -1
  30. package/dist/esm/registry/assetRegistry.js +14 -0
  31. package/dist/esm/registry/assetRegistry.js.map +1 -1
  32. package/dist/esm/types/api.d.ts +11 -13
  33. package/dist/esm/types/api.d.ts.map +1 -1
  34. package/dist/esm/types/provider.d.ts +33 -6
  35. package/dist/esm/types/provider.d.ts.map +1 -1
  36. package/dist/esm/version.d.ts +1 -1
  37. package/dist/esm/version.js +1 -1
  38. package/dist/types/client/PerpsClient.d.ts +54 -47
  39. package/dist/types/client/PerpsClient.d.ts.map +1 -1
  40. package/dist/types/index.d.ts +2 -2
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/dist/types/registry/assetRegistry.d.ts +7 -1
  43. package/dist/types/registry/assetRegistry.d.ts.map +1 -1
  44. package/dist/types/types/api.d.ts +11 -13
  45. package/dist/types/types/api.d.ts.map +1 -1
  46. package/dist/types/types/provider.d.ts +33 -6
  47. package/dist/types/types/provider.d.ts.map +1 -1
  48. package/dist/types/version.d.ts +1 -1
  49. package/package.json +2 -2
  50. package/src/client/PerpsClient.ts +130 -138
  51. package/src/index.ts +6 -1
  52. package/src/registry/assetRegistry.ts +16 -1
  53. package/src/types/api.ts +11 -13
  54. package/src/types/provider.ts +41 -6
  55. package/src/version.ts +1 -1
@@ -4,6 +4,7 @@ import type {
4
4
  ActionParamsMap,
5
5
  ActionResult,
6
6
  ActionStep,
7
+ AvailableToTrade,
7
8
  CreateActionResponse,
8
9
  ExecuteActionResponse,
9
10
  MarketRef,
@@ -16,19 +17,20 @@ import type {
16
17
  PositionMarginConstraints,
17
18
  Provider,
18
19
  ProviderAction,
19
- SetupAction,
20
20
  SignedActionStep,
21
21
  } from '@lifi/perps-types'
22
22
  import {
23
23
  ActionType,
24
24
  META_PROVIDER,
25
25
  PerpsErrorCode,
26
+ PerpsSigner,
26
27
  SigningMethod,
27
28
  } from '@lifi/perps-types'
28
29
  import Big from 'big.js'
29
30
  import type { Address } from 'viem'
30
31
  import { PerpsError } from '../errors/PerpsError.js'
31
- import { getAssetRegistry } from '../registry/assetRegistry.js'
32
+ import { getAssetRegistry, toAssetDisplay } from '../registry/assetRegistry.js'
33
+ import { getMarketRegistry } from '../registry/marketRegistry.js'
32
34
  import { createAction } from '../services/createAction.js'
33
35
  import { executeAction } from '../services/executeAction.js'
34
36
  import { getAccount as fetchAccount } from '../services/getAccount.js'
@@ -87,23 +89,6 @@ import {
87
89
  import { createPerpsClient } from './createPerpsClient.js'
88
90
  import { requireProvider as resolveProvider } from './requireProvider.js'
89
91
 
90
- /** Absent `sequence` sorts last, so an unordered step gates nothing. */
91
- function sequenceOf(descriptor: ProviderAction): number {
92
- return descriptor.sequence ?? Number.MAX_SAFE_INTEGER
93
- }
94
-
95
- /**
96
- * Whether the SDK can apply a preference without user input. Every declared
97
- * parameter must carry a `default`, since the action's params object is only
98
- * complete when each one has a value.
99
- */
100
- function declaresDefault(descriptor: ProviderAction): boolean {
101
- const params = descriptor.params ?? []
102
- return (
103
- params.length > 0 && params.every((param) => param.default !== undefined)
104
- )
105
- }
106
-
107
92
  /**
108
93
  * Look up an action's descriptor in the provider's metadata. Throws if the
109
94
  * action isn't declared — defensive: better to fail loudly than to mis-sign.
@@ -112,9 +97,11 @@ function findActionDescriptor(
112
97
  metadata: Provider,
113
98
  action: ActionType
114
99
  ): ProviderAction {
115
- const descriptor = [...metadata.setup, ...metadata.actions].find(
116
- (d) => d.type === action
117
- )
100
+ const descriptor = [
101
+ ...metadata.setup,
102
+ ...metadata.options,
103
+ ...metadata.actions,
104
+ ].find((d) => d.type === action)
118
105
  if (!descriptor) {
119
106
  throw new PerpsError(
120
107
  PerpsErrorCode.SDKError,
@@ -467,7 +454,7 @@ export class PerpsClient {
467
454
  /**
468
455
  * Fetch the user's account state from the backend and attach the
469
456
  * SDK-projected `settings` array — one `AccountConfigSetting` per
470
- * descriptor on `Provider.setup`. Callers read
457
+ * descriptor on `Provider.setup` + `Provider.options`. Callers read
471
458
  * `result.settings` directly without re-deriving values from the typed
472
459
  * `AccountConfig`.
473
460
  *
@@ -484,7 +471,11 @@ export class PerpsClient {
484
471
  fetchAccount(this.sdkClient, params),
485
472
  this.getProviderMetadata(params.provider),
486
473
  ])
487
- const settings = plugin.projectConfig(response.config, metadata.setup)
474
+ const settings = plugin.projectConfig(
475
+ response.config,
476
+ metadata.setup,
477
+ metadata.options
478
+ )
488
479
  return { ...response, settings }
489
480
  }
490
481
 
@@ -509,6 +500,58 @@ export class PerpsClient {
509
500
  })
510
501
  }
511
502
 
503
+ /**
504
+ * The amounts `params.address` can still buy and sell on one market, in
505
+ * that market's margin asset. The order panel reads this per-market figure;
506
+ * account displays read the account-scoped
507
+ * {@link AccountSummary.availableMargin} instead.
508
+ *
509
+ * Providers that read a per-market figure answer it directly. For every
510
+ * other provider this falls back to the account summary, so both sides
511
+ * equal `availableMargin` and the asset is the market's quote asset.
512
+ *
513
+ * @throws {PerpsError} When the provider plugin is not registered, or the
514
+ * market is unknown to the provider's market registry.
515
+ * @public
516
+ */
517
+ async getAvailableToTrade(
518
+ params: {
519
+ provider: string
520
+ address: Address
521
+ marketId: string
522
+ },
523
+ options?: SDKRequestOptions
524
+ ): Promise<AvailableToTrade> {
525
+ const plugin = this.requireProvider(params.provider)
526
+ const perMarket = await plugin.getAvailableToTrade?.(
527
+ { address: params.address, marketId: params.marketId },
528
+ options
529
+ )
530
+ if (perMarket !== undefined) {
531
+ return perMarket
532
+ }
533
+
534
+ const registry = getMarketRegistry(this.sdkClient, params.provider)
535
+ await registry.sync()
536
+ const market = registry.require(params.marketId)
537
+ const account = await fetchAccount(
538
+ this.sdkClient,
539
+ { provider: params.provider, address: params.address },
540
+ options
541
+ )
542
+ const { availableMargin } = plugin.getAccountSummary(
543
+ account,
544
+ account.positions
545
+ )
546
+ return {
547
+ providerId: market.providerId,
548
+ marketId: market.id,
549
+ asset: toAssetDisplay(market.quoteAsset),
550
+ buy: availableMargin,
551
+ sell: availableMargin,
552
+ }
553
+ }
554
+
512
555
  /**
513
556
  * Resolve the exact venue-owned margin requirements for `position`.
514
557
  * Returns `undefined` when the position has no individual margin adjustment.
@@ -648,16 +691,15 @@ export class PerpsClient {
648
691
  }
649
692
 
650
693
  /**
651
- * Return the setup steps this account must still sign as a flat,
652
- * self-describing list. Trading is gated on `isReady === true`.
653
- * `checklist` carries the renderable onboarding list: every `approval` and
654
- * `preference` descriptor with its satisfied state, with not-required
655
- * conditional steps omitted.
694
+ * Return the unsatisfied entries on `Provider.setup` for this account as a
695
+ * flat, self-describing list. Trading is gated on `isReady === true`.
696
+ * `checklist` carries the renderable onboarding list: every USER-signed
697
+ * descriptor with its satisfied state, with not-required conditional steps
698
+ * omitted.
656
699
  *
657
- * `automatic` descriptors are NEVER returned here — the SDK drains them with
658
- * the provider's own credentials. A `preference` is listed but never staged:
659
- * the user re-enters it through {@link executeProviderOption}, and the SDK
660
- * applies the descriptor's declared default while it stays unsatisfied.
700
+ * `Provider.options` descriptors are NEVER returned here — options are
701
+ * post-setup tunables and never gate trading. Option state is surfaced
702
+ * separately via `getAccount().settings`.
661
703
  *
662
704
  * @public
663
705
  */
@@ -689,12 +731,12 @@ export class PerpsClient {
689
731
  )
690
732
 
691
733
  const plugin = this.sdkClient.getProvider(provider)
692
-
693
- // Only an `approval` is staged by the generic loop: an `automatic` step is
694
- // drained below, and a `preference` is re-entered by the user through
695
- // `executeProviderOption` rather than signed off a staged step.
696
- const stageable = pendingSetup.filter(
697
- (descriptor) => descriptor.kind === 'approval'
734
+ const internalTypes = new Set(plugin?.internalSetupActions ?? [])
735
+ const isInternal = (descriptor: ProviderAction): boolean =>
736
+ internalTypes.has(descriptor.type) &&
737
+ !descriptor.signers.includes(PerpsSigner.USER)
738
+ const visibleSetup = pendingSetup.filter(
739
+ (descriptor) => !isInternal(descriptor)
698
740
  )
699
741
 
700
742
  // The backend filters already-satisfied setup actions and returns typed
@@ -703,7 +745,7 @@ export class PerpsClient {
703
745
  const actions = await this.buildProviderSetupActions(
704
746
  provider,
705
747
  address,
706
- stageable
748
+ visibleSetup
707
749
  )
708
750
 
709
751
  // A staged step is one the build produced actions for — a pending
@@ -711,8 +753,10 @@ export class PerpsClient {
711
753
  // (backend-gated) or not applicable to this account.
712
754
  const stagedTypes = new Set(actions.map((step) => step.action))
713
755
  const conditionalTypes = new Set(plugin?.conditionalSetupActions ?? [])
756
+ const sequenceOf = (descriptor: ProviderAction): number =>
757
+ descriptor.sequence ?? Number.MAX_SAFE_INTEGER
714
758
  const checklist = metadata.setup
715
- .filter((descriptor) => descriptor.kind !== 'automatic')
759
+ .filter((descriptor) => descriptor.signers.includes(PerpsSigner.USER))
716
760
  .filter(
717
761
  (descriptor) =>
718
762
  !conditionalTypes.has(descriptor.type) ||
@@ -721,21 +765,14 @@ export class PerpsClient {
721
765
  .sort((a, b) => sequenceOf(a) - sequenceOf(b))
722
766
  .map((descriptor) => ({
723
767
  descriptor,
724
- satisfied:
725
- descriptor.kind === 'preference'
726
- ? satisfiedSetup.has(descriptor.type)
727
- : !stagedTypes.has(descriptor.type),
768
+ satisfied: !stagedTypes.has(descriptor.type),
728
769
  }))
729
770
 
730
- await this.drainSetup(
771
+ await this.drainInternalSetup(
731
772
  provider,
732
773
  address,
733
- pendingSetup.filter(
734
- (descriptor) =>
735
- descriptor.kind === 'automatic' ||
736
- (descriptor.kind === 'preference' && declaresDefault(descriptor))
737
- ),
738
- stageable.filter((descriptor) => stagedTypes.has(descriptor.type))
774
+ pendingSetup.filter(isInternal),
775
+ visibleSetup.filter((descriptor) => stagedTypes.has(descriptor.type))
739
776
  )
740
777
 
741
778
  return {
@@ -761,34 +798,43 @@ export class PerpsClient {
761
798
  return new Set()
762
799
  }
763
800
  const account = await plugin.getAccount({ address })
764
- const settings = plugin.projectConfig(account.config, metadata.setup)
801
+ const settings = plugin.projectConfig(
802
+ account.config,
803
+ metadata.setup,
804
+ metadata.options
805
+ )
806
+ const setupTypes = new Set(
807
+ metadata.setup.map((descriptor) => descriptor.type)
808
+ )
765
809
  return new Set(
766
810
  settings
767
- .filter((setting) => setting.satisfied)
811
+ .filter((setting) => setting.satisfied && setupTypes.has(setting.type))
768
812
  .map((setting) => setting.type)
769
813
  )
770
814
  }
771
815
 
772
816
  /**
773
- * Drain the pending setup steps the SDK fulfils without user input: every
774
- * `automatic` step, and every unsatisfied `preference` whose descriptor
775
- * declares a parameter default. Each step is built, signed, and executed in
776
- * place with the provider's own credentials. A step is deferred while any
777
- * staged user-facing step with a lower `sequence` is outstanding — it cannot
778
- * succeed before its prerequisite (e.g. SET_REFERRAL authenticates with the
779
- * credential REGISTER_API_KEY installs) and each doomed attempt is venue
780
- * traffic. A drain failure is swallowed so it never blocks setup — the step
781
- * stays unsatisfied and is retried on a later `checkSetup`.
817
+ * Drain the pending setup steps the provider declares as internal via
818
+ * `PerpsProviderPlugin.internalSetupActions`. Each step is built, signed,
819
+ * and executed in place with the provider's own credentials. A step is
820
+ * deferred while any staged user-facing step with a lower `sequence` is
821
+ * outstanding — it cannot succeed before its prerequisite (e.g. SET_REFERRAL
822
+ * authenticates with the credential REGISTER_API_KEY installs) and each
823
+ * doomed attempt is venue traffic. A drain failure is swallowed so it never
824
+ * blocks setup — the step stays unsatisfied and is retried on a later
825
+ * `checkSetup`.
782
826
  */
783
- private async drainSetup(
827
+ private async drainInternalSetup(
784
828
  provider: string,
785
829
  address: Address,
786
- drainable: SetupAction[],
787
- stagedVisible: SetupAction[]
830
+ internalPending: ProviderAction[],
831
+ stagedVisible: ProviderAction[]
788
832
  ): Promise<void> {
789
- for (const descriptor of drainable) {
833
+ const sequenceOf = (descriptor: ProviderAction): number =>
834
+ descriptor.sequence ?? Number.MAX_SAFE_INTEGER
835
+ for (const descriptor of internalPending) {
790
836
  // A tie (equal sequences, or both absent) declares no order, so the
791
- // drained step defers to the next checkSetup rather than racing.
837
+ // internal step defers to the next checkSetup rather than racing.
792
838
  const blocked = stagedVisible.some(
793
839
  (staged) => sequenceOf(staged) <= sequenceOf(descriptor)
794
840
  )
@@ -816,7 +862,7 @@ export class PerpsClient {
816
862
  }
817
863
  } catch (error) {
818
864
  console.debug(
819
- `[perps-sdk] setup step '${descriptor.type}' for '${provider}' did not drain; will retry on the next checkSetup.`,
865
+ `[perps-sdk] internal setup step '${descriptor.type}' for '${provider}' did not complete; will retry on the next checkSetup.`,
820
866
  error
821
867
  )
822
868
  }
@@ -868,9 +914,7 @@ export class PerpsClient {
868
914
 
869
915
  /**
870
916
  * Build the unsigned setup `ActionStep`s still outstanding for an account,
871
- * ordered by descriptor `sequence`. Only an `approval` step is staged: an
872
- * `automatic` step drains inside {@link checkSetup} and a `preference`
873
- * carries the user's own selection. The backend filters already-satisfied
917
+ * ordered by descriptor `sequence`. The backend filters already-satisfied
874
918
  * setup; each plugin contributes its own signer-bearing request fields and
875
919
  * any local-state params (e.g. Lighter's known pubkey).
876
920
  *
@@ -885,7 +929,7 @@ export class PerpsClient {
885
929
  const actions = await this.buildProviderSetupActions(
886
930
  provider,
887
931
  address,
888
- metadata.setup.filter((descriptor) => descriptor.kind === 'approval')
932
+ metadata.setup
889
933
  )
890
934
  return { actions }
891
935
  }
@@ -939,45 +983,6 @@ export class PerpsClient {
939
983
  return { results: { results } }
940
984
  }
941
985
 
942
- /**
943
- * The lowest-`sequence` visible setup step that runs before `descriptor` and
944
- * is not satisfied, or `undefined` when nothing blocks it. A `preference`
945
- * reads its satisfied state from the plugin projection; an `approval` is
946
- * satisfied once the provider stages no action for it, so only the earlier
947
- * steps are staged here.
948
- */
949
- private async findBlockingSetupStep(
950
- provider: string,
951
- address: Address,
952
- descriptor: SetupAction
953
- ): Promise<SetupAction | undefined> {
954
- const metadata = await this.getProviderMetadata(provider)
955
- const earlier = metadata.setup
956
- .filter(
957
- (d) => d.kind !== 'automatic' && sequenceOf(d) < sequenceOf(descriptor)
958
- )
959
- .sort((a, b) => sequenceOf(a) - sequenceOf(b))
960
- if (earlier.length === 0) {
961
- return undefined
962
- }
963
-
964
- const satisfiedSetup = await this.resolveSatisfiedSetup(provider, address)
965
- const pending = earlier.filter((d) => !satisfiedSetup.has(d.type))
966
- if (pending.length === 0) {
967
- return undefined
968
- }
969
-
970
- const stageable = pending.filter((d) => d.kind === 'approval')
971
- const staged =
972
- stageable.length === 0
973
- ? []
974
- : await this.buildProviderSetupActions(provider, address, stageable)
975
- const stagedTypes = new Set(staged.map((action) => action.action))
976
- return pending.find(
977
- (d) => d.kind === 'preference' || stagedTypes.has(d.type)
978
- )
979
- }
980
-
981
986
  /**
982
987
  * Sign and submit one pre-staged setup `ActionStep` end-to-end.
983
988
  *
@@ -991,8 +996,7 @@ export class PerpsClient {
991
996
  * retries with a fresh step.
992
997
  *
993
998
  * @throws {PerpsError} When the step's action is not in the provider's
994
- * `setup` descriptors, or when a lower-`sequence` step on the same
995
- * checklist is still unsatisfied.
999
+ * `setup` descriptors.
996
1000
  * @public
997
1001
  */
998
1002
  async executeProviderSetupAction(params: {
@@ -1011,18 +1015,6 @@ export class PerpsClient {
1011
1015
  )
1012
1016
  }
1013
1017
 
1014
- const blocking = await this.findBlockingSetupStep(
1015
- provider,
1016
- address,
1017
- descriptor
1018
- )
1019
- if (blocking) {
1020
- throw new PerpsError(
1021
- PerpsErrorCode.SDKError,
1022
- `Setup step '${step.action}' for '${provider}' is blocked: '${blocking.type}' runs first and is not satisfied.`
1023
- )
1024
- }
1025
-
1026
1018
  const signed = await this.signProviderSetupAction(provider, address, step)
1027
1019
 
1028
1020
  // A client-executed setup action (Lighter SET_REFERRAL) produces no
@@ -1041,19 +1033,19 @@ export class PerpsClient {
1041
1033
  }
1042
1034
 
1043
1035
  /**
1044
- * Sign and submit a single preference change (a `preference` setup step such
1045
- * as Lighter `ACCOUNT_TYPE` or Hyperliquid `ACCOUNT_MODE`) end-to-end,
1046
- * throwing on a venue rejection.
1036
+ * Sign and submit a single provider-option change (a `Provider.options`
1037
+ * tunable such as Lighter `ACCOUNT_TYPE` or Hyperliquid `ACCOUNT_MODE`)
1038
+ * end-to-end, throwing on a venue rejection.
1047
1039
  *
1048
- * Preferences are dispatched through the same {@link execute} pipeline as
1049
- * trades, but unlike a trade a preference change is a single mandatory
1050
- * action: a per-action `success: false` (returned as a 200 OK) means the
1051
- * user's selection was rejected and must surface, not be silently dropped.
1052
- * This wrapper inspects the result and throws a {@link PerpsError} carrying
1053
- * the venue `error`, giving a preference the same throw contract that setup
1054
- * has via {@link executeProviderSetupAction}. The only structural difference
1055
- * is that a preference carries `params` (the selected value) rather than a
1056
- * pre-staged step.
1040
+ * Options are dispatched through the same {@link execute} pipeline as
1041
+ * trades, but unlike a trade an option change is a single mandatory action:
1042
+ * a per-action `success: false` (returned as a 200 OK) means the user's
1043
+ * selection was rejected and must surface, not be silently dropped. This
1044
+ * wrapper inspects the result and throws a {@link PerpsError} carrying the
1045
+ * venue `error`, giving options the same throw contract that setup has via
1046
+ * {@link executeProviderSetupAction}. The only structural difference is that
1047
+ * an option carries `params` (the selected value) rather than a pre-staged
1048
+ * step.
1057
1049
  *
1058
1050
  * `execute` itself is unchanged — it still returns results without throwing,
1059
1051
  * which the trade hooks rely on for partial-fill handling.
package/src/index.ts CHANGED
@@ -19,7 +19,11 @@ export { PerpsClient } from './client/PerpsClient.js'
19
19
  export { PerpsErrorMessage } from './errors/constants.js'
20
20
  export { PerpsError } from './errors/PerpsError.js'
21
21
  // Registries
22
- export { AssetRegistry, getAssetRegistry } from './registry/assetRegistry.js'
22
+ export {
23
+ AssetRegistry,
24
+ getAssetRegistry,
25
+ toAssetDisplay,
26
+ } from './registry/assetRegistry.js'
23
27
  export type { DefaultMarketProvider } from './registry/marketRegistry.js'
24
28
  export {
25
29
  DEFAULT_MARKET_ID,
@@ -152,6 +156,7 @@ export type {
152
156
  ProviderAccountExistsParams,
153
157
  ProviderGetAccountParams,
154
158
  ProviderGetActivityParams,
159
+ ProviderGetAvailableToTradeParams,
155
160
  ProviderGetDepositFlowParams,
156
161
  ProviderGetFillsParams,
157
162
  ProviderGetMarketSettingsParams,
@@ -1,4 +1,4 @@
1
- import type { Asset, AssetsResponse } from '@lifi/perps-types'
1
+ import type { Asset, AssetDisplay, AssetsResponse } from '@lifi/perps-types'
2
2
  import { PerpsErrorCode } from '@lifi/perps-types'
3
3
  import { PerpsError } from '../errors/PerpsError.js'
4
4
  import { buildUrl, request } from '../transport/request.js'
@@ -59,6 +59,21 @@ export class AssetRegistry extends ReferenceDataRegistry<Asset> {
59
59
  }
60
60
  }
61
61
 
62
+ /**
63
+ * Project an {@link Asset} to the identity and labels a display needs.
64
+ *
65
+ * @public
66
+ */
67
+ export const toAssetDisplay = (asset: Asset): AssetDisplay => ({
68
+ providerId: asset.providerId,
69
+ id: asset.id,
70
+ displaySymbol: asset.displaySymbol,
71
+ logoURI: asset.logoURI,
72
+ ...(asset.displayName === undefined
73
+ ? {}
74
+ : { displayName: asset.displayName }),
75
+ })
76
+
62
77
  const registries = new WeakMap<PerpsSDKClient, Map<string, AssetRegistry>>()
63
78
 
64
79
  /**
package/src/types/api.ts CHANGED
@@ -15,7 +15,7 @@ import type {
15
15
  OrderSide,
16
16
  OrderType,
17
17
  PortfolioHistoryRange,
18
- SetupAction,
18
+ ProviderAction,
19
19
  SignedActionStep,
20
20
  TimeInForce,
21
21
  TriggerOrderInput,
@@ -318,7 +318,7 @@ export interface GetPortfolioHistoryParams {
318
318
  */
319
319
  export interface SetupChecklistItem {
320
320
  /** The `Provider.setup` descriptor for this step. */
321
- descriptor: SetupAction
321
+ descriptor: ProviderAction
322
322
  /** Whether the step is already satisfied for this account. */
323
323
  satisfied: boolean
324
324
  }
@@ -331,10 +331,8 @@ export interface SetupChecklistItem {
331
331
  * to the provider's `setup` descriptor, which declares the step's signer and
332
332
  * scheme), so no signer-role partition is exposed here.
333
333
  *
334
- * `automatic` setup steps are NEVER included here — the SDK drains them
335
- * itself. A `preference` step is listed on `checklist` but never staged: it
336
- * does not gate trading, and the user re-enters it through
337
- * `PerpsClient.executeProviderOption`.
334
+ * `Provider.options` items are NEVER included here — they don't gate trading
335
+ * and are surfaced separately via `getAccount().settings`.
338
336
  *
339
337
  * @public
340
338
  */
@@ -351,9 +349,9 @@ export interface ProviderSetup {
351
349
  /** Whether all setup items are already satisfied (ready to trade) */
352
350
  isReady: boolean
353
351
  /**
354
- * The renderable onboarding list: every `approval` and `preference` setup
355
- * descriptor with its satisfied state, ordered by `sequence`. `automatic`
356
- * steps and conditional steps that staged no work for this account (see
352
+ * The renderable onboarding list: every USER-signed setup descriptor with
353
+ * its satisfied state, ordered by `sequence`. SDK-internal steps and
354
+ * conditional steps that staged no work for this account (see
357
355
  * `PerpsProviderPlugin.conditionalSetupActions`) are omitted. Consumers
358
356
  * render this list directly instead of joining `Provider.setup` metadata
359
357
  * with the staged `setup` steps.
@@ -385,14 +383,14 @@ export interface ExecuteProviderSetupResult {
385
383
  * backend's `AccountResponse` with a single SDK-projected `settings` array.
386
384
  *
387
385
  * `settings` contains exactly one `AccountConfigSetting` per descriptor on
388
- * `Provider.setup`, in that order. Index the projection by
389
- * `setting.type === descriptor.type` and read `setting.values[i].value` for
390
- * each `Param` the descriptor declared.
386
+ * `Provider.setup` + `Provider.options` (in that order). Index the
387
+ * projection by `setting.type === descriptor.type` and read
388
+ * `setting.values[i].value` for each `Param` the descriptor declared.
391
389
  *
392
390
  * @public
393
391
  */
394
392
  export interface GetAccountResult extends AccountResponse {
395
- /** SDK-projected current state of every `Provider.setup` descriptor. */
393
+ /** SDK-projected current state of every setup + options descriptor. */
396
394
  settings: AccountConfigSetting[]
397
395
  }
398
396
 
@@ -8,6 +8,7 @@ import type {
8
8
  ActionType,
9
9
  ActivitiesResponse,
10
10
  ActivityType,
11
+ AvailableToTrade,
11
12
  FillsResponse,
12
13
  Market,
13
14
  MarketRef,
@@ -22,9 +23,9 @@ import type {
22
23
  Position,
23
24
  PositionMarginConstraints,
24
25
  PositionsResponse,
26
+ ProviderAction,
25
27
  Quote,
26
28
  QuoteSide,
27
- SetupAction,
28
29
  SignedActionStep,
29
30
  SigningMethod,
30
31
  TradeType,
@@ -216,6 +217,17 @@ export interface ProviderGetMarketSettingsParams {
216
217
  market: MarketRef
217
218
  }
218
219
 
220
+ /**
221
+ * Read params for {@link PerpsProviderPlugin.getAvailableToTrade}.
222
+ *
223
+ * @public
224
+ */
225
+ export interface ProviderGetAvailableToTradeParams {
226
+ address: Address
227
+ /** The market's opaque `Market.id` (not a display symbol). */
228
+ marketId: string
229
+ }
230
+
219
231
  /**
220
232
  * Read params for {@link PerpsProvider.getOrders}.
221
233
  *
@@ -349,6 +361,17 @@ export interface PerpsProviderPlugin {
349
361
  */
350
362
  readonly type: string
351
363
 
364
+ /**
365
+ * Setup actions the provider completes on its own, without surfacing a card
366
+ * to the caller. `PerpsClient.checkSetup` drains each such pending step in
367
+ * place — building, signing, and executing it with the provider's own
368
+ * credentials — and omits it from the returned `setup` list. A descriptor
369
+ * whose `signers` include {@link PerpsSigner.USER} is never treated as
370
+ * internal, even when named here. Omit when the provider has no
371
+ * self-completed setup steps.
372
+ */
373
+ readonly internalSetupActions?: readonly ActionType[]
374
+
352
375
  /**
353
376
  * Setup actions that stage work only when the account needs remediation
354
377
  * (e.g. Hyperliquid's REVOKE_AGENT frees an agent slot only when every
@@ -431,6 +454,19 @@ export interface PerpsProviderPlugin {
431
454
  options?: SDKRequestOptions
432
455
  ): Promise<MarketSettings | undefined>
433
456
 
457
+ /**
458
+ * The amounts the account can still buy and sell on one market, in that
459
+ * market's margin asset. Optional because venues expose this unevenly:
460
+ * Hyperliquid reads it directly (`activeAssetData`), other venues publish
461
+ * only an account-scoped figure. `undefined` means the venue has nothing
462
+ * to read for this market, and the caller falls back to the account
463
+ * summary.
464
+ */
465
+ getAvailableToTrade?(
466
+ params: ProviderGetAvailableToTradeParams,
467
+ options?: SDKRequestOptions
468
+ ): Promise<AvailableToTrade | undefined>
469
+
434
470
  getOrders(
435
471
  params: ProviderGetOrdersParams,
436
472
  options?: SDKRequestOptions
@@ -540,11 +576,9 @@ export interface PerpsProviderPlugin {
540
576
 
541
577
  /**
542
578
  * Project a typed {@link AccountConfig} against the provider's `setup`
543
- * descriptors into `AccountConfigSetting[]`. Used by
579
+ * + `options` descriptors into `AccountConfigSetting[]`. Used by
544
580
  * `PerpsClient.getAccount` to attach a `settings` array to the response —
545
- * one entry per descriptor, in `setup` order. Every `preference` descriptor
546
- * carries a boolean `satisfied`, because `PerpsClient.checkSetup` reads a
547
- * preference's satisfied state from this projection alone.
581
+ * one entry per descriptor, in `setup`-then-`options` order.
548
582
  *
549
583
  * Implementations receive the union-typed `AccountConfig` and narrow on
550
584
  * `config.provider` themselves; the dispatcher in `PerpsClient` does not
@@ -552,7 +586,8 @@ export interface PerpsProviderPlugin {
552
586
  */
553
587
  projectConfig(
554
588
  config: AccountConfig,
555
- setup: SetupAction[]
589
+ setup: ProviderAction[],
590
+ options: ProviderAction[]
556
591
  ): AccountConfigSetting[]
557
592
 
558
593
  /**
package/src/version.ts CHANGED
@@ -10,4 +10,4 @@ export const name = '@lifi/perps-sdk'
10
10
  *
11
11
  * @public
12
12
  */
13
- export const version = '0.0.0-preview-5cdfc44'
13
+ export const version = '0.0.0-preview-b49efcc'