@exodus/sui-api 1.1.3 → 1.2.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/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.2.0](https://github.com/ExodusMovement/assets/compare/@exodus/sui-api@1.1.3...@exodus/sui-api@1.2.0) (2026-01-06)
7
+
8
+
9
+ ### Features
10
+
11
+
12
+ * feat: add createTx for SUI (#7143)
13
+
14
+
15
+
6
16
  ## [1.1.3](https://github.com/ExodusMovement/assets/compare/@exodus/sui-api@1.1.2...@exodus/sui-api@1.1.3) (2025-11-03)
7
17
 
8
18
  **Note:** Version bump only for package @exodus/sui-api
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exodus/sui-api",
3
3
  "packageManager": "yarn@4.3.1",
4
- "version": "1.1.3",
4
+ "version": "1.2.0",
5
5
  "description": "Transaction monitors, RPC with the blockchain node, and other networking code for Sui",
6
6
  "type": "module",
7
7
  "main": "src/index.js",
@@ -50,5 +50,5 @@
50
50
  "type": "git",
51
51
  "url": "git+https://github.com/ExodusMovement/assets.git"
52
52
  },
53
- "gitHead": "1792bda2b5f11b36e67d2e36669341b51002197f"
53
+ "gitHead": "f32e29d35f604bd6857bb1d19e2896b34352f122"
54
54
  }
