@exodus/sui-api 0.1.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 (44) hide show
  1. package/README.md +20 -0
  2. package/package.json +53 -0
  3. package/src/api.js +198 -0
  4. package/src/balances.js +22 -0
  5. package/src/basic-account-state.js +11 -0
  6. package/src/create-unsigned-tx.js +60 -0
  7. package/src/get-fee-async.js +62 -0
  8. package/src/get-fee.js +10 -0
  9. package/src/index.js +9 -0
  10. package/src/sui-no-history-monitor.js +277 -0
  11. package/src/transform-tx.js +100 -0
  12. package/src/tx-send.js +249 -0
  13. package/src/vendors/client/client.js +891 -0
  14. package/src/vendors/client/errors.js +38 -0
  15. package/src/vendors/client/http-transport.js +108 -0
  16. package/src/vendors/client/index.js +1 -0
  17. package/src/vendors/client/rpc-websocket-client.js +178 -0
  18. package/src/vendors/experimental/cache.js +89 -0
  19. package/src/vendors/experimental/client.js +27 -0
  20. package/src/vendors/experimental/core.js +105 -0
  21. package/src/vendors/experimental/errors.js +32 -0
  22. package/src/vendors/experimental/index.js +5 -0
  23. package/src/vendors/experimental/mvr.js +443 -0
  24. package/src/vendors/experimental/transports/json-rpc-resolver.js +359 -0
  25. package/src/vendors/experimental/transports/jsonRPC.js +504 -0
  26. package/src/vendors/experimental/transports/utils.js +90 -0
  27. package/src/vendors/transactions/Arguments.js +22 -0
  28. package/src/vendors/transactions/Commands.js +108 -0
  29. package/src/vendors/transactions/Inputs.js +59 -0
  30. package/src/vendors/transactions/ObjectCache.js +272 -0
  31. package/src/vendors/transactions/Transaction.js +843 -0
  32. package/src/vendors/transactions/TransactionData.js +293 -0
  33. package/src/vendors/transactions/data/internal.js +218 -0
  34. package/src/vendors/transactions/data/v1.js +538 -0
  35. package/src/vendors/transactions/data/v2.js +119 -0
  36. package/src/vendors/transactions/hash.js +12 -0
  37. package/src/vendors/transactions/index.js +1 -0
  38. package/src/vendors/transactions/object.js +22 -0
  39. package/src/vendors/transactions/plugins/NamedPackagesPlugin.js +49 -0
  40. package/src/vendors/transactions/pure.js +40 -0
  41. package/src/vendors/transactions/resolve.js +95 -0
  42. package/src/vendors/transactions/serializer.js +203 -0
  43. package/src/vendors/transactions/utils.js +67 -0
  44. package/src/vendors/valibot.js +7920 -0
