@lifi/perps-sdk 25.0.1 → 26.0.0

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 (88) hide show
  1. package/README.md +57 -3
  2. package/dist/cjs/client/PerpsClient.d.ts +5 -5
  3. package/dist/cjs/client/PerpsClient.d.ts.map +1 -1
  4. package/dist/cjs/client/PerpsClient.js +107 -87
  5. package/dist/cjs/client/PerpsClient.js.map +1 -1
  6. package/dist/cjs/errors/PerpsExecutionError.d.ts +9 -0
  7. package/dist/cjs/errors/PerpsExecutionError.d.ts.map +1 -0
  8. package/dist/cjs/errors/PerpsExecutionError.js +29 -0
  9. package/dist/cjs/errors/PerpsExecutionError.js.map +1 -0
  10. package/dist/cjs/index.d.ts +5 -4
  11. package/dist/cjs/index.d.ts.map +1 -1
  12. package/dist/cjs/index.js +14 -12
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/math/fills.d.ts +0 -1
  15. package/dist/cjs/math/fills.d.ts.map +1 -1
  16. package/dist/cjs/math/fills.js +0 -18
  17. package/dist/cjs/math/fills.js.map +1 -1
  18. package/dist/cjs/math/order.d.ts +5 -5
  19. package/dist/cjs/math/order.d.ts.map +1 -1
  20. package/dist/cjs/math/order.js +10 -10
  21. package/dist/cjs/math/order.js.map +1 -1
  22. package/dist/cjs/math/position.d.ts +2 -2
  23. package/dist/cjs/math/position.d.ts.map +1 -1
  24. package/dist/cjs/math/position.js +5 -5
  25. package/dist/cjs/math/position.js.map +1 -1
  26. package/dist/cjs/types/api.d.ts +1 -10
  27. package/dist/cjs/types/api.d.ts.map +1 -1
  28. package/dist/cjs/types/provider.d.ts +5 -1
  29. package/dist/cjs/types/provider.d.ts.map +1 -1
  30. package/dist/cjs/version.d.ts +1 -1
  31. package/dist/cjs/version.js +1 -1
  32. package/dist/esm/client/PerpsClient.d.ts +70 -50
  33. package/dist/esm/client/PerpsClient.d.ts.map +1 -1
  34. package/dist/esm/client/PerpsClient.js +176 -144
  35. package/dist/esm/client/PerpsClient.js.map +1 -1
  36. package/dist/esm/errors/PerpsExecutionError.d.ts +24 -0
  37. package/dist/esm/errors/PerpsExecutionError.d.ts.map +1 -0
  38. package/dist/esm/errors/PerpsExecutionError.js +39 -0
  39. package/dist/esm/errors/PerpsExecutionError.js.map +1 -0
  40. package/dist/esm/index.d.ts +5 -4
  41. package/dist/esm/index.d.ts.map +1 -1
  42. package/dist/esm/index.js +4 -3
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/esm/math/fills.d.ts +0 -9
  45. package/dist/esm/math/fills.d.ts.map +1 -1
  46. package/dist/esm/math/fills.js +0 -25
  47. package/dist/esm/math/fills.js.map +1 -1
  48. package/dist/esm/math/order.d.ts +6 -6
  49. package/dist/esm/math/order.d.ts.map +1 -1
  50. package/dist/esm/math/order.js +6 -6
  51. package/dist/esm/math/order.js.map +1 -1
  52. package/dist/esm/math/position.d.ts +2 -2
  53. package/dist/esm/math/position.d.ts.map +1 -1
  54. package/dist/esm/math/position.js +3 -3
  55. package/dist/esm/math/position.js.map +1 -1
  56. package/dist/esm/types/api.d.ts +1 -18
  57. package/dist/esm/types/api.d.ts.map +1 -1
  58. package/dist/esm/types/provider.d.ts +21 -4
  59. package/dist/esm/types/provider.d.ts.map +1 -1
  60. package/dist/esm/version.d.ts +1 -1
  61. package/dist/esm/version.js +1 -1
  62. package/dist/types/client/PerpsClient.d.ts +70 -50
  63. package/dist/types/client/PerpsClient.d.ts.map +1 -1
  64. package/dist/types/errors/PerpsExecutionError.d.ts +24 -0
  65. package/dist/types/errors/PerpsExecutionError.d.ts.map +1 -0
  66. package/dist/types/index.d.ts +5 -4
  67. package/dist/types/index.d.ts.map +1 -1
  68. package/dist/types/math/fills.d.ts +0 -9
  69. package/dist/types/math/fills.d.ts.map +1 -1
  70. package/dist/types/math/order.d.ts +6 -6
  71. package/dist/types/math/order.d.ts.map +1 -1
  72. package/dist/types/math/position.d.ts +2 -2
  73. package/dist/types/math/position.d.ts.map +1 -1
  74. package/dist/types/types/api.d.ts +1 -18
  75. package/dist/types/types/api.d.ts.map +1 -1
  76. package/dist/types/types/provider.d.ts +21 -4
  77. package/dist/types/types/provider.d.ts.map +1 -1
  78. package/dist/types/version.d.ts +1 -1
  79. package/package.json +2 -2
  80. package/src/client/PerpsClient.ts +224 -186
  81. package/src/errors/PerpsExecutionError.ts +54 -0
  82. package/src/index.ts +12 -8
  83. package/src/math/fills.ts +0 -29
  84. package/src/math/order.ts +11 -11
  85. package/src/math/position.ts +3 -3
  86. package/src/types/api.ts +0 -21
  87. package/src/types/provider.ts +22 -4
  88. package/src/version.ts +1 -1