@@ -21,7 +21,6 @@ export const getFeeAsyncFactory = ({ assetClientInterface, serverApi, createUnsi
21
21
  walletAccount,
22
22
  recipientAddress: recipientAddress || senderAddress,
23
23
  amount: amount.toBaseString(),
24
- serverApi,
25
24
  gasBudget: MIN_GAS_BUDGET,
26
25
  buildOptions: {
27
26
  onlyTransactionKind: true,
package/src/index.js CHANGED
@@ -7,3 +7,4 @@ export { createSuiNoHistoryMonitor } from './sui-no-history-monitor.js'
7
7
  export { getFeeAsyncFactory } from './get-fee-async.js'
8
8
  export { txSendFactory } from './tx-send.js'
9
9
  export { createUnsignedTxFactory } from './create-unsigned-tx.js'
10
+ export { createTxFactory } from './tx-create.js'
@@ -53,7 +53,7 @@ export class SuiNoHistoryMonitor extends BaseMonitor {
53
53
 
54
54
  let batch = this.aci.createOperationsBatch()
55
55
 
56
- batch = await this.aci.updateAccountStateBatch({
56
+ batch = this.aci.updateAccountStateBatch({
57
57
  assetName,
58
58
  walletAccount,
59
59
  newData: {
@@ -71,11 +71,11 @@ export class SuiNoHistoryMonitor extends BaseMonitor {
71
71
  })
72
72
 
73
73
  for (const [assetName, txsToRemove] of Object.entries(txsToRemoveByAsset)) {
74
- batch = await this.aci.removeTxLogBatch({ assetName, walletAccount, txs: txsToRemove, batch })
74
+ batch = this.aci.removeTxLogBatch({ assetName, walletAccount, txs: txsToRemove, batch })
75
75
  }
76
76
 
77
77
  for (const [assetName, txLogItems] of Object.entries(txLogItemsByAsset)) {
78
- batch = await this.aci.updateTxLogAndNotifyBatch({
78
+ batch = this.aci.updateTxLogAndNotifyBatch({
79
79
  assetName,
80
80
  walletAccount,
81
81
  txs: txLogItems,
@@ -0,0 +1,40 @@
1
+ import assert from 'minimalistic-assert'
2
+
3
+ export const createTxFactory = ({
4
+ assetClientInterface: aci,
5
+ serverApi,
6
+ getFeeAsync,
7
+ createUnsignedTx,
8
+ }) => {
9
+ assert(aci, 'assetClientInterface is required')
10
+ assert(serverApi, 'serverApi is required')
11
+ assert(getFeeAsync, 'getFeeAsync is required')
12
+ assert(createUnsignedTx, 'createUnsignedTx is required')
13
+
14
+ return async ({ asset, walletAccount, fromAddress, address: toAddress, amount, gasBudget }) => {
15
+ const { name: assetName } = asset
16
+
17
+ if (!fromAddress) {
18
+ fromAddress = await aci.getReceiveAddress({ assetName, walletAccount })
19
+ }
20
+
21
+ const unsignedTx = await createUnsignedTx({
22
+ asset,
23
+ senderAddress: fromAddress,
24
+ recipientAddress: toAddress,
25
+ amount: amount.toBaseString(),
26
+ gasBudget,
27
+ walletAccount,
28
+ })
29
+
30
+ const { fee } = await getFeeAsync({
31
+ asset,
32
+ walletAccount,
33
+ toAddress,
34
+ fromAddress,
35
+ amount,
36
+ })
37
+
38
+ return { unsignedTx, fee }
39
+ }
40
+ }
package/src/tx-send.js CHANGED
@@ -125,40 +125,26 @@ const getObjectAndBalanceChangesByType = ({
125
125
  }
126
126
  }
127
127
 
128
- const updateAccountStateAfterSend = async ({
129
- aci,
130
- asset,
131
- walletAccount,
132
- ownerAddress,
133
- result,
134
- amount,
135
- recipientAddress,
136
- }) => {
128
+ const getRecipientAddress = ({ balanceChanges, ownerAddress }) => {
129
+ const receiverItem = balanceChanges.find((item) => item.owner.AddressOwner !== ownerAddress)
130
+ return receiverItem?.owner?.AddressOwner
131
+ }
132
+
133
+ // TODO: extract into separate file
134
+ const getStateChanges = async ({ aci, asset, walletAccount, result }) => {
137
135
  const { feeAsset, currency: assetCurrency, name: assetName } = asset
138
136
  const { objectChanges, balanceChanges, digest, effects } = result
139
137
  const { gasUsed } = effects
140
138
  const wasBaseAssetSent = assetName === asset.baseAssetName
141
139
 
142
140
  const fee = getFeeAmount({ gasUsed, feeAsset })
141
+ const ownerAddress = await aci.getReceiveAddress({ assetName, walletAccount })
143
142
 
144
143
  const accountState = await aci.getAccountState({
145
144
  assetName: asset.baseAssetName,
146
145
  walletAccount,
147
146
  })
148
147
 
149
- const tx = {
150
- txId: digest,
151
- confirmations: 0,
152
- coinName: assetName,
153
- coinAmount: amount.abs().negate(),
154
- feeAmount: fee,
155
- feeCoinName: feeAsset.name,
156
- selfSend: false,
157
- to: recipientAddress,
158
- data: Object.create(null),
159
- currencies: { [assetName]: assetCurrency, [feeAsset.name]: feeAsset.currency },
160
- }
161
-
162
148
  const { ownerObjectChangesByType, ownerBalanceChangesByType } = getObjectAndBalanceChangesByType({
163
149
  objectChanges,
164
150
  balanceChanges,
@@ -178,7 +164,10 @@ const updateAccountStateAfterSend = async ({
178
164
 
179
165
  newAccountState.utxos = newBaseUtxos.utxos
180
166
 
181
- if (!wasBaseAssetSent) {
167
+ let amount
168
+ if (wasBaseAssetSent) {
169
+ amount = asset.currency.baseUnit(ownerBalanceChangesByType[coinTypes.Base]).add(fee)
170
+ } else {
182
171
  const coinType = toCoinType(asset.assetId)
183
172
  const newTokenUtxos = deriveNewUtxos({
184
173
  mutatedObject: ownerObjectChangesByType[coinType].mutated[0],
@@ -189,14 +178,26 @@ const updateAccountStateAfterSend = async ({
189
178
  })
190
179
 
191
180
  newAccountState.tokenUtxos = { [asset.name]: newTokenUtxos.utxos }
181
+ amount = asset.currency.baseUnit(ownerBalanceChangesByType[asset.assetId])
192
182
  }
193
183
 
194
- let batch = aci.createOperationsBatch()
184
+ const to = getRecipientAddress({ balanceChanges, ownerAddress })
195
185
 
196
- batch = await aci.updateAccountStateBatch({
197
- assetName: asset.baseAssetName,
198
- walletAccount,
199
- newData: {
186
+ const tx = {
187
+ txId: digest,
188
+ confirmations: 0,
189
+ coinName: assetName,
190
+ coinAmount: amount.abs().negate(),
191
+ feeAmount: fee,
192
+ feeCoinName: feeAsset.name,
193
+ selfSend: false,
194
+ to,
195
+ data: Object.create(null),
196
+ currencies: { [assetName]: assetCurrency, [feeAsset.name]: feeAsset.currency },
197
+ }
198
+
199
+ return {
200
+ accountStateData: {
200
201
  utxos: newAccountState.utxos,
201
202
  tokenUtxos: {
202
203
  ...accountState.tokenUtxos,
@@ -205,52 +206,53 @@ const updateAccountStateAfterSend = async ({
205
206
  : Object.create(null)),
206
207
  },
207
208
  },
208
- batch,
209
- })
210
-
211
- batch = await aci.updateTxLogAndNotifyBatch({ assetName, walletAccount, txs: [tx], batch })
212
-
213
- await aci.executeOperationsBatch(batch)
209
+ txLogsByAssetName: {
210
+ [assetName]: [tx],
211
+ // TODO: add fee-to-send entry to the feeAsset txLog
212
+ },
213
+ }
214
214
  }
215
215
 
216
- export const txSendFactory = ({ assetClientInterface: aci, serverApi, createUnsignedTx }) => {
216
+ export const txSendFactory = ({ assetClientInterface: aci, serverApi, createTx }) => {
217
217
  assert(aci, 'assetClientInterface is required')
218
+ assert(serverApi, 'serverApi is required')
219
+ assert(createTx, 'createTx is required')
218
220
 
219
- return async ({
220
- asset,
221
- walletAccount,
222
- address: recipientAddress,
223
- amount,
224
- gasBudget,
225
- feeOpts,
226
- }) => {
227
- const { name: assetName } = asset
228
-
229
- const senderAddress = await aci.getReceiveAddress({ assetName, walletAccount })
221
+ return async ({ asset, walletAccount, unsignedTx: providedUnsignedTx, ...legacyParams }) => {
222
+ const { unsignedTx } = providedUnsignedTx
223
+ ? { unsignedTx: providedUnsignedTx }
224
+ : await createTx({ asset, walletAccount, ...legacyParams })
230
225
 
231
- const unsignedTx = await createUnsignedTx({
232
- asset,
233
- senderAddress,
234
- recipientAddress,
235
- amount: amount.toBaseString(),
236
- serverApi,
237
- gasBudget: gasBudget || feeOpts.gasBudget,
226
+ const { plainTx } = await aci.signTransaction({
227
+ assetName: asset.name,
228
+ unsignedTx,
238
229
  walletAccount,
239
230
  })
240
231
 
241
- const { plainTx } = await aci.signTransaction({ assetName, unsignedTx, walletAccount })
232
+ const result = await asset.baseAsset.api.broadcastTx(plainTx)
242
233
 
243
- const result = await serverApi.broadcastTx(plainTx)
244
-
245
- await updateAccountStateAfterSend({
246
- aci,
234
+ const { accountStateData: newData, txLogsByAssetName } = await getStateChanges({
247
235
  asset,
248
236
  walletAccount,
249
- ownerAddress: senderAddress,
250
237
  result,
251
- amount,
252
- recipientAddress,
238
+ aci,
253
239
  })
240
+
241
+ let batch = aci.createOperationsBatch()
242
+
243
+ batch = await aci.updateAccountStateBatch({
244
+ assetName: asset.baseAsset.name,
245
+ walletAccount,
246
+ newData,
247
+ batch,
248
+ })
249
+
250
+ Object.entries(txLogsByAssetName).forEach(([assetName, txs]) => {
251
+ batch = aci.updateTxLogAndNotifyBatch({ assetName, walletAccount, txs, batch })
252
+ })
253
+
254
+ await aci.executeOperationsBatch(batch)
255
+
254
256
  return { txId: result.digest }
255
257
  }
256
258
  }