@lifi/perps-sdk 18.1.0 → 19.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.
- package/dist/cjs/client/PerpsClient.d.ts +13 -7
- package/dist/cjs/client/PerpsClient.d.ts.map +1 -1
- package/dist/cjs/client/PerpsClient.js +141 -52
- package/dist/cjs/client/PerpsClient.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/api.d.ts +4 -2
- package/dist/cjs/types/api.d.ts.map +1 -1
- package/dist/cjs/types/provider.d.ts +4 -5
- package/dist/cjs/types/provider.d.ts.map +1 -1
- package/dist/cjs/utils/setupActions.d.ts +4 -2
- package/dist/cjs/utils/setupActions.d.ts.map +1 -1
- package/dist/cjs/utils/setupActions.js +9 -1
- package/dist/cjs/utils/setupActions.js.map +1 -1
- package/dist/cjs/utils/switchChain.js +1 -1
- package/dist/cjs/utils/switchChain.js.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client/PerpsClient.d.ts +62 -54
- package/dist/esm/client/PerpsClient.d.ts.map +1 -1
- package/dist/esm/client/PerpsClient.js +204 -100
- package/dist/esm/client/PerpsClient.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/api.d.ts +31 -14
- package/dist/esm/types/api.d.ts.map +1 -1
- package/dist/esm/types/provider.d.ts +21 -27
- package/dist/esm/types/provider.d.ts.map +1 -1
- package/dist/esm/utils/setupActions.d.ts +17 -8
- package/dist/esm/utils/setupActions.d.ts.map +1 -1
- package/dist/esm/utils/setupActions.js +20 -7
- package/dist/esm/utils/setupActions.js.map +1 -1
- package/dist/esm/utils/switchChain.js +1 -1
- package/dist/esm/utils/switchChain.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/types/client/PerpsClient.d.ts +62 -54
- package/dist/types/client/PerpsClient.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types/api.d.ts +31 -14
- package/dist/types/types/api.d.ts.map +1 -1
- package/dist/types/types/provider.d.ts +21 -27
- package/dist/types/types/provider.d.ts.map +1 -1
- package/dist/types/utils/setupActions.d.ts +17 -8
- package/dist/types/utils/setupActions.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/client/PerpsClient.ts +284 -129
- package/src/index.ts +4 -1
- package/src/types/api.ts +32 -14
- package/src/types/provider.ts +21 -29
- package/src/utils/setupActions.ts +27 -13
- package/src/utils/switchChain.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AccountConfigSetting,
|
|
2
3
|
AccountResponse,
|
|
3
4
|
AccountSummary,
|
|
4
5
|
ActionParamsMap,
|
|
@@ -16,9 +17,12 @@ import type {
|
|
|
16
17
|
Position,
|
|
17
18
|
Provider,
|
|
18
19
|
ProviderAction,
|
|
20
|
+
SetupAction,
|
|
21
|
+
SetupOption,
|
|
19
22
|
SignedActionStep,
|
|
20
23
|
} from '@lifi/perps-types'
|
|
21
24
|
import {
|
|
25
|
+
ActionRelay,
|
|
22
26
|
ActionType,
|
|
23
27
|
META_PROVIDER,
|
|
24
28
|
PerpsErrorCode,
|
|
@@ -63,6 +67,7 @@ import type {
|
|
|
63
67
|
PlaceTwapOrderParams,
|
|
64
68
|
ProviderSetup,
|
|
65
69
|
SendAssetActionParams,
|
|
70
|
+
SetupChecklistItem,
|
|
66
71
|
SubmitOnboardingParams,
|
|
67
72
|
WithdrawParams,
|
|
68
73
|
} from '../types/api.js'
|
|
@@ -80,6 +85,7 @@ import type {
|
|
|
80
85
|
SignActionsContext,
|
|
81
86
|
} from '../types/provider.js'
|
|
82
87
|
import type { WithdrawableBalance, WithdrawFlow } from '../types/withdrawal.js'
|
|
88
|
+
import { isUserFacingSetupStep } from '../utils/setupActions.js'
|
|
83
89
|
import { signTypedDataWithSigner } from '../utils/signTypedData.js'
|
|
84
90
|
import {
|
|
85
91
|
eip712DomainChainId,
|
|
@@ -89,6 +95,127 @@ import {
|
|
|
89
95
|
import { createPerpsClient } from './createPerpsClient.js'
|
|
90
96
|
import { requireProvider as resolveProvider } from './requireProvider.js'
|
|
91
97
|
|
|
98
|
+
/** Absent `sequence` sorts last, so an unordered step gates nothing. */
|
|
99
|
+
function sequenceOf(descriptor: ProviderAction): number {
|
|
100
|
+
return descriptor.sequence ?? Number.MAX_SAFE_INTEGER
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const SIGNERS: ReadonlySet<string> = new Set(Object.values(PerpsSigner))
|
|
104
|
+
const RELAYS: ReadonlySet<string> = new Set(Object.values(ActionRelay))
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Reject provider metadata whose steps the SDK cannot classify. A step with no
|
|
108
|
+
* known `signer`, `relay`, `options` or `revoke` would otherwise read as
|
|
109
|
+
* satisfied and report a not-onboarded account as ready.
|
|
110
|
+
*/
|
|
111
|
+
function assertSetupContract(metadata: Provider): void {
|
|
112
|
+
const reject = (step: ActionType, problem: string): never => {
|
|
113
|
+
throw new PerpsError(
|
|
114
|
+
PerpsErrorCode.SDKError,
|
|
115
|
+
`Provider '${metadata.key}' step '${step}' ${problem}; this SDK needs a ` +
|
|
116
|
+
`backend that emits signer, relay, options and revoke.`
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
for (const descriptor of [...metadata.setup, ...metadata.actions]) {
|
|
120
|
+
if (!SIGNERS.has(descriptor.signer)) {
|
|
121
|
+
reject(descriptor.type, 'declares no known signer')
|
|
122
|
+
}
|
|
123
|
+
if (!RELAYS.has(descriptor.relay)) {
|
|
124
|
+
reject(descriptor.type, 'declares no known relay')
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const actionTypes = new Set(metadata.actions.map((d) => d.type))
|
|
128
|
+
for (const descriptor of metadata.setup) {
|
|
129
|
+
if (descriptor.options === undefined) {
|
|
130
|
+
reject(descriptor.type, 'declares no options')
|
|
131
|
+
}
|
|
132
|
+
if (descriptor.options !== null && descriptor.options.length === 0) {
|
|
133
|
+
reject(descriptor.type, 'declares an empty options list')
|
|
134
|
+
}
|
|
135
|
+
if (descriptor.revoke === undefined) {
|
|
136
|
+
reject(descriptor.type, 'declares no revoke')
|
|
137
|
+
}
|
|
138
|
+
if (descriptor.revoke !== null && !actionTypes.has(descriptor.revoke)) {
|
|
139
|
+
reject(
|
|
140
|
+
descriptor.type,
|
|
141
|
+
`revokes with '${descriptor.revoke}', which Provider.actions does not declare`
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
const defaults = (descriptor.options ?? []).filter(
|
|
145
|
+
(option) => option.default === true
|
|
146
|
+
)
|
|
147
|
+
if (defaults.length > 1) {
|
|
148
|
+
reject(descriptor.type, 'marks more than one option default')
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Only a USER-signed non-choice step is staged for the user to sign. */
|
|
154
|
+
function isStageable(descriptor: SetupAction): boolean {
|
|
155
|
+
return descriptor.options === null && descriptor.signer === PerpsSigner.USER
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function satisfiedTypes(settings: AccountConfigSetting[]): Set<ActionType> {
|
|
159
|
+
return new Set(
|
|
160
|
+
settings.filter((setting) => setting.satisfied).map((s) => s.type)
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The choice option whose every bound param equals the projected value. */
|
|
165
|
+
function selectedOption(
|
|
166
|
+
descriptor: SetupAction,
|
|
167
|
+
settings: AccountConfigSetting[]
|
|
168
|
+
): SetupOption | null {
|
|
169
|
+
if (descriptor.options === null) {
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
const values =
|
|
173
|
+
settings.find((setting) => setting.type === descriptor.type)?.values ?? []
|
|
174
|
+
return (
|
|
175
|
+
descriptor.options.find((option) => {
|
|
176
|
+
const bound = Object.entries(option.params)
|
|
177
|
+
return (
|
|
178
|
+
bound.length > 0 &&
|
|
179
|
+
bound.every(([name, value]) =>
|
|
180
|
+
values.some((v) => v.name === name && v.value === value)
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
}) ?? null
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The projected value of the param a choice's options bind, as the venue
|
|
189
|
+
* names it, whether or not an option binds that value.
|
|
190
|
+
*/
|
|
191
|
+
function currentChoiceValue(
|
|
192
|
+
descriptor: SetupAction,
|
|
193
|
+
settings: AccountConfigSetting[]
|
|
194
|
+
): string | null {
|
|
195
|
+
if (descriptor.options === null) {
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
const names = new Set(
|
|
199
|
+
descriptor.options.flatMap((option) => Object.keys(option.params))
|
|
200
|
+
)
|
|
201
|
+
const value =
|
|
202
|
+
settings
|
|
203
|
+
.find((setting) => setting.type === descriptor.type)
|
|
204
|
+
?.values.find((v) => names.has(v.name))?.value ?? null
|
|
205
|
+
return value === null ? null : String(value)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Throw the first per-action failure a 200 OK `/executeAction` carried. */
|
|
209
|
+
function assertAllSucceeded(results: ActionResult[]): void {
|
|
210
|
+
const failure = results.find((r) => !r.success)
|
|
211
|
+
if (failure) {
|
|
212
|
+
throw new PerpsError(
|
|
213
|
+
failure.errorCode ?? PerpsErrorCode.ExchangeRejected,
|
|
214
|
+
failure.error
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
92
219
|
function isNonNegativeDecimal(value: string): boolean {
|
|
93
220
|
try {
|
|
94
221
|
return new Big(value).gte(0)
|
|
@@ -105,11 +232,9 @@ function findActionDescriptor(
|
|
|
105
232
|
metadata: Provider,
|
|
106
233
|
action: ActionType
|
|
107
234
|
): ProviderAction {
|
|
108
|
-
const descriptor = [
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
...metadata.actions,
|
|
112
|
-
].find((d) => d.type === action)
|
|
235
|
+
const descriptor = [...metadata.setup, ...metadata.actions].find(
|
|
236
|
+
(d) => d.type === action
|
|
237
|
+
)
|
|
113
238
|
if (!descriptor) {
|
|
114
239
|
throw new PerpsError(
|
|
115
240
|
PerpsErrorCode.SDKError,
|
|
@@ -171,7 +296,7 @@ export class PerpsClient {
|
|
|
171
296
|
|
|
172
297
|
/**
|
|
173
298
|
* Set or update the end-user's wallet. Used whenever an action's descriptor
|
|
174
|
-
* names
|
|
299
|
+
* names `PerpsSigner.USER` as its `signer`. Pass undefined to clear.
|
|
175
300
|
*
|
|
176
301
|
* @public
|
|
177
302
|
*/
|
|
@@ -205,17 +330,15 @@ export class PerpsClient {
|
|
|
205
330
|
}
|
|
206
331
|
|
|
207
332
|
private async getProviderMetadata(provider: string): Promise<Provider> {
|
|
208
|
-
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
this.providerMetadataCache.set(d.key, d)
|
|
333
|
+
let metadata = this.providerMetadataCache.get(provider)
|
|
334
|
+
if (!metadata) {
|
|
335
|
+
const { providers } = await getProviders(this.sdkClient)
|
|
336
|
+
for (const d of providers) {
|
|
337
|
+
this.providerMetadataCache.set(d.key, d)
|
|
338
|
+
}
|
|
339
|
+
metadata = this.providerMetadataCache.get(provider)
|
|
216
340
|
}
|
|
217
341
|
|
|
218
|
-
const metadata = this.providerMetadataCache.get(provider)
|
|
219
342
|
if (!metadata) {
|
|
220
343
|
const error = new PerpsError(
|
|
221
344
|
PerpsErrorCode.SDKError,
|
|
@@ -224,6 +347,7 @@ export class PerpsClient {
|
|
|
224
347
|
error.tool = '@lifi/perps-sdk'
|
|
225
348
|
throw error
|
|
226
349
|
}
|
|
350
|
+
assertSetupContract(metadata)
|
|
227
351
|
return metadata
|
|
228
352
|
}
|
|
229
353
|
|
|
@@ -256,7 +380,7 @@ export class PerpsClient {
|
|
|
256
380
|
* Ask the provider plugin for the signer-bearing wire fields of `action` —
|
|
257
381
|
* the on-wire `signerAddress` and any signer-derived params (e.g.
|
|
258
382
|
* Hyperliquid's `agentAddress` for `APPROVE_AGENT`). Forwards the descriptor's
|
|
259
|
-
* `
|
|
383
|
+
* `signer` so the plugin can branch on signer role. Returns empty when the
|
|
260
384
|
* plugin signs as the user or with a non-EVM credential. Core constructs no
|
|
261
385
|
* `signerAddress` itself; signer identity is plugin-owned.
|
|
262
386
|
*/
|
|
@@ -272,17 +396,17 @@ export class PerpsClient {
|
|
|
272
396
|
return plugin.resolveActionRequest(
|
|
273
397
|
descriptor.type,
|
|
274
398
|
address,
|
|
275
|
-
descriptor.
|
|
399
|
+
descriptor.signer
|
|
276
400
|
)
|
|
277
401
|
}
|
|
278
402
|
|
|
279
403
|
/**
|
|
280
404
|
* Delegate signing of `actions` to the provider plugin, one call per
|
|
281
405
|
* consecutive run of steps sharing a descriptor. Each call carries that
|
|
282
|
-
* descriptor's `signingMethod` and `
|
|
406
|
+
* descriptor's `signingMethod` and `signer`, so a mixed batch signs every
|
|
283
407
|
* step with the signer its own descriptor declares; the signed steps come
|
|
284
408
|
* back in the batch's original order. The plugin owns every signing arm and
|
|
285
|
-
* branches on `
|
|
409
|
+
* branches on `signer` internally, reading the end-user's wallet from the
|
|
286
410
|
* {@link SignActionsContext} when an arm signs as the user.
|
|
287
411
|
*
|
|
288
412
|
* @throws {PerpsError} When a step's action is not declared by the provider,
|
|
@@ -405,9 +529,9 @@ export class PerpsClient {
|
|
|
405
529
|
|
|
406
530
|
/**
|
|
407
531
|
* Assemble the per-call context the provider plugin needs in order to sign:
|
|
408
|
-
* the end-user's wallet and the descriptor's declared `
|
|
409
|
-
* forwards `
|
|
410
|
-
* branch on
|
|
532
|
+
* the end-user's wallet and the descriptor's declared `signer`. Core
|
|
533
|
+
* forwards `signer` as data so the plugin can pick WHO signs; it does not
|
|
534
|
+
* branch on it. Provider-owned session credentials (the Hyperliquid agent
|
|
411
535
|
* keypair, Lighter's API key) are resolved inside the provider's
|
|
412
536
|
* `signActions`, not threaded through here.
|
|
413
537
|
*
|
|
@@ -425,7 +549,7 @@ export class PerpsClient {
|
|
|
425
549
|
userWallet?: PerpsClientSigner,
|
|
426
550
|
onProgress?: (progress: SignActionProgress) => void
|
|
427
551
|
): SignActionsContext {
|
|
428
|
-
const ctx: SignActionsContext = {
|
|
552
|
+
const ctx: SignActionsContext = { signer: descriptor.signer }
|
|
429
553
|
if (onProgress !== undefined) {
|
|
430
554
|
ctx.onProgress = onProgress
|
|
431
555
|
}
|
|
@@ -501,7 +625,7 @@ export class PerpsClient {
|
|
|
501
625
|
/**
|
|
502
626
|
* Fetch the user's account state from the backend and attach the
|
|
503
627
|
* SDK-projected `settings` array — one `AccountConfigSetting` per
|
|
504
|
-
* descriptor on `Provider.setup
|
|
628
|
+
* descriptor on `Provider.setup`. Callers read
|
|
505
629
|
* `result.settings` directly without re-deriving values from the typed
|
|
506
630
|
* `AccountConfig`.
|
|
507
631
|
*
|
|
@@ -518,11 +642,7 @@ export class PerpsClient {
|
|
|
518
642
|
fetchAccount(this.sdkClient, params),
|
|
519
643
|
this.getProviderMetadata(params.provider),
|
|
520
644
|
])
|
|
521
|
-
const settings = plugin.projectConfig(
|
|
522
|
-
response.config,
|
|
523
|
-
metadata.setup,
|
|
524
|
-
metadata.options
|
|
525
|
-
)
|
|
645
|
+
const settings = plugin.projectConfig(response.config, metadata.setup)
|
|
526
646
|
return { ...response, settings }
|
|
527
647
|
}
|
|
528
648
|
|
|
@@ -773,15 +893,17 @@ export class PerpsClient {
|
|
|
773
893
|
}
|
|
774
894
|
|
|
775
895
|
/**
|
|
776
|
-
* Return the
|
|
777
|
-
*
|
|
778
|
-
* `checklist` carries the renderable onboarding list: every
|
|
779
|
-
*
|
|
780
|
-
* omitted.
|
|
896
|
+
* Return the setup steps this account must still sign as a flat,
|
|
897
|
+
* self-describing list. Trading is gated on `isReady === true`.
|
|
898
|
+
* `checklist` carries the renderable onboarding list: every choice step and
|
|
899
|
+
* every `USER`-signed step with its satisfied state, with not-required
|
|
900
|
+
* conditional steps omitted.
|
|
781
901
|
*
|
|
782
|
-
* `
|
|
783
|
-
*
|
|
784
|
-
*
|
|
902
|
+
* A non-choice `SDK`-signed step is NEVER returned here — the SDK drains it
|
|
903
|
+
* with the provider's own credentials. A choice is listed but never staged
|
|
904
|
+
* or executed, whatever its `signer`: the user picks an option through
|
|
905
|
+
* {@link executeProviderOption}, and `isReady` stays `false` until every
|
|
906
|
+
* choice on `checklist` is satisfied.
|
|
785
907
|
*
|
|
786
908
|
* @public
|
|
787
909
|
*/
|
|
@@ -807,19 +929,15 @@ export class PerpsClient {
|
|
|
807
929
|
}
|
|
808
930
|
}
|
|
809
931
|
|
|
810
|
-
const
|
|
932
|
+
const settings = await this.resolveSetupSettings(provider, address)
|
|
933
|
+
const satisfiedSetup = satisfiedTypes(settings)
|
|
811
934
|
const pendingSetup = metadata.setup.filter(
|
|
812
935
|
(descriptor) => !satisfiedSetup.has(descriptor.type)
|
|
813
936
|
)
|
|
814
937
|
|
|
815
938
|
const plugin = this.sdkClient.getProvider(provider)
|
|
816
|
-
|
|
817
|
-
const
|
|
818
|
-
internalTypes.has(descriptor.type) &&
|
|
819
|
-
!descriptor.signers.includes(PerpsSigner.USER)
|
|
820
|
-
const visibleSetup = pendingSetup.filter(
|
|
821
|
-
(descriptor) => !isInternal(descriptor)
|
|
822
|
-
)
|
|
939
|
+
|
|
940
|
+
const stageable = pendingSetup.filter(isStageable)
|
|
823
941
|
|
|
824
942
|
// The backend filters already-satisfied setup actions and returns typed
|
|
825
943
|
// data for those still outstanding; each plugin contributes its own
|
|
@@ -827,7 +945,7 @@ export class PerpsClient {
|
|
|
827
945
|
const actions = await this.buildProviderSetupActions(
|
|
828
946
|
provider,
|
|
829
947
|
address,
|
|
830
|
-
|
|
948
|
+
stageable
|
|
831
949
|
)
|
|
832
950
|
|
|
833
951
|
// A staged step is one the build produced actions for — a pending
|
|
@@ -835,69 +953,64 @@ export class PerpsClient {
|
|
|
835
953
|
// (backend-gated) or not applicable to this account.
|
|
836
954
|
const stagedTypes = new Set(actions.map((step) => step.action))
|
|
837
955
|
const conditionalTypes = new Set(plugin?.conditionalSetupActions ?? [])
|
|
838
|
-
const sequenceOf = (descriptor: ProviderAction): number =>
|
|
839
|
-
descriptor.sequence ?? Number.MAX_SAFE_INTEGER
|
|
840
956
|
const checklist = metadata.setup
|
|
841
|
-
.filter(
|
|
957
|
+
.filter(isUserFacingSetupStep)
|
|
842
958
|
.filter(
|
|
843
959
|
(descriptor) =>
|
|
960
|
+
descriptor.options !== null ||
|
|
844
961
|
!conditionalTypes.has(descriptor.type) ||
|
|
845
962
|
stagedTypes.has(descriptor.type)
|
|
846
963
|
)
|
|
847
964
|
.sort((a, b) => sequenceOf(a) - sequenceOf(b))
|
|
848
965
|
.map((descriptor) => ({
|
|
849
966
|
descriptor,
|
|
850
|
-
satisfied:
|
|
967
|
+
satisfied:
|
|
968
|
+
descriptor.options !== null
|
|
969
|
+
? satisfiedSetup.has(descriptor.type)
|
|
970
|
+
: !stagedTypes.has(descriptor.type),
|
|
971
|
+
selected: selectedOption(descriptor, settings),
|
|
972
|
+
currentValue: currentChoiceValue(descriptor, settings),
|
|
851
973
|
}))
|
|
852
974
|
|
|
853
|
-
await this.
|
|
975
|
+
await this.drainSetup(
|
|
854
976
|
provider,
|
|
855
977
|
address,
|
|
856
|
-
pendingSetup.filter(
|
|
857
|
-
|
|
978
|
+
pendingSetup.filter(
|
|
979
|
+
(descriptor) =>
|
|
980
|
+
descriptor.options === null && descriptor.signer === PerpsSigner.SDK
|
|
981
|
+
),
|
|
982
|
+
stageable.filter((descriptor) => stagedTypes.has(descriptor.type))
|
|
858
983
|
)
|
|
859
984
|
|
|
860
985
|
return {
|
|
861
986
|
accountExists: true,
|
|
862
987
|
setup: actions,
|
|
863
|
-
isReady:
|
|
988
|
+
isReady:
|
|
989
|
+
actions.length === 0 && checklist.every((item) => item.satisfied),
|
|
864
990
|
checklist,
|
|
865
991
|
}
|
|
866
992
|
}
|
|
867
993
|
|
|
868
994
|
/**
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
995
|
+
* Project the provider's own typed account config onto its setup steps. This
|
|
996
|
+
* catches client-held auth state (e.g. Ondo SIWE/JWT) that the backend
|
|
997
|
+
* cannot observe, and carries the current value of each choice.
|
|
872
998
|
*/
|
|
873
|
-
private async
|
|
999
|
+
private async resolveSetupSettings(
|
|
874
1000
|
provider: string,
|
|
875
1001
|
address: Address
|
|
876
|
-
): Promise<
|
|
1002
|
+
): Promise<AccountConfigSetting[]> {
|
|
877
1003
|
const metadata = await this.getProviderMetadata(provider)
|
|
878
1004
|
const plugin = this.sdkClient.getProvider(provider)
|
|
879
1005
|
if (!plugin || typeof plugin.getAccount !== 'function') {
|
|
880
|
-
return
|
|
1006
|
+
return []
|
|
881
1007
|
}
|
|
882
1008
|
const account = await plugin.getAccount({ address })
|
|
883
|
-
|
|
884
|
-
account.config,
|
|
885
|
-
metadata.setup,
|
|
886
|
-
metadata.options
|
|
887
|
-
)
|
|
888
|
-
const setupTypes = new Set(
|
|
889
|
-
metadata.setup.map((descriptor) => descriptor.type)
|
|
890
|
-
)
|
|
891
|
-
return new Set(
|
|
892
|
-
settings
|
|
893
|
-
.filter((setting) => setting.satisfied && setupTypes.has(setting.type))
|
|
894
|
-
.map((setting) => setting.type)
|
|
895
|
-
)
|
|
1009
|
+
return plugin.projectConfig(account.config, metadata.setup)
|
|
896
1010
|
}
|
|
897
1011
|
|
|
898
1012
|
/**
|
|
899
|
-
* Drain the pending
|
|
900
|
-
* `PerpsProviderPlugin.internalSetupActions`. Each step is built, signed,
|
|
1013
|
+
* Drain the pending `SDK`-signed non-choice steps: each is built, signed,
|
|
901
1014
|
* and executed in place with the provider's own credentials. A step is
|
|
902
1015
|
* deferred while any staged user-facing step with a lower `sequence` is
|
|
903
1016
|
* outstanding — it cannot succeed before its prerequisite (e.g. SET_REFERRAL
|
|
@@ -906,17 +1019,15 @@ export class PerpsClient {
|
|
|
906
1019
|
* blocks setup — the step stays unsatisfied and is retried on a later
|
|
907
1020
|
* `checkSetup`.
|
|
908
1021
|
*/
|
|
909
|
-
private async
|
|
1022
|
+
private async drainSetup(
|
|
910
1023
|
provider: string,
|
|
911
1024
|
address: Address,
|
|
912
|
-
|
|
913
|
-
stagedVisible:
|
|
1025
|
+
drainable: SetupAction[],
|
|
1026
|
+
stagedVisible: SetupAction[]
|
|
914
1027
|
): Promise<void> {
|
|
915
|
-
const
|
|
916
|
-
descriptor.sequence ?? Number.MAX_SAFE_INTEGER
|
|
917
|
-
for (const descriptor of internalPending) {
|
|
1028
|
+
for (const descriptor of drainable) {
|
|
918
1029
|
// A tie (equal sequences, or both absent) declares no order, so the
|
|
919
|
-
//
|
|
1030
|
+
// drained step defers to the next checkSetup rather than racing.
|
|
920
1031
|
const blocked = stagedVisible.some(
|
|
921
1032
|
(staged) => sequenceOf(staged) <= sequenceOf(descriptor)
|
|
922
1033
|
)
|
|
@@ -944,7 +1055,7 @@ export class PerpsClient {
|
|
|
944
1055
|
}
|
|
945
1056
|
} catch (error) {
|
|
946
1057
|
console.debug(
|
|
947
|
-
`[perps-sdk]
|
|
1058
|
+
`[perps-sdk] setup step '${descriptor.type}' for '${provider}' did not drain; will retry on the next checkSetup.`,
|
|
948
1059
|
error
|
|
949
1060
|
)
|
|
950
1061
|
}
|
|
@@ -958,7 +1069,7 @@ export class PerpsClient {
|
|
|
958
1069
|
private async buildProviderSetupActions(
|
|
959
1070
|
provider: string,
|
|
960
1071
|
address: Address,
|
|
961
|
-
descriptors:
|
|
1072
|
+
descriptors: SetupAction[]
|
|
962
1073
|
): Promise<ActionStep[]> {
|
|
963
1074
|
const setupPriority = (descriptor: ProviderAction): number =>
|
|
964
1075
|
descriptor.signingMethod === SigningMethod.SIWE ? 0 : 1
|
|
@@ -996,9 +1107,11 @@ export class PerpsClient {
|
|
|
996
1107
|
|
|
997
1108
|
/**
|
|
998
1109
|
* Build the unsigned setup `ActionStep`s still outstanding for an account,
|
|
999
|
-
* ordered by descriptor `sequence`.
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1110
|
+
* ordered by descriptor `sequence`. Only a `USER`-signed non-choice step is
|
|
1111
|
+
* staged: an `SDK`-signed non-choice step drains inside {@link checkSetup}
|
|
1112
|
+
* and a choice executes the option the user picks. The backend filters
|
|
1113
|
+
* already-satisfied setup; each plugin contributes its own signer-bearing
|
|
1114
|
+
* request fields and any local-state params (e.g. Lighter's known pubkey).
|
|
1002
1115
|
*
|
|
1003
1116
|
* @public
|
|
1004
1117
|
*/
|
|
@@ -1011,7 +1124,7 @@ export class PerpsClient {
|
|
|
1011
1124
|
const actions = await this.buildProviderSetupActions(
|
|
1012
1125
|
provider,
|
|
1013
1126
|
address,
|
|
1014
|
-
metadata.setup
|
|
1127
|
+
metadata.setup.filter(isStageable)
|
|
1015
1128
|
)
|
|
1016
1129
|
return { actions }
|
|
1017
1130
|
}
|
|
@@ -1054,13 +1167,7 @@ export class PerpsClient {
|
|
|
1054
1167
|
|
|
1055
1168
|
await this.notifyExecuteResults(provider, address, results)
|
|
1056
1169
|
|
|
1057
|
-
|
|
1058
|
-
if (failure) {
|
|
1059
|
-
throw new PerpsError(
|
|
1060
|
-
failure.errorCode ?? PerpsErrorCode.ExchangeRejected,
|
|
1061
|
-
failure.error
|
|
1062
|
-
)
|
|
1063
|
-
}
|
|
1170
|
+
assertAllSucceeded(results)
|
|
1064
1171
|
|
|
1065
1172
|
return { results: { results } }
|
|
1066
1173
|
}
|
|
@@ -1068,25 +1175,23 @@ export class PerpsClient {
|
|
|
1068
1175
|
/**
|
|
1069
1176
|
* Sign and submit one pre-staged setup `ActionStep` end-to-end.
|
|
1070
1177
|
*
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1075
|
-
* through submit. If the cached step has gone stale (Lighter's `/nextNonce`
|
|
1076
|
-
* advanced underneath us), `executeProviderSetup` will surface a nonce
|
|
1077
|
-
* conflict that the caller invalidates on, refetches `checkSetup`, and
|
|
1078
|
-
* retries with a fresh step.
|
|
1178
|
+
* `step` and `checklist` must come from the same {@link checkSetup} result.
|
|
1179
|
+
* This preserves the nonce allocated while staging and validates sequence
|
|
1180
|
+
* prerequisites against that same snapshot, without another `createAction`
|
|
1181
|
+
* round-trip.
|
|
1079
1182
|
*
|
|
1080
1183
|
* @throws {PerpsError} When the step's action is not in the provider's
|
|
1081
|
-
* `setup` descriptors
|
|
1184
|
+
* `setup` descriptors, or when a lower-`sequence` step on the same
|
|
1185
|
+
* checklist, a choice included, is still unsatisfied.
|
|
1082
1186
|
* @public
|
|
1083
1187
|
*/
|
|
1084
1188
|
async executeProviderSetupAction(params: {
|
|
1085
1189
|
provider: string
|
|
1086
1190
|
address: Address
|
|
1087
1191
|
step: ActionStep
|
|
1192
|
+
checklist: SetupChecklistItem[]
|
|
1088
1193
|
}): Promise<void> {
|
|
1089
|
-
const { provider, address, step } = params
|
|
1194
|
+
const { provider, address, step, checklist } = params
|
|
1090
1195
|
|
|
1091
1196
|
const metadata = await this.getProviderMetadata(provider)
|
|
1092
1197
|
const descriptor = metadata.setup.find((d) => d.type === step.action)
|
|
@@ -1097,6 +1202,17 @@ export class PerpsClient {
|
|
|
1097
1202
|
)
|
|
1098
1203
|
}
|
|
1099
1204
|
|
|
1205
|
+
const blockingStep = checklist.find(
|
|
1206
|
+
(item) =>
|
|
1207
|
+
!item.satisfied && sequenceOf(item.descriptor) < sequenceOf(descriptor)
|
|
1208
|
+
)
|
|
1209
|
+
if (blockingStep) {
|
|
1210
|
+
throw new PerpsError(
|
|
1211
|
+
PerpsErrorCode.SDKError,
|
|
1212
|
+
`Setup step '${step.action}' for '${provider}' is blocked: '${blockingStep.descriptor.type}' runs first and is not satisfied.`
|
|
1213
|
+
)
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1100
1216
|
const signed = await this.signProviderSetupAction(provider, address, step)
|
|
1101
1217
|
|
|
1102
1218
|
// A client-executed setup action (Lighter SET_REFERRAL) produces no
|
|
@@ -1115,22 +1231,13 @@ export class PerpsClient {
|
|
|
1115
1231
|
}
|
|
1116
1232
|
|
|
1117
1233
|
/**
|
|
1118
|
-
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1234
|
+
* Execute one option of a choice setup step (Lighter `ACCOUNT_TYPE`,
|
|
1235
|
+
* Hyperliquid `ACCOUNT_MODE`) end-to-end with its bound `{ type, params }`,
|
|
1236
|
+
* throwing on a venue rejection.
|
|
1121
1237
|
*
|
|
1122
|
-
*
|
|
1123
|
-
* trades, but unlike a trade an option change is a single mandatory action:
|
|
1238
|
+
* The option runs through the same {@link execute} pipeline as a trade, but
|
|
1124
1239
|
* a per-action `success: false` (returned as a 200 OK) means the user's
|
|
1125
|
-
* selection was rejected and must surface, not be silently dropped.
|
|
1126
|
-
* wrapper inspects the result and throws a {@link PerpsError} carrying the
|
|
1127
|
-
* venue `error`, giving options the same throw contract that setup has via
|
|
1128
|
-
* {@link executeProviderSetupAction}. The only structural difference is that
|
|
1129
|
-
* an option carries `params` (the selected value) rather than a pre-staged
|
|
1130
|
-
* step.
|
|
1131
|
-
*
|
|
1132
|
-
* `execute` itself is unchanged — it still returns results without throwing,
|
|
1133
|
-
* which the trade hooks rely on for partial-fill handling.
|
|
1240
|
+
* selection was rejected and must surface, not be silently dropped.
|
|
1134
1241
|
*
|
|
1135
1242
|
* @throws {PerpsError} Carrying the venue error when any returned result has
|
|
1136
1243
|
* `success: false`, under that result's `errorCode` when the backend
|
|
@@ -1139,20 +1246,56 @@ export class PerpsClient {
|
|
|
1139
1246
|
* signer, signing failure).
|
|
1140
1247
|
* @public
|
|
1141
1248
|
*/
|
|
1142
|
-
async executeProviderOption
|
|
1249
|
+
async executeProviderOption(params: {
|
|
1143
1250
|
provider: string
|
|
1144
1251
|
address: Address
|
|
1145
|
-
|
|
1146
|
-
params: ActionParamsMap[T]
|
|
1252
|
+
option: SetupOption
|
|
1147
1253
|
}): Promise<void> {
|
|
1148
|
-
const {
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1254
|
+
const { provider, address, option } = params
|
|
1255
|
+
const { results } = await this.execute({
|
|
1256
|
+
provider,
|
|
1257
|
+
address,
|
|
1258
|
+
action: option.type,
|
|
1259
|
+
params: option.params,
|
|
1260
|
+
})
|
|
1261
|
+
assertAllSucceeded(results)
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Undo a satisfied setup step by executing its `revoke` action through
|
|
1266
|
+
* create, sign and execute, signed with that action's `Provider.actions`
|
|
1267
|
+
* descriptor. The step stages again on the next {@link checkSetup}.
|
|
1268
|
+
*
|
|
1269
|
+
* @throws {PerpsError} `SDKError` when `step.revoke` is `null` or when
|
|
1270
|
+
* `createAction` stages nothing to revoke; otherwise the errors
|
|
1271
|
+
* {@link executeProviderOption} throws.
|
|
1272
|
+
* @public
|
|
1273
|
+
*/
|
|
1274
|
+
async executeProviderRevoke(params: {
|
|
1275
|
+
provider: string
|
|
1276
|
+
address: Address
|
|
1277
|
+
step: SetupAction
|
|
1278
|
+
}): Promise<void> {
|
|
1279
|
+
const { provider, address, step } = params
|
|
1280
|
+
if (step.revoke === null) {
|
|
1151
1281
|
throw new PerpsError(
|
|
1152
|
-
|
|
1153
|
-
|
|
1282
|
+
PerpsErrorCode.SDKError,
|
|
1283
|
+
`Setup step '${step.type}' for '${provider}' declares no revoke.`
|
|
1154
1284
|
)
|
|
1155
1285
|
}
|
|
1286
|
+
const { staged, results } = await this.runAction({
|
|
1287
|
+
provider,
|
|
1288
|
+
address,
|
|
1289
|
+
action: step.revoke,
|
|
1290
|
+
params: {},
|
|
1291
|
+
})
|
|
1292
|
+
if (staged.length === 0) {
|
|
1293
|
+
throw new PerpsError(
|
|
1294
|
+
PerpsErrorCode.SDKError,
|
|
1295
|
+
`Nothing to revoke for setup step '${step.type}' on '${provider}'.`
|
|
1296
|
+
)
|
|
1297
|
+
}
|
|
1298
|
+
assertAllSucceeded(results)
|
|
1156
1299
|
}
|
|
1157
1300
|
|
|
1158
1301
|
/**
|
|
@@ -1333,6 +1476,18 @@ export class PerpsClient {
|
|
|
1333
1476
|
* deposit); called as each leg is submitted and confirmed. */
|
|
1334
1477
|
onProgress?: (progress: SignActionProgress) => void
|
|
1335
1478
|
}): Promise<ExecuteActionResponse> {
|
|
1479
|
+
const { results } = await this.runAction(params)
|
|
1480
|
+
return { results }
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
/** {@link execute}, also returning the steps `createAction` staged. */
|
|
1484
|
+
private async runAction<T extends ActionType>(params: {
|
|
1485
|
+
provider: string
|
|
1486
|
+
address: Address
|
|
1487
|
+
action: T
|
|
1488
|
+
params: ActionParamsMap[T]
|
|
1489
|
+
onProgress?: (progress: SignActionProgress) => void
|
|
1490
|
+
}): Promise<{ staged: ActionStep[]; results: ActionResult[] }> {
|
|
1336
1491
|
const { provider, address, action, onProgress } = params
|
|
1337
1492
|
const metadata = await this.getProviderMetadata(provider)
|
|
1338
1493
|
const descriptor = findActionDescriptor(metadata, action)
|
|
@@ -1363,7 +1518,7 @@ export class PerpsClient {
|
|
|
1363
1518
|
// token-authenticated venue mutations), leaving no backend-bound step. With
|
|
1364
1519
|
// nothing to submit, skip the `/executeAction` hop.
|
|
1365
1520
|
if (signedActions.length === 0) {
|
|
1366
|
-
return { results: [] }
|
|
1521
|
+
return { staged: actions, results: [] }
|
|
1367
1522
|
}
|
|
1368
1523
|
|
|
1369
1524
|
const response = await executeAction(this.sdkClient, {
|
|
@@ -1380,7 +1535,7 @@ export class PerpsClient {
|
|
|
1380
1535
|
|
|
1381
1536
|
await this.notifyExecuteResults(provider, address, results)
|
|
1382
1537
|
|
|
1383
|
-
return { results }
|
|
1538
|
+
return { staged: actions, results }
|
|
1384
1539
|
}
|
|
1385
1540
|
|
|
1386
1541
|
/**
|