@@ -0,0 +1,359 @@
1
+ /* eslint-disable @exodus/mutable/no-param-reassign-prop-only */
2
+ /* eslint-disable no-return-await */
3
+ import { vendorLib } from '@exodus/sui-lib'
4
+
5
+ import { ObjectRef } from '../../transactions/data/internal.js'
6
+ import { Inputs } from '../../transactions/Inputs.js'
7
+ import {
8
+ getPureBcsSchema,
9
+ isTxContext,
10
+ normalizedTypeToMoveTypeSignature,
11
+ } from '../../transactions/serializer.js'
12
+ import { parse } from '../../valibot.js'
13
+
14
+ const { normalizeSuiAddress, normalizeSuiObjectId, SUI_TYPE_ARG } = vendorLib.utils
15
+
16
+ const { chunk } = vendorLib.utils
17
+
18
+ const MAX_OBJECTS_PER_FETCH = 50
19
+ const GAS_SAFE_OVERHEAD = BigInt(1000)
20
+ const MAX_GAS = 5e10
21
+
22
+ function suiClientResolveTransactionPlugin(client) {
23
+ return async function resolveTransactionData(transactionData, options, next) {
24
+ await normalizeInputs(transactionData, client)
25
+ await resolveObjectReferences(transactionData, client)
26
+ if (!options.onlyTransactionKind) {
27
+ await setGasPrice(transactionData, client)
28
+ await setGasBudget(transactionData, client)
29
+ await setGasPayment(transactionData, client)
30
+ }
31
+
32
+ return await next()
33
+ }
34
+ }
35
+
36
+ async function setGasPrice(transactionData, client) {
37
+ if (!transactionData.gasConfig.price) {
38
+ transactionData.gasConfig.price = String(await client.getReferenceGasPrice())
39
+ }
40
+ }
41
+
42
+ async function setGasBudget(transactionData, client) {
43
+ if (transactionData.gasConfig.budget) {
44
+ return
45
+ }
46
+
47
+ const dryRunResult = await client.dryRunTransactionBlock({
48
+ transactionBlock: transactionData.build({
49
+ overrides: {
50
+ gasData: {
51
+ budget: String(MAX_GAS),
52
+ payment: [],
53
+ },
54
+ },
55
+ }),
56
+ })
57
+ if (dryRunResult.effects.status.status !== 'success') {
58
+ throw new Error(
59
+ `Dry run failed, could not automatically determine a budget: ${dryRunResult.effects.status.error}`,
60
+ { cause: dryRunResult }
61
+ )
62
+ }
63
+
64
+ const safeOverhead = GAS_SAFE_OVERHEAD * BigInt(transactionData.gasConfig.price || BigInt(1))
65
+ const baseComputationCostWithOverhead =
66
+ BigInt(dryRunResult.effects.gasUsed.computationCost) + safeOverhead
67
+ const gasBudget =
68
+ baseComputationCostWithOverhead +
69
+ BigInt(dryRunResult.effects.gasUsed.storageCost) -
70
+ BigInt(dryRunResult.effects.gasUsed.storageRebate)
71
+ transactionData.gasConfig.budget = String(
72
+ gasBudget > baseComputationCostWithOverhead ? gasBudget : baseComputationCostWithOverhead
73
+ )
74
+ }
75
+
76
+ async function setGasPayment(transactionData, client) {
77
+ if (!transactionData.gasConfig.payment) {
78
+ const coins = await client.getCoins({
79
+ owner: transactionData.gasConfig.owner || transactionData.sender,
80
+ coinType: SUI_TYPE_ARG,
81
+ })
82
+ const paymentCoins = coins.data
83
+ .filter((coin) => {
84
+ const matchingInput = transactionData.inputs.find((input) => {
85
+ if (input.Object?.ImmOrOwnedObject) {
86
+ return coin.coinObjectId === input.Object.ImmOrOwnedObject.objectId
87
+ }
88
+
89
+ return false
90
+ })
91
+ return !matchingInput
92
+ })
93
+ .map((coin) => ({
94
+ objectId: coin.coinObjectId,
95
+ digest: coin.digest,
96
+ version: coin.version,
97
+ }))
98
+ if (paymentCoins.length === 0) {
99
+ throw new Error('No valid gas coins found for the transaction.')
100
+ }
101
+
102
+ transactionData.gasConfig.payment = paymentCoins.map((payment) => parse(ObjectRef, payment))
103
+ }
104
+ }
105
+
106
+ async function resolveObjectReferences(transactionData, client) {
107
+ const objectsToResolve = transactionData.inputs.filter((input) => {
108
+ return (
109
+ input.UnresolvedObject &&
110
+ !(input.UnresolvedObject.version || input.UnresolvedObject?.initialSharedVersion)
111
+ )
112
+ })
113
+ const dedupedIds = [
114
+ ...new Set(
115
+ objectsToResolve.map((input) => normalizeSuiObjectId(input.UnresolvedObject.objectId))
116
+ ),
117
+ ]
118
+ const objectChunks = dedupedIds.length > 0 ? chunk(dedupedIds, MAX_OBJECTS_PER_FETCH) : []
119
+ const resolved = (
120
+ await Promise.all(
121
+ objectChunks.map((chunk2) =>
122
+ client.multiGetObjects({
123
+ ids: chunk2,
124
+ options: { showOwner: true },
125
+ })
126
+ )
127
+ )
128
+ )
129
+ // eslint-disable-next-line unicorn/no-await-expression-member
130
+ .flat()
131
+ const responsesById = new Map(
132
+ dedupedIds.map((id, index) => {
133
+ return [id, resolved[index]]
134
+ })
135
+ )
136
+ const invalidObjects = [...responsesById]
137
+ .filter(([_, obj]) => obj.error)
138
+ .map(([_, obj]) => JSON.stringify(obj.error))
139
+ if (invalidObjects.length > 0) {
140
+ throw new Error(`The following input objects are invalid: ${invalidObjects.join(', ')}`)
141
+ }
142
+
143
+ const objects = resolved.map((object) => {
144
+ if (object.error || !object.data) {
145
+ throw new Error(`Failed to fetch object: ${object.error}`)
146
+ }
147
+
148
+ const owner = object.data.owner
149
+ const initialSharedVersion =
150
+ owner && typeof owner === 'object'
151
+ ? 'Shared' in owner
152
+ ? owner.Shared.initial_shared_version
153
+ : 'ConsensusAddressOwner' in owner
154
+ ? owner.ConsensusAddressOwner.start_version
155
+ : null
156
+ : null
157
+ return {
158
+ objectId: object.data.objectId,
159
+ digest: object.data.digest,
160
+ version: object.data.version,
161
+ initialSharedVersion,
162
+ }
163
+ })
164
+ const objectsById = new Map(
165
+ dedupedIds.map((id, index) => {
166
+ return [id, objects[index]]
167
+ })
168
+ )
169
+ for (const [index, input] of transactionData.inputs.entries()) {
170
+ if (!input.UnresolvedObject) {
171
+ continue
172
+ }
173
+
174
+ let updated
175
+ const id = normalizeSuiAddress(input.UnresolvedObject.objectId)
176
+ const object = objectsById.get(id)
177
+ if (input.UnresolvedObject.initialSharedVersion ?? object?.initialSharedVersion) {
178
+ updated = Inputs.SharedObjectRef({
179
+ objectId: id,
180
+ initialSharedVersion:
181
+ input.UnresolvedObject.initialSharedVersion || object?.initialSharedVersion,
182
+ mutable: isUsedAsMutable(transactionData, index),
183
+ })
184
+ } else if (isUsedAsReceiving(transactionData, index)) {
185
+ updated = Inputs.ReceivingRef({
186
+ objectId: id,
187
+ digest: input.UnresolvedObject.digest ?? object?.digest,
188
+ version: input.UnresolvedObject.version ?? object?.version,
189
+ })
190
+ }
191
+
192
+ transactionData.inputs[transactionData.inputs.indexOf(input)] =
193
+ updated ??
194
+ Inputs.ObjectRef({
195
+ objectId: id,
196
+ digest: input.UnresolvedObject.digest ?? object?.digest,
197
+ version: input.UnresolvedObject.version ?? object?.version,
198
+ })
199
+ }
200
+ }
201
+
202
+ async function normalizeInputs(transactionData, client) {
203
+ const { inputs, commands } = transactionData
204
+ const moveCallsToResolve = []
205
+ const moveFunctionsToResolve = /* @__PURE__ */ new Set()
206
+ commands.forEach((command) => {
207
+ if (command.MoveCall) {
208
+ if (command.MoveCall._argumentTypes) {
209
+ return
210
+ }
211
+
212
+ const inputs2 = command.MoveCall.arguments.map((arg) => {
213
+ if (arg.$kind === 'Input') {
214
+ return transactionData.inputs[arg.Input]
215
+ }
216
+
217
+ return null
218
+ })
219
+ const needsResolution = inputs2.some(
220
+ (input) => input?.UnresolvedPure || input?.UnresolvedObject
221
+ )
222
+ if (needsResolution) {
223
+ const functionName = `${command.MoveCall.package}::${command.MoveCall.module}::${command.MoveCall.function}`
224
+ moveFunctionsToResolve.add(functionName)
225
+ moveCallsToResolve.push(command.MoveCall)
226
+ }
227
+ }
228
+ })
229
+ const moveFunctionParameters = /* @__PURE__ */ new Map()
230
+ if (moveFunctionsToResolve.size > 0) {
231
+ await Promise.all(
232
+ [...moveFunctionsToResolve].map(async (functionName) => {
233
+ const [packageId, moduleId, functionId] = functionName.split('::')
234
+ const def = await client.getNormalizedMoveFunction({
235
+ package: packageId,
236
+ module: moduleId,
237
+ function: functionId,
238
+ })
239
+ moveFunctionParameters.set(
240
+ functionName,
241
+ def.parameters.map((param) => normalizedTypeToMoveTypeSignature(param))
242
+ )
243
+ })
244
+ )
245
+ }
246
+
247
+ if (moveCallsToResolve.length > 0) {
248
+ await Promise.all(
249
+ moveCallsToResolve.map(async (moveCall) => {
250
+ const parameters = moveFunctionParameters.get(
251
+ `${moveCall.package}::${moveCall.module}::${moveCall.function}`
252
+ )
253
+ if (!parameters) {
254
+ return
255
+ }
256
+
257
+ const hasTxContext = parameters.length > 0 && isTxContext(parameters.at(-1))
258
+ const params = hasTxContext ? parameters.slice(0, -1) : parameters
259
+ moveCall._argumentTypes = params
260
+ })
261
+ )
262
+ }
263
+
264
+ commands.forEach((command) => {
265
+ if (!command.MoveCall) {
266
+ return
267
+ }
268
+
269
+ const moveCall = command.MoveCall
270
+ const fnName = `${moveCall.package}::${moveCall.module}::${moveCall.function}`
271
+ const params = moveCall._argumentTypes
272
+ if (!params) {
273
+ return
274
+ }
275
+
276
+ if (params.length !== command.MoveCall.arguments.length) {
277
+ throw new Error(`Incorrect number of arguments for ${fnName}`)
278
+ }
279
+
280
+ params.forEach((param, i) => {
281
+ const arg = moveCall.arguments[i]
282
+ if (arg.$kind !== 'Input') return
283
+ const input = inputs[arg.Input]
284
+ if (!input.UnresolvedPure && !input.UnresolvedObject) {
285
+ return
286
+ }
287
+
288
+ const inputValue = input.UnresolvedPure?.value ?? input.UnresolvedObject?.objectId
289
+ const schema = getPureBcsSchema(param.body)
290
+ if (schema) {
291
+ arg.type = 'pure'
292
+ inputs[inputs.indexOf(input)] = Inputs.Pure(schema.serialize(inputValue))
293
+ return
294
+ }
295
+
296
+ if (typeof inputValue !== 'string') {
297
+ throw new TypeError(
298
+ `Expect the argument to be an object id string, got ${JSON.stringify(
299
+ inputValue,
300
+ null,
301
+ 2
302
+ )}`
303
+ )
304
+ }
305
+
306
+ arg.type = 'object'
307
+ const unresolvedObject = input.UnresolvedPure
308
+ ? {
309
+ $kind: 'UnresolvedObject',
310
+ UnresolvedObject: {
311
+ objectId: inputValue,
312
+ },
313
+ }
314
+ : input
315
+ inputs[arg.Input] = unresolvedObject
316
+ })
317
+ })
318
+ }
319
+
320
+ function isUsedAsMutable(transactionData, index) {
321
+ let usedAsMutable = false
322
+ transactionData.getInputUses(index, (arg, tx) => {
323
+ if (tx.MoveCall && tx.MoveCall._argumentTypes) {
324
+ const argIndex = tx.MoveCall.arguments.indexOf(arg)
325
+ usedAsMutable = tx.MoveCall._argumentTypes[argIndex].ref !== '&' || usedAsMutable
326
+ }
327
+
328
+ if (tx.$kind === 'MakeMoveVec' || tx.$kind === 'MergeCoins' || tx.$kind === 'SplitCoins') {
329
+ usedAsMutable = true
330
+ }
331
+ })
332
+ return usedAsMutable
333
+ }
334
+
335
+ function isUsedAsReceiving(transactionData, index) {
336
+ let usedAsReceiving = false
337
+ transactionData.getInputUses(index, (arg, tx) => {
338
+ if (tx.MoveCall && tx.MoveCall._argumentTypes) {
339
+ const argIndex = tx.MoveCall.arguments.indexOf(arg)
340
+ usedAsReceiving = isReceivingType(tx.MoveCall._argumentTypes[argIndex]) || usedAsReceiving
341
+ }
342
+ })
343
+ return usedAsReceiving
344
+ }
345
+
346
+ function isReceivingType(type) {
347
+ if (typeof type.body !== 'object' || !('datatype' in type.body)) {
348
+ return false
349
+ }
350
+
351
+ return (
352
+ type.body.datatype.package === '0x2' &&
353
+ type.body.datatype.module === 'transfer' &&
354
+ type.body.datatype.type === 'Receiving'
355
+ )
356
+ }
357
+
358
+ export { suiClientResolveTransactionPlugin }
359
+ // # sourceMappingURL=json-rpc-resolver.js.map