@@ -5,6 +5,7 @@ import type {
5
5
  ActionParamsMap,
6
6
  ActionResult,
7
7
  ActionStep,
8
+ Asset,
8
9
  AvailableToTrade,
9
10
  CreateActionResponse,
10
11
  DecimalString,
@@ -29,11 +30,13 @@ import {
29
30
  PerpsErrorCode,
30
31
  PerpsSigner,
31
32
  SigningMethod,
33
+ UserActionStatus,
32
34
  } from '@lifi/perps-types'
33
35
  import type { Address } from 'viem'
34
36
  import { isDecimalStringGreaterThan } from '../decimal/compare.js'
35
37
  import { unknownToDecimalString } from '../decimal/parse.js'
36
38
  import { PerpsError } from '../errors/PerpsError.js'
39
+ import { rethrowWithKnownResults } from '../errors/PerpsExecutionError.js'
37
40
  import { getAssetRegistry, toAssetDisplay } from '../registry/assetRegistry.js'
38
41
  import { getMarketRegistry } from '../registry/marketRegistry.js'
39
42
  import { createAction } from '../services/createAction.js'
@@ -54,8 +57,6 @@ import type {
54
57
  CancelTwapOrderParams,
55
58
  CreateReferralCodeActionParams,
56
59
  ExecuteMetaActionParams,
57
- ExecuteProviderSetupParams,
58
- ExecuteProviderSetupResult,
59
60
  GetAccountResult,
60
61
  GetDepositFlowParams,
61
62
  GetPortfolioHistoryParams,
@@ -86,6 +87,7 @@ import type {
86
87
  PerpsSDKClient,
87
88
  SignActionProgress,
88
89
  SignActionsContext,
90
+ SignActionsResult,
89
91
  } from '../types/provider.js'
90
92
  import type {
91
93
  WithdrawableBalance,
@@ -212,15 +214,11 @@ function currentChoiceValue(
212
214
  return value === null ? null : String(value)
213
215
  }
214
216
 
215
- /** Throw the first per-action failure a 200 OK `/executeAction` carried. */
216
- function assertAllSucceeded(results: ActionResult[]): void {
217
- const failure = results.find((r) => !r.success)
218
- if (failure) {
219
- throw new PerpsError(
220
- failure.errorCode ?? PerpsErrorCode.ExchangeRejected,
221
- failure.error
222
- )
223
- }
217
+ function isAcknowledged(result: ActionResult): boolean {
218
+ return (
219
+ result.status === UserActionStatus.Submitted ||
220
+ result.status === UserActionStatus.Accepted
221
+ )
224
222
  }
225
223
 
226
224
  function isNonNegativeDecimal(value: string): boolean {
@@ -234,6 +232,25 @@ function isNonNegativeDecimal(value: string): boolean {
234
232
  }
235
233
  }
236
234
 
235
+ /** @throws {PerpsError} When `asset.minWithdrawalAmount` is not a decimal. */
236
+ function minWithdrawalAmountOf(asset: Asset): DecimalString | undefined {
237
+ if (asset.minWithdrawalAmount === undefined) {
238
+ return undefined
239
+ }
240
+ try {
241
+ return unknownToDecimalString(
242
+ asset.minWithdrawalAmount,
243
+ 'minWithdrawalAmount',
244
+ 'perps-sdk'
245
+ )
246
+ } catch {
247
+ throw new PerpsError(
248
+ PerpsErrorCode.SDKError,
249
+ `Asset '${asset.id}' field \`minWithdrawalAmount\` is not a valid decimal.`
250
+ )
251
+ }
252
+ }
253
+
237
254
  /**
238
255
  * Look up an action's descriptor in the provider's metadata. Throws if the
239
256
  * action isn't declared — defensive: better to fail loudly than to mis-sign.
@@ -431,10 +448,13 @@ export class PerpsClient {
431
448
  * step with the signer its own descriptor declares; the signed steps come
432
449
  * back in the batch's original order. The plugin owns every signing arm and
433
450
  * branches on `signer` internally, reading the end-user's wallet from the
434
- * {@link SignActionsContext} when an arm signs as the user.
451
+ * {@link SignActionsContext} when an arm signs as the user. Signing stops
452
+ * after the first group that executed a step without an acknowledgement.
435
453
  *
436
454
  * @throws {PerpsError} When a step's action is not declared by the provider,
437
455
  * or the plugin implements no `signActions`.
456
+ * @throws {PerpsExecutionError} When a group throws after an earlier group
457
+ * executed steps.
438
458
  */
439
459
  private async delegateSignActions(
440
460
  provider: string,
@@ -442,36 +462,90 @@ export class PerpsClient {
442
462
  metadata: Provider,
443
463
  actions: ActionStep[],
444
464
  onProgress?: (progress: SignActionProgress) => void
445
- ): Promise<SignedActionStep[]> {
465
+ ): Promise<SignActionsResult> {
446
466
  const plugin = this.requireProvider(provider)
447
- if (typeof plugin.signActions !== 'function') {
467
+ const signActions = plugin.signActions?.bind(plugin)
468
+ if (signActions === undefined) {
448
469
  throw new PerpsError(
449
470
  PerpsErrorCode.SDKError,
450
471
  `Provider '${provider}' does not implement signActions.`
451
472
  )
452
473
  }
453
474
  const signed: SignedActionStep[] = []
475
+ const executed: ActionResult[] = []
454
476
  for (const group of groupStepsByDescriptor(metadata, actions)) {
455
- const userWallet = await this.resolveSigningWallet(
456
- group.descriptor,
457
- group.steps
458
- )
459
- signed.push(
460
- ...(await plugin.signActions(
477
+ try {
478
+ const userWallet = await this.resolveSigningWallet(
479
+ group.descriptor,
480
+ group.steps
481
+ )
482
+ const result = await signActions(
461
483
  group.descriptor.signingMethod,
462
484
  group.steps,
463
485
  address,
464
486
  this.buildSignActionsContext(group.descriptor, userWallet, onProgress)
465
- ))
466
- )
487
+ )
488
+ signed.push(...result.signed)
489
+ executed.push(...result.executed)
490
+ } catch (error) {
491
+ rethrowWithKnownResults(executed, error)
492
+ }
493
+ if (!executed.every(isAcknowledged)) {
494
+ break
495
+ }
467
496
  }
468
- return signed
497
+ return { signed, executed }
469
498
  }
470
499
 
471
500
  /**
472
- * Let the plugin observe `/executeAction` per-step results before the core
473
- * surfaces failures — e.g. to evict a locally stored credential the venue
474
- * rejected.
501
+ * Submit `signed` to `/executeAction` when every step the plugin executed
502
+ * while signing was acknowledged, then resolve explorer links and notify the
503
+ * plugin. Resolves to the full report: `executed`, then the `/executeAction`
504
+ * results. An empty report means nothing executed.
505
+ *
506
+ * @throws {PerpsExecutionError} When the submission or the plugin callback
507
+ * throws after a result is known.
508
+ */
509
+ private async submitSignedActions(
510
+ provider: string,
511
+ address: Address,
512
+ action: ActionType,
513
+ signerAddress: Address | undefined,
514
+ { signed, executed }: SignActionsResult
515
+ ): Promise<ActionResult[]> {
516
+ let results = executed
517
+ if (signed.length > 0 && executed.every(isAcknowledged)) {
518
+ try {
519
+ const response = await executeAction(this.sdkClient, {
520
+ provider,
521
+ address,
522
+ // The submitting account: the plugin-resolved signer (Hyperliquid's
523
+ // agent) when present, else the end-user's address.
524
+ signerAddress: signerAddress ?? address,
525
+ action,
526
+ actions: signed,
527
+ })
528
+ results = [...executed, ...response.results]
529
+ } catch (error) {
530
+ rethrowWithKnownResults(executed, error)
531
+ }
532
+ }
533
+ if (results.length === 0) {
534
+ return results
535
+ }
536
+
537
+ try {
538
+ results = this.resolveExplorerLinks(provider, results)
539
+ await this.notifyExecuteResults(provider, address, results)
540
+ } catch (error) {
541
+ rethrowWithKnownResults(results, error)
542
+ }
543
+ return results
544
+ }
545
+
546
+ /**
547
+ * Let the plugin observe an execution report before the core returns it —
548
+ * e.g. to evict a locally stored credential the venue refused.
475
549
  */
476
550
  private async notifyExecuteResults(
477
551
  provider: string,
@@ -485,9 +559,10 @@ export class PerpsClient {
485
559
  }
486
560
 
487
561
  /**
488
- * Attach a provider-built explorer URL to every result the backend returned a
489
- * venue `txHash` on. The provider owns its explorer target, so core only asks
490
- * — a plugin without the hook leaves results untouched.
562
+ * Attach a provider-built explorer URL to every result that carries a venue
563
+ * `txHash`, whatever its status: an unacknowledged hash is what the user
564
+ * inspects. The provider owns its explorer target, so core only asks — a
565
+ * plugin without the hook leaves results untouched.
491
566
  */
492
567
  private resolveExplorerLinks(
493
568
  provider: string,
@@ -499,7 +574,7 @@ export class PerpsClient {
499
574
  return results
500
575
  }
501
576
  return results.map((result) => {
502
- if (!result.success || result.txHash === undefined) {
577
+ if (result.txHash === undefined) {
503
578
  return result
504
579
  }
505
580
  const explorerLink = resolveLink(result.txHash)
@@ -590,30 +665,47 @@ export class PerpsClient {
590
665
  }
591
666
 
592
667
  /**
593
- * Sign a single provider setup action step by delegating to the provider
594
- * plugin, which branches on the step's signing scheme internally. Lets
595
- * consumers collect signed setup actions without embedding per-method
596
- * signing logic.
597
- *
598
- * Returns `undefined` when the plugin executed the action entirely
599
- * client-side (e.g. Lighter's token-authenticated venue mutations), leaving
600
- * no backend-bound step to submit.
668
+ * Sign one provider setup step through the provider plugin and submit it.
669
+ * A step the plugin executed client-side (e.g. Lighter's token-authenticated
670
+ * venue mutations) reports the plugin's own result instead of an
671
+ * `/executeAction` result.
601
672
  *
602
- * @throws {PerpsError} When the step's action is not declared by the provider.
673
+ * @throws {PerpsError} When the step's action is not declared by the provider,
674
+ * or signing or submission fails before any result is known.
675
+ * @throws {PerpsExecutionError} When a later stage fails after a result is
676
+ * known.
603
677
  */
604
- private async signProviderSetupAction(
678
+ private async runProviderSetupStep(
605
679
  provider: string,
606
680
  address: Address,
607
681
  step: ActionStep
608
- ): Promise<SignedActionStep | undefined> {
682
+ ): Promise<ActionResult[]> {
609
683
  const metadata = await this.getProviderMetadata(provider)
610
- const [signed] = await this.delegateSignActions(
684
+ const descriptor = findActionDescriptor(metadata, step.action)
685
+ const signResult = await this.delegateSignActions(
611
686
  provider,
612
687
  address,
613
688
  metadata,
614
689
  [step]
615
690
  )
616
- return signed
691
+ let signerAddress: Address | undefined
692
+ try {
693
+ const request = await this.resolveActionRequest(
694
+ provider,
695
+ descriptor,
696
+ address
697
+ )
698
+ signerAddress = request.signerAddress
699
+ } catch (error) {
700
+ rethrowWithKnownResults(signResult.executed, error)
701
+ }
702
+ return this.submitSignedActions(
703
+ provider,
704
+ address,
705
+ step.action,
706
+ signerAddress,
707
+ signResult
708
+ )
617
709
  }
618
710
 
619
711
  /**
@@ -886,24 +978,12 @@ export class PerpsClient {
886
978
  if (asset === undefined) {
887
979
  return []
888
980
  }
889
- const minimum = asset.minWithdrawalAmount
890
- if (minimum !== undefined) {
891
- let floor: string
892
- try {
893
- floor = unknownToDecimalString(
894
- minimum,
895
- 'minWithdrawalAmount',
896
- 'perps-sdk'
897
- )
898
- } catch {
899
- throw new PerpsError(
900
- PerpsErrorCode.SDKError,
901
- `Asset '${asset.id}' field \`minWithdrawalAmount\` is not a valid decimal.`
902
- )
903
- }
904
- if (isDecimalStringGreaterThan(floor, row.available)) {
905
- return []
906
- }
981
+ const minimum = minWithdrawalAmountOf(asset)
982
+ if (
983
+ minimum !== undefined &&
984
+ isDecimalStringGreaterThan(minimum, row.available)
985
+ ) {
986
+ return []
907
987
  }
908
988
  if (row.withdrawalFee === undefined) {
909
989
  return [
@@ -939,12 +1019,16 @@ export class PerpsClient {
939
1019
  /**
940
1020
  * The withdrawal types `params.address` can choose per holding at
941
1021
  * `params.provider`, each with its cap and the `withdrawalOptions` to pass
942
- * unchanged in `WithdrawalParams`.
1022
+ * unchanged in `WithdrawalParams`. Every option whose `max` is below the
1023
+ * source asset's core `/assets` `minWithdrawalAmount` is removed, since no
1024
+ * amount can satisfy both limits; a source asset the provider's registry
1025
+ * does not carry keeps its options.
943
1026
  *
944
1027
  * @returns `undefined` when the registered plugin declares no withdrawal
945
1028
  * types read.
946
- * @throws {PerpsError} When the provider plugin is not registered, or when
947
- * the plugin read fails.
1029
+ * @throws {PerpsError} When the provider plugin is not registered, when
1030
+ * either the plugin read or the asset sync fails, or when a source asset's
1031
+ * `minWithdrawalAmount` is not a decimal.
948
1032
  * @public
949
1033
  */
950
1034
  async getWithdrawalTypes(
@@ -952,7 +1036,30 @@ export class PerpsClient {
952
1036
  options?: SDKRequestOptions
953
1037
  ): Promise<WithdrawalSourceTypes[] | undefined> {
954
1038
  const plugin = this.requireProvider(params.provider)
955
- return plugin.getWithdrawalTypes?.({ address: params.address }, options)
1039
+ const sources = await plugin.getWithdrawalTypes?.(
1040
+ { address: params.address },
1041
+ options
1042
+ )
1043
+ if (sources === undefined) {
1044
+ return undefined
1045
+ }
1046
+
1047
+ const registry = getAssetRegistry(this.sdkClient, params.provider)
1048
+ await registry.sync()
1049
+ return sources.map((row) => {
1050
+ const asset = registry.get(row.source.asset.id)
1051
+ const minimum =
1052
+ asset === undefined ? undefined : minWithdrawalAmountOf(asset)
1053
+ if (minimum === undefined) {
1054
+ return row
1055
+ }
1056
+ return {
1057
+ ...row,
1058
+ options: row.options.filter(
1059
+ (option) => !isDecimalStringGreaterThan(minimum, option.max)
1060
+ ),
1061
+ }
1062
+ })
956
1063
  }
957
1064
 
958
1065
  /**
@@ -1107,9 +1214,9 @@ export class PerpsClient {
1107
1214
  * deferred while any staged user-facing step with a lower `sequence` is
1108
1215
  * outstanding — it cannot succeed before its prerequisite (e.g. SET_REFERRAL
1109
1216
  * authenticates with the credential REGISTER_API_KEY installs) and each
1110
- * doomed attempt is venue traffic. A drain failure is swallowed so it never
1111
- * blocks setup — the step stays unsatisfied and is retried on a later
1112
- * `checkSetup`.
1217
+ * doomed attempt is venue traffic. A drain failure, thrown or reported, is
1218
+ * swallowed so it never blocks setup — the step stays unsatisfied and is
1219
+ * retried on a later `checkSetup`.
1113
1220
  */
1114
1221
  private async drainSetup(
1115
1222
  provider: string,
@@ -1131,18 +1238,18 @@ export class PerpsClient {
1131
1238
  descriptor,
1132
1239
  ])
1133
1240
  for (const step of steps) {
1134
- const signed = await this.signProviderSetupAction(
1241
+ const results = await this.runProviderSetupStep(
1135
1242
  provider,
1136
1243
  address,
1137
1244
  step
1138
1245
  )
1139
- if (signed !== undefined) {
1140
- await this.executeProviderSetup({
1141
- provider,
1142
- address,
1143
- setup: [step],
1144
- signedActions: [signed],
1145
- })
1246
+ const failure = results.find((result) => !isAcknowledged(result))
1247
+ if (failure !== undefined) {
1248
+ console.debug(
1249
+ `[perps-sdk] setup step '${descriptor.type}' for '${provider}' reported '${failure.status}'; will retry on the next checkSetup.`,
1250
+ failure
1251
+ )
1252
+ break
1146
1253
  }
1147
1254
  }
1148
1255
  } catch (error) {
@@ -1221,49 +1328,6 @@ export class PerpsClient {
1221
1328
  return { actions }
1222
1329
  }
1223
1330
 
1224
- /**
1225
- * Submit the signed setup steps returned by `checkSetup` (and signed by the
1226
- * caller / `signProviderSetupAction`). Routes the batch on the first step's
1227
- * action and lets the plugin contribute that action's `signerAddress`.
1228
- * Throws on any per-step venue rejection.
1229
- */
1230
- private async executeProviderSetup(
1231
- params: ExecuteProviderSetupParams
1232
- ): Promise<ExecuteProviderSetupResult> {
1233
- const { provider, address, setup, signedActions } = params
1234
-
1235
- if (signedActions.length === 0) {
1236
- return { results: { results: [] } }
1237
- }
1238
-
1239
- const action = setup[0]?.action ?? signedActions[0].action
1240
- const metadata = await this.getProviderMetadata(provider)
1241
- const descriptor = findActionDescriptor(metadata, action)
1242
- const { signerAddress } = await this.resolveActionRequest(
1243
- provider,
1244
- descriptor,
1245
- address
1246
- )
1247
-
1248
- const response = await executeAction(this.sdkClient, {
1249
- provider,
1250
- address,
1251
- // The submitting account: the plugin-resolved signer (Hyperliquid's
1252
- // agent) when present, else the end-user's address.
1253
- signerAddress: signerAddress ?? address,
1254
- action,
1255
- actions: signedActions,
1256
- })
1257
-
1258
- const results = this.resolveExplorerLinks(provider, response.results)
1259
-
1260
- await this.notifyExecuteResults(provider, address, results)
1261
-
1262
- assertAllSucceeded(results)
1263
-
1264
- return { results: { results } }
1265
- }
1266
-
1267
1331
  /**
1268
1332
  * Sign and submit one pre-staged setup `ActionStep` end-to-end.
1269
1333
  *
@@ -1272,9 +1336,15 @@ export class PerpsClient {
1272
1336
  * prerequisites against that same snapshot, without another `createAction`
1273
1337
  * round-trip.
1274
1338
  *
1339
+ * Resolves to the step's execution report. A refused or indeterminate step
1340
+ * is a result in the report, not an exception.
1341
+ *
1275
1342
  * @throws {PerpsError} When the step's action is not in the provider's
1276
- * `setup` descriptors, or when a lower-`sequence` step on the same
1277
- * checklist, a choice included, is still unsatisfied.
1343
+ * `setup` descriptors, when a lower-`sequence` step on the same
1344
+ * checklist, a choice included, is still unsatisfied, or when signing or
1345
+ * submission fails before any result is known.
1346
+ * @throws {PerpsExecutionError} When a later stage fails after a result is
1347
+ * known; its `response` holds the known results.
1278
1348
  * @public
1279
1349
  */
1280
1350
  async executeProviderSetupAction(params: {
@@ -1282,7 +1352,7 @@ export class PerpsClient {
1282
1352
  address: Address
1283
1353
  step: ActionStep
1284
1354
  checklist: SetupChecklistItem[]
1285
- }): Promise<void> {
1355
+ }): Promise<ExecuteActionResponse> {
1286
1356
  const { provider, address, step, checklist } = params
1287
1357
 
1288
1358
  const metadata = await this.getProviderMetadata(provider)
@@ -1305,52 +1375,34 @@ export class PerpsClient {
1305
1375
  )
1306
1376
  }
1307
1377
 
1308
- const signed = await this.signProviderSetupAction(provider, address, step)
1309
-
1310
- // A client-executed setup action (Lighter SET_REFERRAL) produces no
1311
- // backend-bound step — it already ran during signing, so there is nothing
1312
- // to submit.
1313
- if (signed === undefined) {
1314
- return
1378
+ return {
1379
+ results: await this.runProviderSetupStep(provider, address, step),
1315
1380
  }
1316
-
1317
- await this.executeProviderSetup({
1318
- provider,
1319
- address,
1320
- setup: [step],
1321
- signedActions: [signed],
1322
- })
1323
1381
  }
1324
1382
 
1325
1383
  /**
1326
1384
  * Execute one option of a choice setup step (Lighter `ACCOUNT_TYPE`,
1327
1385
  * Hyperliquid `ACCOUNT_MODE`) end-to-end with its bound `{ type, params }`,
1328
- * throwing on a venue rejection.
1386
+ * through the same {@link execute} pipeline as a trade.
1329
1387
  *
1330
- * The option runs through the same {@link execute} pipeline as a trade, but
1331
- * a per-action `success: false` (returned as a 200 OK) means the user's
1332
- * selection was rejected and must surface, not be silently dropped.
1388
+ * Resolves to the execution report. Read each result's `status`: a refused
1389
+ * selection is a `Failed` result, not an exception.
1333
1390
  *
1334
- * @throws {PerpsError} Carrying the venue error when any returned result has
1335
- * `success: false`, under that result's `errorCode` when the backend
1336
- * classified the failure and `PerpsErrorCode.ExchangeRejected` otherwise;
1337
- * also the errors `execute` itself can throw (unregistered provider, no
1338
- * signer, signing failure).
1391
+ * @throws {PerpsError} The errors {@link execute} throws.
1339
1392
  * @public
1340
1393
  */
1341
1394
  async executeProviderOption(params: {
1342
1395
  provider: string
1343
1396
  address: Address
1344
1397
  option: SetupOption
1345
- }): Promise<void> {
1398
+ }): Promise<ExecuteActionResponse> {
1346
1399
  const { provider, address, option } = params
1347
- const { results } = await this.execute({
1400
+ return this.execute({
1348
1401
  provider,
1349
1402
  address,
1350
1403
  action: option.type,
1351
1404
  params: option.params,
1352
1405
  })
1353
- assertAllSucceeded(results)
1354
1406
  }
1355
1407
 
1356
1408
  /**
@@ -1358,16 +1410,18 @@ export class PerpsClient {
1358
1410
  * create, sign and execute, signed with that action's `Provider.actions`
1359
1411
  * descriptor. The step stages again on the next {@link checkSetup}.
1360
1412
  *
1413
+ * Resolves to the execution report.
1414
+ *
1361
1415
  * @throws {PerpsError} `SDKError` when `step.revoke` is `null` or when
1362
1416
  * `createAction` stages nothing to revoke; otherwise the errors
1363
- * {@link executeProviderOption} throws.
1417
+ * {@link execute} throws.
1364
1418
  * @public
1365
1419
  */
1366
1420
  async executeProviderRevoke(params: {
1367
1421
  provider: string
1368
1422
  address: Address
1369
1423
  step: SetupAction
1370
- }): Promise<void> {
1424
+ }): Promise<ExecuteActionResponse> {
1371
1425
  const { provider, address, step } = params
1372
1426
  if (step.revoke === null) {
1373
1427
  throw new PerpsError(
@@ -1387,7 +1441,7 @@ export class PerpsClient {
1387
1441
  `Nothing to revoke for setup step '${step.type}' on '${provider}'.`
1388
1442
  )
1389
1443
  }
1390
- assertAllSucceeded(results)
1444
+ return { results }
1391
1445
  }
1392
1446
 
1393
1447
  /**
@@ -1555,8 +1609,15 @@ export class PerpsClient {
1555
1609
  * descriptor's scheme and signer internally), and submit. Core stays
1556
1610
  * signer-agnostic — the plugin owns WHO signs and HOW.
1557
1611
  *
1612
+ * Resolves to the execution report: one result per executed step, in
1613
+ * execution order, each with its own `status`. An empty report means
1614
+ * nothing executed.
1615
+ *
1558
1616
  * @throws {PerpsError} When the action is not declared by the provider,
1559
- * the plugin cannot sign it, or submission fails.
1617
+ * the plugin cannot sign it, or submission fails before any result is
1618
+ * known.
1619
+ * @throws {PerpsExecutionError} When a later stage fails after a result is
1620
+ * known; its `response` holds the known results.
1560
1621
  * @public
1561
1622
  */
1562
1623
  async execute<T extends ActionType>(params: {
@@ -1598,7 +1659,7 @@ export class PerpsClient {
1598
1659
  } as ActionParamsMap[T],
1599
1660
  })
1600
1661
 
1601
- const signedActions = await this.delegateSignActions(
1662
+ const signResult = await this.delegateSignActions(
1602
1663
  provider,
1603
1664
  address,
1604
1665
  metadata,
@@ -1606,27 +1667,13 @@ export class PerpsClient {
1606
1667
  onProgress
1607
1668
  )
1608
1669
 
1609
- // A plugin may execute an action entirely client-side (e.g. Lighter's
1610
- // token-authenticated venue mutations), leaving no backend-bound step. With
1611
- // nothing to submit, skip the `/executeAction` hop.
1612
- if (signedActions.length === 0) {
1613
- return { staged: actions, results: [] }
1614
- }
1615
-
1616
- const response = await executeAction(this.sdkClient, {
1670
+ const results = await this.submitSignedActions(
1617
1671
  provider,
1618
1672
  address,
1619
- // The submitting account: the plugin-resolved signer (Hyperliquid's
1620
- // agent) when present, else the end-user's address.
1621
- signerAddress: signerAddress ?? address,
1622
1673
  action,
1623
- actions: signedActions,
1624
- })
1625
-
1626
- const results = this.resolveExplorerLinks(provider, response.results)
1627
-
1628
- await this.notifyExecuteResults(provider, address, results)
1629
-
1674
+ signerAddress,
1675
+ signResult
1676
+ )
1630
1677
  return { staged: actions, results }
1631
1678
  }
1632
1679
 
@@ -1682,12 +1729,13 @@ export class PerpsClient {
1682
1729
  * typed data with the configured user wallet.
1683
1730
  *
1684
1731
  * The backend returns at most one step. Resolves to `{ results: [] }` when it
1685
- * returns none, meaning no consent is outstanding for this address.
1732
+ * returns none, meaning no consent is outstanding for this address. Else it
1733
+ * resolves to the backend's execution report; read each result's `status`.
1686
1734
  *
1687
1735
  * @throws {PerpsError} When no user wallet is configured, the backend returns
1688
- * more than one step or a non-EIP-712 step, or the submitted step reports a
1689
- * failed result. A signature refusal from the wallet client propagates as
1690
- * the wallet's own error.
1736
+ * more than one step or a non-EIP-712 step, or the submission fails. A
1737
+ * signature refusal from the wallet client propagates as the wallet's own
1738
+ * error.
1691
1739
  * @public
1692
1740
  */
1693
1741
  async executeMetaAction<T extends MetaActionType>(
@@ -1722,21 +1770,11 @@ export class PerpsClient {
1722
1770
  const wallet = await this.resolveMetaSigningWallet(actions)
1723
1771
  const signature = await signTypedDataWithSigner(wallet, step.typedData)
1724
1772
 
1725
- const response = await executeAction(this.sdkClient, {
1773
+ return executeAction(this.sdkClient, {
1726
1774
  provider: META_PROVIDER,
1727
1775
  address,
1728
1776
  action,
1729
1777
  actions: [{ ...step, signature }],
1730
1778
  })
1731
-
1732
- const failure = response.results.find((r) => !r.success)
1733
- if (failure) {
1734
- throw new PerpsError(
1735
- failure.errorCode ?? PerpsErrorCode.ExchangeRejected,
1736
- failure.error
1737
- )
1738
- }
1739
-
1740
- return response
1741
1779
  }
1742
1780
  }