@lifi/perps-sdk 18.0.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/README.md +12 -0
- package/dist/cjs/client/PerpsClient.d.ts +15 -8
- package/dist/cjs/client/PerpsClient.d.ts.map +1 -1
- package/dist/cjs/client/PerpsClient.js +145 -52
- package/dist/cjs/client/PerpsClient.js.map +1 -1
- package/dist/cjs/index.d.ts +4 -4
- 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 +8 -2
- package/dist/cjs/types/api.d.ts.map +1 -1
- package/dist/cjs/types/provider.d.ts +9 -6
- package/dist/cjs/types/provider.d.ts.map +1 -1
- package/dist/cjs/types/withdrawal.d.ts +11 -1
- package/dist/cjs/types/withdrawal.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 +74 -55
- package/dist/esm/client/PerpsClient.d.ts.map +1 -1
- package/dist/esm/client/PerpsClient.js +218 -100
- package/dist/esm/client/PerpsClient.js.map +1 -1
- package/dist/esm/index.d.ts +4 -4
- 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 +40 -14
- package/dist/esm/types/api.d.ts.map +1 -1
- package/dist/esm/types/provider.d.ts +39 -28
- package/dist/esm/types/provider.d.ts.map +1 -1
- package/dist/esm/types/withdrawal.d.ts +30 -1
- package/dist/esm/types/withdrawal.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 +74 -55
- package/dist/types/client/PerpsClient.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types/api.d.ts +40 -14
- package/dist/types/types/api.d.ts.map +1 -1
- package/dist/types/types/provider.d.ts +39 -28
- package/dist/types/types/provider.d.ts.map +1 -1
- package/dist/types/types/withdrawal.d.ts +30 -1
- package/dist/types/types/withdrawal.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 +303 -130
- package/src/index.ts +9 -1
- package/src/types/api.ts +42 -14
- package/src/types/provider.ts +44 -30
- package/src/types/withdrawal.ts +33 -1
- 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,
|
|
@@ -55,6 +59,7 @@ import type {
|
|
|
55
59
|
GetPortfolioHistoryParams,
|
|
56
60
|
GetSetupParams,
|
|
57
61
|
GetWithdrawableBalancesParams,
|
|
62
|
+
GetWithdrawFlowParams,
|
|
58
63
|
ModifyOrdersParams,
|
|
59
64
|
PerpsClientOptions,
|
|
60
65
|
PlaceOrderParams,
|
|
@@ -62,6 +67,7 @@ import type {
|
|
|
62
67
|
PlaceTwapOrderParams,
|
|
63
68
|
ProviderSetup,
|
|
64
69
|
SendAssetActionParams,
|
|
70
|
+
SetupChecklistItem,
|
|
65
71
|
SubmitOnboardingParams,
|
|
66
72
|
WithdrawParams,
|
|
67
73
|
} from '../types/api.js'
|
|
@@ -78,7 +84,8 @@ import type {
|
|
|
78
84
|
SignActionProgress,
|
|
79
85
|
SignActionsContext,
|
|
80
86
|
} from '../types/provider.js'
|
|
81
|
-
import type { WithdrawableBalance } from '../types/withdrawal.js'
|
|
87
|
+
import type { WithdrawableBalance, WithdrawFlow } from '../types/withdrawal.js'
|
|
88
|
+
import { isUserFacingSetupStep } from '../utils/setupActions.js'
|
|
82
89
|
import { signTypedDataWithSigner } from '../utils/signTypedData.js'
|
|
83
90
|
import {
|
|
84
91
|
eip712DomainChainId,
|
|
@@ -88,6 +95,127 @@ import {
|
|
|
88
95
|
import { createPerpsClient } from './createPerpsClient.js'
|
|
89
96
|
import { requireProvider as resolveProvider } from './requireProvider.js'
|
|
90
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
|
+
|
|
91
219
|
function isNonNegativeDecimal(value: string): boolean {
|
|
92
220
|
try {
|
|
93
221
|
return new Big(value).gte(0)
|
|
@@ -104,11 +232,9 @@ function findActionDescriptor(
|
|
|
104
232
|
metadata: Provider,
|
|
105
233
|
action: ActionType
|
|
106
234
|
): ProviderAction {
|
|
107
|
-
const descriptor = [
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
...metadata.actions,
|
|
111
|
-
].find((d) => d.type === action)
|
|
235
|
+
const descriptor = [...metadata.setup, ...metadata.actions].find(
|
|
236
|
+
(d) => d.type === action
|
|
237
|
+
)
|
|
112
238
|
if (!descriptor) {
|
|
113
239
|
throw new PerpsError(
|
|
114
240
|
PerpsErrorCode.SDKError,
|
|
@@ -170,7 +296,7 @@ export class PerpsClient {
|
|
|
170
296
|
|
|
171
297
|
/**
|
|
172
298
|
* Set or update the end-user's wallet. Used whenever an action's descriptor
|
|
173
|
-
* names
|
|
299
|
+
* names `PerpsSigner.USER` as its `signer`. Pass undefined to clear.
|
|
174
300
|
*
|
|
175
301
|
* @public
|
|
176
302
|
*/
|
|
@@ -204,17 +330,15 @@ export class PerpsClient {
|
|
|
204
330
|
}
|
|
205
331
|
|
|
206
332
|
private async getProviderMetadata(provider: string): Promise<Provider> {
|
|
207
|
-
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
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)
|
|
215
340
|
}
|
|
216
341
|
|
|
217
|
-
const metadata = this.providerMetadataCache.get(provider)
|
|
218
342
|
if (!metadata) {
|
|
219
343
|
const error = new PerpsError(
|
|
220
344
|
PerpsErrorCode.SDKError,
|
|
@@ -223,6 +347,7 @@ export class PerpsClient {
|
|
|
223
347
|
error.tool = '@lifi/perps-sdk'
|
|
224
348
|
throw error
|
|
225
349
|
}
|
|
350
|
+
assertSetupContract(metadata)
|
|
226
351
|
return metadata
|
|
227
352
|
}
|
|
228
353
|
|
|
@@ -255,7 +380,7 @@ export class PerpsClient {
|
|
|
255
380
|
* Ask the provider plugin for the signer-bearing wire fields of `action` —
|
|
256
381
|
* the on-wire `signerAddress` and any signer-derived params (e.g.
|
|
257
382
|
* Hyperliquid's `agentAddress` for `APPROVE_AGENT`). Forwards the descriptor's
|
|
258
|
-
* `
|
|
383
|
+
* `signer` so the plugin can branch on signer role. Returns empty when the
|
|
259
384
|
* plugin signs as the user or with a non-EVM credential. Core constructs no
|
|
260
385
|
* `signerAddress` itself; signer identity is plugin-owned.
|
|
261
386
|
*/
|
|
@@ -271,17 +396,17 @@ export class PerpsClient {
|
|
|
271
396
|
return plugin.resolveActionRequest(
|
|
272
397
|
descriptor.type,
|
|
273
398
|
address,
|
|
274
|
-
descriptor.
|
|
399
|
+
descriptor.signer
|
|
275
400
|
)
|
|
276
401
|
}
|
|
277
402
|
|
|
278
403
|
/**
|
|
279
404
|
* Delegate signing of `actions` to the provider plugin, one call per
|
|
280
405
|
* consecutive run of steps sharing a descriptor. Each call carries that
|
|
281
|
-
* descriptor's `signingMethod` and `
|
|
406
|
+
* descriptor's `signingMethod` and `signer`, so a mixed batch signs every
|
|
282
407
|
* step with the signer its own descriptor declares; the signed steps come
|
|
283
408
|
* back in the batch's original order. The plugin owns every signing arm and
|
|
284
|
-
* branches on `
|
|
409
|
+
* branches on `signer` internally, reading the end-user's wallet from the
|
|
285
410
|
* {@link SignActionsContext} when an arm signs as the user.
|
|
286
411
|
*
|
|
287
412
|
* @throws {PerpsError} When a step's action is not declared by the provider,
|
|
@@ -404,9 +529,9 @@ export class PerpsClient {
|
|
|
404
529
|
|
|
405
530
|
/**
|
|
406
531
|
* Assemble the per-call context the provider plugin needs in order to sign:
|
|
407
|
-
* the end-user's wallet and the descriptor's declared `
|
|
408
|
-
* forwards `
|
|
409
|
-
* 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
|
|
410
535
|
* keypair, Lighter's API key) are resolved inside the provider's
|
|
411
536
|
* `signActions`, not threaded through here.
|
|
412
537
|
*
|
|
@@ -424,7 +549,7 @@ export class PerpsClient {
|
|
|
424
549
|
userWallet?: PerpsClientSigner,
|
|
425
550
|
onProgress?: (progress: SignActionProgress) => void
|
|
426
551
|
): SignActionsContext {
|
|
427
|
-
const ctx: SignActionsContext = {
|
|
552
|
+
const ctx: SignActionsContext = { signer: descriptor.signer }
|
|
428
553
|
if (onProgress !== undefined) {
|
|
429
554
|
ctx.onProgress = onProgress
|
|
430
555
|
}
|
|
@@ -500,7 +625,7 @@ export class PerpsClient {
|
|
|
500
625
|
/**
|
|
501
626
|
* Fetch the user's account state from the backend and attach the
|
|
502
627
|
* SDK-projected `settings` array — one `AccountConfigSetting` per
|
|
503
|
-
* descriptor on `Provider.setup
|
|
628
|
+
* descriptor on `Provider.setup`. Callers read
|
|
504
629
|
* `result.settings` directly without re-deriving values from the typed
|
|
505
630
|
* `AccountConfig`.
|
|
506
631
|
*
|
|
@@ -517,11 +642,7 @@ export class PerpsClient {
|
|
|
517
642
|
fetchAccount(this.sdkClient, params),
|
|
518
643
|
this.getProviderMetadata(params.provider),
|
|
519
644
|
])
|
|
520
|
-
const settings = plugin.projectConfig(
|
|
521
|
-
response.config,
|
|
522
|
-
metadata.setup,
|
|
523
|
-
metadata.options
|
|
524
|
-
)
|
|
645
|
+
const settings = plugin.projectConfig(response.config, metadata.setup)
|
|
525
646
|
return { ...response, settings }
|
|
526
647
|
}
|
|
527
648
|
|
|
@@ -661,6 +782,23 @@ export class PerpsClient {
|
|
|
661
782
|
return plugin.getDepositFlow?.({ address: params.address })
|
|
662
783
|
}
|
|
663
784
|
|
|
785
|
+
/**
|
|
786
|
+
* The withdrawal flow for `params.address` at `params.provider`, delegated to
|
|
787
|
+
* the provider plugin — the withdrawal destination and any setup gate before
|
|
788
|
+
* it are venue-owned.
|
|
789
|
+
*
|
|
790
|
+
* @returns `undefined` when the registered plugin declares no withdrawal flow.
|
|
791
|
+
* @throws {PerpsError} When the provider plugin is not registered, or the
|
|
792
|
+
* plugin's flow resolution fails.
|
|
793
|
+
* @public
|
|
794
|
+
*/
|
|
795
|
+
async getWithdrawFlow(
|
|
796
|
+
params: GetWithdrawFlowParams
|
|
797
|
+
): Promise<WithdrawFlow | undefined> {
|
|
798
|
+
const plugin = this.requireProvider(params.provider)
|
|
799
|
+
return plugin.getWithdrawFlow?.({ address: params.address })
|
|
800
|
+
}
|
|
801
|
+
|
|
664
802
|
/**
|
|
665
803
|
* The `(asset, route)` selections `params.address` can actually withdraw at
|
|
666
804
|
* `params.provider`. The venue owns how its balances split across routes;
|
|
@@ -755,15 +893,17 @@ export class PerpsClient {
|
|
|
755
893
|
}
|
|
756
894
|
|
|
757
895
|
/**
|
|
758
|
-
* Return the
|
|
759
|
-
*
|
|
760
|
-
* `checklist` carries the renderable onboarding list: every
|
|
761
|
-
*
|
|
762
|
-
* 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.
|
|
763
901
|
*
|
|
764
|
-
* `
|
|
765
|
-
*
|
|
766
|
-
*
|
|
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.
|
|
767
907
|
*
|
|
768
908
|
* @public
|
|
769
909
|
*/
|
|
@@ -789,19 +929,15 @@ export class PerpsClient {
|
|
|
789
929
|
}
|
|
790
930
|
}
|
|
791
931
|
|
|
792
|
-
const
|
|
932
|
+
const settings = await this.resolveSetupSettings(provider, address)
|
|
933
|
+
const satisfiedSetup = satisfiedTypes(settings)
|
|
793
934
|
const pendingSetup = metadata.setup.filter(
|
|
794
935
|
(descriptor) => !satisfiedSetup.has(descriptor.type)
|
|
795
936
|
)
|
|
796
937
|
|
|
797
938
|
const plugin = this.sdkClient.getProvider(provider)
|
|
798
|
-
|
|
799
|
-
const
|
|
800
|
-
internalTypes.has(descriptor.type) &&
|
|
801
|
-
!descriptor.signers.includes(PerpsSigner.USER)
|
|
802
|
-
const visibleSetup = pendingSetup.filter(
|
|
803
|
-
(descriptor) => !isInternal(descriptor)
|
|
804
|
-
)
|
|
939
|
+
|
|
940
|
+
const stageable = pendingSetup.filter(isStageable)
|
|
805
941
|
|
|
806
942
|
// The backend filters already-satisfied setup actions and returns typed
|
|
807
943
|
// data for those still outstanding; each plugin contributes its own
|
|
@@ -809,7 +945,7 @@ export class PerpsClient {
|
|
|
809
945
|
const actions = await this.buildProviderSetupActions(
|
|
810
946
|
provider,
|
|
811
947
|
address,
|
|
812
|
-
|
|
948
|
+
stageable
|
|
813
949
|
)
|
|
814
950
|
|
|
815
951
|
// A staged step is one the build produced actions for — a pending
|
|
@@ -817,69 +953,64 @@ export class PerpsClient {
|
|
|
817
953
|
// (backend-gated) or not applicable to this account.
|
|
818
954
|
const stagedTypes = new Set(actions.map((step) => step.action))
|
|
819
955
|
const conditionalTypes = new Set(plugin?.conditionalSetupActions ?? [])
|
|
820
|
-
const sequenceOf = (descriptor: ProviderAction): number =>
|
|
821
|
-
descriptor.sequence ?? Number.MAX_SAFE_INTEGER
|
|
822
956
|
const checklist = metadata.setup
|
|
823
|
-
.filter(
|
|
957
|
+
.filter(isUserFacingSetupStep)
|
|
824
958
|
.filter(
|
|
825
959
|
(descriptor) =>
|
|
960
|
+
descriptor.options !== null ||
|
|
826
961
|
!conditionalTypes.has(descriptor.type) ||
|
|
827
962
|
stagedTypes.has(descriptor.type)
|
|
828
963
|
)
|
|
829
964
|
.sort((a, b) => sequenceOf(a) - sequenceOf(b))
|
|
830
965
|
.map((descriptor) => ({
|
|
831
966
|
descriptor,
|
|
832
|
-
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),
|
|
833
973
|
}))
|
|
834
974
|
|
|
835
|
-
await this.
|
|
975
|
+
await this.drainSetup(
|
|
836
976
|
provider,
|
|
837
977
|
address,
|
|
838
|
-
pendingSetup.filter(
|
|
839
|
-
|
|
978
|
+
pendingSetup.filter(
|
|
979
|
+
(descriptor) =>
|
|
980
|
+
descriptor.options === null && descriptor.signer === PerpsSigner.SDK
|
|
981
|
+
),
|
|
982
|
+
stageable.filter((descriptor) => stagedTypes.has(descriptor.type))
|
|
840
983
|
)
|
|
841
984
|
|
|
842
985
|
return {
|
|
843
986
|
accountExists: true,
|
|
844
987
|
setup: actions,
|
|
845
|
-
isReady:
|
|
988
|
+
isReady:
|
|
989
|
+
actions.length === 0 && checklist.every((item) => item.satisfied),
|
|
846
990
|
checklist,
|
|
847
991
|
}
|
|
848
992
|
}
|
|
849
993
|
|
|
850
994
|
/**
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
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.
|
|
854
998
|
*/
|
|
855
|
-
private async
|
|
999
|
+
private async resolveSetupSettings(
|
|
856
1000
|
provider: string,
|
|
857
1001
|
address: Address
|
|
858
|
-
): Promise<
|
|
1002
|
+
): Promise<AccountConfigSetting[]> {
|
|
859
1003
|
const metadata = await this.getProviderMetadata(provider)
|
|
860
1004
|
const plugin = this.sdkClient.getProvider(provider)
|
|
861
1005
|
if (!plugin || typeof plugin.getAccount !== 'function') {
|
|
862
|
-
return
|
|
1006
|
+
return []
|
|
863
1007
|
}
|
|
864
1008
|
const account = await plugin.getAccount({ address })
|
|
865
|
-
|
|
866
|
-
account.config,
|
|
867
|
-
metadata.setup,
|
|
868
|
-
metadata.options
|
|
869
|
-
)
|
|
870
|
-
const setupTypes = new Set(
|
|
871
|
-
metadata.setup.map((descriptor) => descriptor.type)
|
|
872
|
-
)
|
|
873
|
-
return new Set(
|
|
874
|
-
settings
|
|
875
|
-
.filter((setting) => setting.satisfied && setupTypes.has(setting.type))
|
|
876
|
-
.map((setting) => setting.type)
|
|
877
|
-
)
|
|
1009
|
+
return plugin.projectConfig(account.config, metadata.setup)
|
|
878
1010
|
}
|
|
879
1011
|
|
|
880
1012
|
/**
|
|
881
|
-
* Drain the pending
|
|
882
|
-
* `PerpsProviderPlugin.internalSetupActions`. Each step is built, signed,
|
|
1013
|
+
* Drain the pending `SDK`-signed non-choice steps: each is built, signed,
|
|
883
1014
|
* and executed in place with the provider's own credentials. A step is
|
|
884
1015
|
* deferred while any staged user-facing step with a lower `sequence` is
|
|
885
1016
|
* outstanding — it cannot succeed before its prerequisite (e.g. SET_REFERRAL
|
|
@@ -888,17 +1019,15 @@ export class PerpsClient {
|
|
|
888
1019
|
* blocks setup — the step stays unsatisfied and is retried on a later
|
|
889
1020
|
* `checkSetup`.
|
|
890
1021
|
*/
|
|
891
|
-
private async
|
|
1022
|
+
private async drainSetup(
|
|
892
1023
|
provider: string,
|
|
893
1024
|
address: Address,
|
|
894
|
-
|
|
895
|
-
stagedVisible:
|
|
1025
|
+
drainable: SetupAction[],
|
|
1026
|
+
stagedVisible: SetupAction[]
|
|
896
1027
|
): Promise<void> {
|
|
897
|
-
const
|
|
898
|
-
descriptor.sequence ?? Number.MAX_SAFE_INTEGER
|
|
899
|
-
for (const descriptor of internalPending) {
|
|
1028
|
+
for (const descriptor of drainable) {
|
|
900
1029
|
// A tie (equal sequences, or both absent) declares no order, so the
|
|
901
|
-
//
|
|
1030
|
+
// drained step defers to the next checkSetup rather than racing.
|
|
902
1031
|
const blocked = stagedVisible.some(
|
|
903
1032
|
(staged) => sequenceOf(staged) <= sequenceOf(descriptor)
|
|
904
1033
|
)
|
|
@@ -926,7 +1055,7 @@ export class PerpsClient {
|
|
|
926
1055
|
}
|
|
927
1056
|
} catch (error) {
|
|
928
1057
|
console.debug(
|
|
929
|
-
`[perps-sdk]
|
|
1058
|
+
`[perps-sdk] setup step '${descriptor.type}' for '${provider}' did not drain; will retry on the next checkSetup.`,
|
|
930
1059
|
error
|
|
931
1060
|
)
|
|
932
1061
|
}
|
|
@@ -940,7 +1069,7 @@ export class PerpsClient {
|
|
|
940
1069
|
private async buildProviderSetupActions(
|
|
941
1070
|
provider: string,
|
|
942
1071
|
address: Address,
|
|
943
|
-
descriptors:
|
|
1072
|
+
descriptors: SetupAction[]
|
|
944
1073
|
): Promise<ActionStep[]> {
|
|
945
1074
|
const setupPriority = (descriptor: ProviderAction): number =>
|
|
946
1075
|
descriptor.signingMethod === SigningMethod.SIWE ? 0 : 1
|
|
@@ -978,9 +1107,11 @@ export class PerpsClient {
|
|
|
978
1107
|
|
|
979
1108
|
/**
|
|
980
1109
|
* Build the unsigned setup `ActionStep`s still outstanding for an account,
|
|
981
|
-
* ordered by descriptor `sequence`.
|
|
982
|
-
*
|
|
983
|
-
*
|
|
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).
|
|
984
1115
|
*
|
|
985
1116
|
* @public
|
|
986
1117
|
*/
|
|
@@ -993,7 +1124,7 @@ export class PerpsClient {
|
|
|
993
1124
|
const actions = await this.buildProviderSetupActions(
|
|
994
1125
|
provider,
|
|
995
1126
|
address,
|
|
996
|
-
metadata.setup
|
|
1127
|
+
metadata.setup.filter(isStageable)
|
|
997
1128
|
)
|
|
998
1129
|
return { actions }
|
|
999
1130
|
}
|
|
@@ -1036,13 +1167,7 @@ export class PerpsClient {
|
|
|
1036
1167
|
|
|
1037
1168
|
await this.notifyExecuteResults(provider, address, results)
|
|
1038
1169
|
|
|
1039
|
-
|
|
1040
|
-
if (failure) {
|
|
1041
|
-
throw new PerpsError(
|
|
1042
|
-
failure.errorCode ?? PerpsErrorCode.ExchangeRejected,
|
|
1043
|
-
failure.error
|
|
1044
|
-
)
|
|
1045
|
-
}
|
|
1170
|
+
assertAllSucceeded(results)
|
|
1046
1171
|
|
|
1047
1172
|
return { results: { results } }
|
|
1048
1173
|
}
|
|
@@ -1050,25 +1175,23 @@ export class PerpsClient {
|
|
|
1050
1175
|
/**
|
|
1051
1176
|
* Sign and submit one pre-staged setup `ActionStep` end-to-end.
|
|
1052
1177
|
*
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1055
|
-
*
|
|
1056
|
-
*
|
|
1057
|
-
* through submit. If the cached step has gone stale (Lighter's `/nextNonce`
|
|
1058
|
-
* advanced underneath us), `executeProviderSetup` will surface a nonce
|
|
1059
|
-
* conflict that the caller invalidates on, refetches `checkSetup`, and
|
|
1060
|
-
* 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.
|
|
1061
1182
|
*
|
|
1062
1183
|
* @throws {PerpsError} When the step's action is not in the provider's
|
|
1063
|
-
* `setup` descriptors
|
|
1184
|
+
* `setup` descriptors, or when a lower-`sequence` step on the same
|
|
1185
|
+
* checklist, a choice included, is still unsatisfied.
|
|
1064
1186
|
* @public
|
|
1065
1187
|
*/
|
|
1066
1188
|
async executeProviderSetupAction(params: {
|
|
1067
1189
|
provider: string
|
|
1068
1190
|
address: Address
|
|
1069
1191
|
step: ActionStep
|
|
1192
|
+
checklist: SetupChecklistItem[]
|
|
1070
1193
|
}): Promise<void> {
|
|
1071
|
-
const { provider, address, step } = params
|
|
1194
|
+
const { provider, address, step, checklist } = params
|
|
1072
1195
|
|
|
1073
1196
|
const metadata = await this.getProviderMetadata(provider)
|
|
1074
1197
|
const descriptor = metadata.setup.find((d) => d.type === step.action)
|
|
@@ -1079,6 +1202,17 @@ export class PerpsClient {
|
|
|
1079
1202
|
)
|
|
1080
1203
|
}
|
|
1081
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
|
+
|
|
1082
1216
|
const signed = await this.signProviderSetupAction(provider, address, step)
|
|
1083
1217
|
|
|
1084
1218
|
// A client-executed setup action (Lighter SET_REFERRAL) produces no
|
|
@@ -1097,22 +1231,13 @@ export class PerpsClient {
|
|
|
1097
1231
|
}
|
|
1098
1232
|
|
|
1099
1233
|
/**
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
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.
|
|
1103
1237
|
*
|
|
1104
|
-
*
|
|
1105
|
-
* 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
|
|
1106
1239
|
* a per-action `success: false` (returned as a 200 OK) means the user's
|
|
1107
|
-
* selection was rejected and must surface, not be silently dropped.
|
|
1108
|
-
* wrapper inspects the result and throws a {@link PerpsError} carrying the
|
|
1109
|
-
* venue `error`, giving options the same throw contract that setup has via
|
|
1110
|
-
* {@link executeProviderSetupAction}. The only structural difference is that
|
|
1111
|
-
* an option carries `params` (the selected value) rather than a pre-staged
|
|
1112
|
-
* step.
|
|
1113
|
-
*
|
|
1114
|
-
* `execute` itself is unchanged — it still returns results without throwing,
|
|
1115
|
-
* which the trade hooks rely on for partial-fill handling.
|
|
1240
|
+
* selection was rejected and must surface, not be silently dropped.
|
|
1116
1241
|
*
|
|
1117
1242
|
* @throws {PerpsError} Carrying the venue error when any returned result has
|
|
1118
1243
|
* `success: false`, under that result's `errorCode` when the backend
|
|
@@ -1121,20 +1246,56 @@ export class PerpsClient {
|
|
|
1121
1246
|
* signer, signing failure).
|
|
1122
1247
|
* @public
|
|
1123
1248
|
*/
|
|
1124
|
-
async executeProviderOption
|
|
1249
|
+
async executeProviderOption(params: {
|
|
1125
1250
|
provider: string
|
|
1126
1251
|
address: Address
|
|
1127
|
-
|
|
1128
|
-
params: ActionParamsMap[T]
|
|
1252
|
+
option: SetupOption
|
|
1129
1253
|
}): Promise<void> {
|
|
1130
|
-
const {
|
|
1131
|
-
const
|
|
1132
|
-
|
|
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) {
|
|
1133
1281
|
throw new PerpsError(
|
|
1134
|
-
|
|
1135
|
-
|
|
1282
|
+
PerpsErrorCode.SDKError,
|
|
1283
|
+
`Setup step '${step.type}' for '${provider}' declares no revoke.`
|
|
1136
1284
|
)
|
|
1137
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)
|
|
1138
1299
|
}
|
|
1139
1300
|
|
|
1140
1301
|
/**
|
|
@@ -1315,6 +1476,18 @@ export class PerpsClient {
|
|
|
1315
1476
|
* deposit); called as each leg is submitted and confirmed. */
|
|
1316
1477
|
onProgress?: (progress: SignActionProgress) => void
|
|
1317
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[] }> {
|
|
1318
1491
|
const { provider, address, action, onProgress } = params
|
|
1319
1492
|
const metadata = await this.getProviderMetadata(provider)
|
|
1320
1493
|
const descriptor = findActionDescriptor(metadata, action)
|
|
@@ -1345,7 +1518,7 @@ export class PerpsClient {
|
|
|
1345
1518
|
// token-authenticated venue mutations), leaving no backend-bound step. With
|
|
1346
1519
|
// nothing to submit, skip the `/executeAction` hop.
|
|
1347
1520
|
if (signedActions.length === 0) {
|
|
1348
|
-
return { results: [] }
|
|
1521
|
+
return { staged: actions, results: [] }
|
|
1349
1522
|
}
|
|
1350
1523
|
|
|
1351
1524
|
const response = await executeAction(this.sdkClient, {
|
|
@@ -1362,7 +1535,7 @@ export class PerpsClient {
|
|
|
1362
1535
|
|
|
1363
1536
|
await this.notifyExecuteResults(provider, address, results)
|
|
1364
1537
|
|
|
1365
|
-
return { results }
|
|
1538
|
+
return { staged: actions, results }
|
|
1366
1539
|
}
|
|
1367
1540
|
|
|
1368
1541
|
/**
|