@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
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @exodus/sui-api · [![npm version](https://img.shields.io/badge/npm-public-blue.svg?style=flat)](https://www.npmjs.com/package/@exodus/sui-api)
2
+
3
+ The **sui-api** provides utilities for interacting with the Sui blockchain.
4
+ It offers functionality to query transaction data, monitor network health, and broadcast transactions
5
+
6
+ ---
7
+
8
+ ## Installation
9
+
10
+ Install the package via `yarn`:
11
+
12
+ ```bash
13
+ yarn add @exodus/sui-api
14
+ ```
15
+
16
+ ## License
17
+
18
+ This project is licensed under the MIT License.
19
+ You are free to use, modify, and distribute this software under the terms of the MIT License.
20
+ For more details, see the [LICENSE](LICENSE) file.
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@exodus/sui-api",
3
+ "packageManager": "yarn@4.3.1",
4
+ "version": "0.1.0",
5
+ "description": "Transaction monitors, RPC with the blockchain node, and other networking code for Sui",
6
+ "type": "module",
7
+ "main": "src/index.js",
8
+ "files": [
9
+ "src",
10
+ "CHANGELOG.md",
11
+ "package.json",
12
+ "README.md",
13
+ "!src/**/__tests__"
14
+ ],
15
+ "author": "Exodus Movement, Inc.",
16
+ "license": "MIT",
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "test": "run -T exodus-test --jest",
22
+ "lint": "run -T eslintc .",
23
+ "lint:fix": "yarn lint --fix"
24
+ },
25
+ "dependencies": {
26
+ "@exodus/asset-lib": "^5.0.0",
27
+ "@exodus/assets": "^11.1.2",
28
+ "@exodus/basic-utils": "^4.2.1",
29
+ "@exodus/crypto": "^1.0.0-rc.26",
30
+ "@exodus/currency": "^6.0.1",
31
+ "@exodus/models": "^12.0.1",
32
+ "@exodus/sui-lib": "^1.1.2",
33
+ "@exodus/sui-meta": "^1.1.0",
34
+ "bn.js": "^5.2.1",
35
+ "lodash": "^4.17.21",
36
+ "minimalistic-assert": "^1.0.1",
37
+ "ms": "^2.1.3",
38
+ "url-join": "^4.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@exodus/asset": "^2.0.4",
42
+ "@exodus/assets-testing": "^1.0.0"
43
+ },
44
+ "homepage": "https://github.com/ExodusMovement/assets/tree/master/sui/sui-api",
45
+ "bugs": {
46
+ "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asui-api"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/ExodusMovement/assets.git"
51
+ },
52
+ "gitHead": "c942fdd35a045680780f1dc5b096aebc5ca54c81"
53
+ }
package/src/api.js ADDED
@@ -0,0 +1,198 @@
1
+ import { RPC_URL } from '@exodus/sui-lib'
2
+ import assert from 'minimalistic-assert'
3
+
4
+ import { SuiClient } from './vendors/client/index.js'
5
+
6
+ const getTextFromResponse = async (response) => {
7
+ try {
8
+ const responseBody = await response.text()
9
+ return responseBody.slice(0, 100)
10
+ } catch {
11
+ return ''
12
+ }
13
+ }
14
+
15
+ const fetchJson = async (url, fetchOptions) => {
16
+ const response = await fetch(url, fetchOptions)
17
+
18
+ if (!response.ok) {
19
+ throw new Error(
20
+ `${url} returned ${response.status}: ${
21
+ response.statusText || 'Unknown Status Text'
22
+ }. Body: ${await getTextFromResponse(response)}`
23
+ )
24
+ }
25
+
26
+ return response.json()
27
+ }
28
+
29
+ export class SuiApi {
30
+ constructor({ rpcUrl }) {
31
+ this.setServer({ rpcUrl })
32
+ }
33
+
34
+ setServer({ rpcUrl = RPC_URL }) {
35
+ this.rpcUrl = encodeURI(rpcUrl)
36
+ this.client = new SuiClient({
37
+ url: rpcUrl,
38
+ })
39
+ }
40
+
41
+ handleJsonRPCResponse(response) {
42
+ const result = response?.result
43
+ const error = response?.error
44
+ if (error || result === undefined) {
45
+ const message = error?.message || error?.code || 'no result'
46
+ throw new Error(`Bad rpc response: ${message}`)
47
+ }
48
+
49
+ return result
50
+ }
51
+
52
+ async request({ baseApiPath, path = '', method, body, headers = Object.create(null) }) {
53
+ assert(typeof baseApiPath === 'string', 'expected string baseApiPath')
54
+
55
+ const url = new URL(`${baseApiPath}${path}`)
56
+ const fetchOptions = {
57
+ method,
58
+ headers: { ...this.getDefaultHeaders(), ...headers },
59
+ }
60
+
61
+ if (body) fetchOptions.body = JSON.stringify(body)
62
+ const response = await fetchJson(url, fetchOptions)
63
+ return this.handleJsonRPCResponse(response)
64
+ }
65
+
66
+ getDefaultHeaders() {
67
+ return {
68
+ Accept: 'application/json',
69
+ 'Content-Type': 'application/json',
70
+ }
71
+ }
72
+
73
+ // Transactions
74
+
75
+ async getTransactionByHash({ hash }) {
76
+ return this.request({
77
+ baseApiPath: this.rpcUrl,
78
+ method: 'POST',
79
+ body: {
80
+ jsonrpc: '2.0',
81
+ id: 1,
82
+ method: 'sui_getTransactionBlock',
83
+ params: [
84
+ hash,
85
+ {
86
+ showInput: true,
87
+ showRawInput: false,
88
+ showEffects: true,
89
+ showEvents: false,
90
+ showObjectChanges: true,
91
+ showBalanceChanges: true,
92
+ showRawEffects: false,
93
+ },
94
+ ],
95
+ },
96
+ })
97
+ }
98
+
99
+ async getAccountBalance({ address }) {
100
+ return this.request({
101
+ baseApiPath: this.rpcUrl,
102
+ method: 'POST',
103
+ body: {
104
+ jsonrpc: '2.0',
105
+ id: 1,
106
+ method: 'suix_getBalance',
107
+ params: [address],
108
+ },
109
+ })
110
+ }
111
+
112
+ async getAccountAllBalances({ address }) {
113
+ return this.request({
114
+ baseApiPath: this.rpcUrl,
115
+ method: 'POST',
116
+ body: {
117
+ jsonrpc: '2.0',
118
+ id: 1,
119
+ method: 'suix_getAllBalances',
120
+ params: [address],
121
+ },
122
+ })
123
+ }
124
+
125
+ async getTransactions({ txIds }) {
126
+ return this.client.multiGetTransactionBlocks({
127
+ digests: txIds,
128
+ options: {
129
+ showEffects: true,
130
+ showObjectChanges: true,
131
+ showBalanceChanges: true,
132
+ showInput: true,
133
+ },
134
+ })
135
+ }
136
+
137
+ async getAllCoins({ owner }) {
138
+ const requestByCursor = async ({ cursor }) => {
139
+ return this.client.getAllCoins({
140
+ owner,
141
+ cursor,
142
+ limit: 100,
143
+ })
144
+ }
145
+
146
+ const coins = []
147
+ let cursor = null
148
+
149
+ while (true) {
150
+ const response = await requestByCursor({ cursor })
151
+ coins.push(...response.data)
152
+ if (!response.nextCursor || !response.hasNextPage) {
153
+ break
154
+ }
155
+
156
+ cursor = response.nextCursor
157
+ }
158
+
159
+ return coins
160
+ }
161
+
162
+ async getCoins({ owner, coinType }) {
163
+ const requestByCursor = async ({ cursor }) => {
164
+ return this.client.getCoins({
165
+ owner,
166
+ coinType,
167
+ cursor,
168
+ limit: 100,
169
+ })
170
+ }
171
+
172
+ const coins = []
173
+ let cursor = null
174
+ while (true) {
175
+ const response = await requestByCursor({ cursor })
176
+ coins.push(...response.data)
177
+ if (!response.nextCursor || !response.hasNextPage) {
178
+ break
179
+ }
180
+
181
+ cursor = response.nextCursor
182
+ }
183
+
184
+ return coins
185
+ }
186
+
187
+ async broadcastTx(plainTx) {
188
+ return this.client.executeTransactionBlock({
189
+ transactionBlock: plainTx.bytes,
190
+ signature: plainTx.signature,
191
+ options: {
192
+ showEffects: true,
193
+ showObjectChanges: true,
194
+ showBalanceChanges: true,
195
+ },
196
+ })
197
+ }
198
+ }
@@ -0,0 +1,22 @@
1
+ export const getBalances = ({ asset, accountState }) => {
2
+ const isBaseAsset = asset.name === asset.baseAssetName
3
+
4
+ if (isBaseAsset) {
5
+ return {
6
+ total: accountState.utxos.value,
7
+ spendable: accountState.utxos.value,
8
+ }
9
+ }
10
+
11
+ if (accountState.tokenUtxos[asset.name]) {
12
+ return {
13
+ total: accountState.tokenUtxos[asset.name].value,
14
+ spendable: accountState.tokenUtxos[asset.name].value,
15
+ }
16
+ }
17
+
18
+ return {
19
+ total: asset.currency.ZERO,
20
+ spendable: asset.currency.ZERO,
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ import { AccountState, UtxoCollection } from '@exodus/models'
2
+ import { asset } from '@exodus/sui-meta'
3
+
4
+ export const createBasicAccountState = () => {
5
+ return class SuiAccountState extends AccountState {
6
+ static defaults = {
7
+ utxos: UtxoCollection.createEmpty({ currency: asset.currency }),
8
+ tokenUtxos: Object.create(null),
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,60 @@
1
+ import { UtxoCollection } from '@exodus/models'
2
+ import assert from 'minimalistic-assert'
3
+
4
+ import { Transaction } from './vendors/transactions/index.js'
5
+
6
+ export const createUnsignedTxFactory = ({ assetClientInterface, serverApi }) => {
7
+ assert(assetClientInterface, 'assetClientInterface is required')
8
+ return async ({
9
+ asset,
10
+ senderAddress,
11
+ recipientAddress,
12
+ amount,
13
+ gasBudget,
14
+ walletAccount,
15
+ buildOptions = Object.create(null),
16
+ }) => {
17
+ const accountState = await assetClientInterface.getAccountState({
18
+ assetName: asset.baseAssetName,
19
+ walletAccount,
20
+ })
21
+
22
+ const txb = new Transaction()
23
+
24
+ txb.setGasBudget(BigInt(gasBudget))
25
+
26
+ if (asset.name === asset.baseAssetName) {
27
+ txb.transferObjects([txb.splitCoins(txb.gas, [amount])], txb.pure.address(recipientAddress))
28
+ txb.setSender(senderAddress)
29
+
30
+ return {
31
+ txData: await txb.build({ client: serverApi.client, ...buildOptions }),
32
+ txMeta: Object.create(null),
33
+ }
34
+ }
35
+
36
+ const usedCoins = (
37
+ accountState.tokenUtxos[asset.name] ||
38
+ UtxoCollection.createEmpty({ currency: asset.currency })
39
+ ).toArray()
40
+
41
+ usedCoins.reverse()
42
+
43
+ const coinObjectIds = usedCoins.map((coin) => coin.coinObjectId)
44
+
45
+ const [inputCoin, ...rest] = coinObjectIds
46
+ if (rest.length > 0) {
47
+ txb.mergeCoins(inputCoin, rest)
48
+ }
49
+
50
+ const [coin] = txb.splitCoins(inputCoin, [amount])
51
+
52
+ txb.transferObjects([coin], txb.pure.address(recipientAddress))
53
+ txb.setSender(senderAddress)
54
+
55
+ return {
56
+ txData: await txb.build({ client: serverApi.client, ...buildOptions }),
57
+ txMeta: Object.create(null),
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,62 @@
1
+ import { MIN_GAS_BUDGET } from '@exodus/sui-lib'
2
+ import assert from 'minimalistic-assert'
3
+
4
+ import { getFeeAmount, getGasBudget } from './transform-tx.js'
5
+
6
+ export const getFeeAsyncFactory = ({ assetClientInterface, serverApi, createUnsignedTx }) => {
7
+ assert(assetClientInterface, 'assetClientInterface is required')
8
+
9
+ return async ({
10
+ asset,
11
+ walletAccount,
12
+ toAddress: recipientAddress,
13
+ fromAddress: senderAddress,
14
+ amount,
15
+ }) => {
16
+ const feeAsset = asset.feeAsset
17
+
18
+ const { txData } = await createUnsignedTx({
19
+ asset,
20
+ senderAddress,
21
+ walletAccount,
22
+ recipientAddress:
23
+ recipientAddress || '0x7a2ddb6af66beb0d9987c6c9010cb9053454f067e16775a8ecf19961195c3d28',
24
+ amount: amount.toBaseString(),
25
+ serverApi,
26
+ gasBudget: MIN_GAS_BUDGET,
27
+ buildOptions: {
28
+ onlyTransactionKind: true,
29
+ },
30
+ })
31
+
32
+ const simulation = await serverApi.client.devInspectTransactionBlock({
33
+ sender: senderAddress,
34
+ transactionBlock: txData,
35
+ })
36
+
37
+ const { gasUsed } = simulation.effects
38
+
39
+ assert(
40
+ gasUsed?.computationCost && gasUsed?.storageCost && gasUsed?.storageRebate,
41
+ 'Invalid simulation result'
42
+ )
43
+
44
+ let fee = getFeeAmount({
45
+ feeAsset,
46
+ gasUsed,
47
+ })
48
+
49
+ const gasBudget = getGasBudget({ gasUsed, feeAsset })
50
+
51
+ if (fee.lt(asset.feeAsset.currency.baseUnit(MIN_GAS_BUDGET))) {
52
+ // sometimes we can receive a negative fee because of storage rebate
53
+ // so we use the gas budget instead
54
+ fee = gasBudget
55
+ }
56
+
57
+ return {
58
+ fee,
59
+ gasBudget: gasBudget.toNumber(),
60
+ }
61
+ }
62
+ }
package/src/get-fee.js ADDED
@@ -0,0 +1,10 @@
1
+ import { DEFAULT_GAS_BUDGET } from '@exodus/sui-lib'
2
+
3
+ export const getFeeFactory =
4
+ ({ asset }) =>
5
+ ({ feeData, feeOpts }) => {
6
+ return {
7
+ fee: asset.feeAsset.currency.baseUnit(DEFAULT_GAS_BUDGET),
8
+ gasBudget: DEFAULT_GAS_BUDGET,
9
+ }
10
+ }
package/src/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { SuiApi } from './api.js'
2
+
3
+ export { getFeeFactory } from './get-fee.js'
4
+ export { createBasicAccountState } from './basic-account-state.js'
5
+ export { getBalances } from './balances.js'
6
+ export { createSuiNoHistoryMonitor } from './sui-no-history-monitor.js'
7
+ export { getFeeAsyncFactory } from './get-fee-async.js'
8
+ export { txSendFactory } from './tx-send.js'
9
+ export { createUnsignedTxFactory } from './create-unsigned-tx.js'