@sip-protocol/sdk 0.7.3 → 0.8.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 (263) hide show
  1. package/README.md +267 -0
  2. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  3. package/dist/browser.d.mts +10 -4
  4. package/dist/browser.d.ts +10 -4
  5. package/dist/browser.js +47556 -19603
  6. package/dist/browser.mjs +628 -48
  7. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  8. package/dist/chunk-5D7A3L3W.mjs +717 -0
  9. package/dist/chunk-64AYA5F5.mjs +7834 -0
  10. package/dist/chunk-GMDGB22A.mjs +379 -0
  11. package/dist/chunk-I534WKN7.mjs +328 -0
  12. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  13. package/dist/chunk-PRRZAWJE.mjs +223 -0
  14. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  15. package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  16. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  17. package/dist/constants-LHAAUC2T.mjs +51 -0
  18. package/dist/dist-2OGQ7FED.mjs +3957 -0
  19. package/dist/dist-IFHPYLDX.mjs +254 -0
  20. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  21. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  22. package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  23. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  24. package/dist/index.d.mts +9 -3
  25. package/dist/index.d.ts +9 -3
  26. package/dist/index.js +48396 -19623
  27. package/dist/index.mjs +537 -19
  28. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  29. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  30. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  31. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  32. package/dist/proofs/halo2.d.mts +151 -0
  33. package/dist/proofs/halo2.d.ts +151 -0
  34. package/dist/proofs/halo2.js +350 -0
  35. package/dist/proofs/halo2.mjs +11 -0
  36. package/dist/proofs/kimchi.d.mts +160 -0
  37. package/dist/proofs/kimchi.d.ts +160 -0
  38. package/dist/proofs/kimchi.js +431 -0
  39. package/dist/proofs/kimchi.mjs +13 -0
  40. package/dist/proofs/noir.d.mts +1 -1
  41. package/dist/proofs/noir.d.ts +1 -1
  42. package/dist/proofs/noir.js +74 -18
  43. package/dist/proofs/noir.mjs +84 -24
  44. package/dist/solana-U3MEGU7W.mjs +280 -0
  45. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  46. package/package.json +44 -11
  47. package/src/adapters/index.ts +41 -0
  48. package/src/adapters/jupiter.ts +571 -0
  49. package/src/adapters/near-intents.ts +135 -0
  50. package/src/advisor/advisor.ts +653 -0
  51. package/src/advisor/index.ts +54 -0
  52. package/src/advisor/tools.ts +303 -0
  53. package/src/advisor/types.ts +164 -0
  54. package/src/chains/ethereum/announcement.ts +536 -0
  55. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  56. package/src/chains/ethereum/commitment.ts +522 -0
  57. package/src/chains/ethereum/constants.ts +462 -0
  58. package/src/chains/ethereum/deployment.ts +596 -0
  59. package/src/chains/ethereum/gas-estimation.ts +538 -0
  60. package/src/chains/ethereum/index.ts +268 -0
  61. package/src/chains/ethereum/optimizations.ts +614 -0
  62. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  63. package/src/chains/ethereum/registry.ts +584 -0
  64. package/src/chains/ethereum/rpc.ts +905 -0
  65. package/src/chains/ethereum/stealth.ts +491 -0
  66. package/src/chains/ethereum/token.ts +790 -0
  67. package/src/chains/ethereum/transfer.ts +637 -0
  68. package/src/chains/ethereum/types.ts +456 -0
  69. package/src/chains/ethereum/viewing-key.ts +455 -0
  70. package/src/chains/near/commitment.ts +608 -0
  71. package/src/chains/near/constants.ts +284 -0
  72. package/src/chains/near/function-call.ts +871 -0
  73. package/src/chains/near/history.ts +654 -0
  74. package/src/chains/near/implicit-account.ts +840 -0
  75. package/src/chains/near/index.ts +393 -0
  76. package/src/chains/near/native-transfer.ts +658 -0
  77. package/src/chains/near/nep141.ts +775 -0
  78. package/src/chains/near/privacy-adapter.ts +889 -0
  79. package/src/chains/near/resolver.ts +971 -0
  80. package/src/chains/near/rpc.ts +1016 -0
  81. package/src/chains/near/stealth.ts +419 -0
  82. package/src/chains/near/types.ts +317 -0
  83. package/src/chains/near/viewing-key.ts +876 -0
  84. package/src/chains/solana/anchor-transfer.ts +386 -0
  85. package/src/chains/solana/commitment.ts +577 -0
  86. package/src/chains/solana/constants.ts +126 -12
  87. package/src/chains/solana/ephemeral-keys.ts +543 -0
  88. package/src/chains/solana/index.ts +252 -1
  89. package/src/chains/solana/key-derivation.ts +418 -0
  90. package/src/chains/solana/kit-compat.ts +334 -0
  91. package/src/chains/solana/optimizations.ts +560 -0
  92. package/src/chains/solana/privacy-adapter.ts +605 -0
  93. package/src/chains/solana/providers/generic.ts +47 -6
  94. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  95. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  96. package/src/chains/solana/providers/helius.ts +186 -33
  97. package/src/chains/solana/providers/index.ts +31 -0
  98. package/src/chains/solana/providers/interface.ts +61 -18
  99. package/src/chains/solana/providers/quicknode.ts +409 -0
  100. package/src/chains/solana/providers/triton.ts +426 -0
  101. package/src/chains/solana/providers/webhook.ts +338 -67
  102. package/src/chains/solana/rpc-client.ts +1150 -0
  103. package/src/chains/solana/scan.ts +83 -66
  104. package/src/chains/solana/sol-transfer.ts +732 -0
  105. package/src/chains/solana/spl-transfer.ts +886 -0
  106. package/src/chains/solana/stealth-scanner.ts +703 -0
  107. package/src/chains/solana/sunspot-verifier.ts +453 -0
  108. package/src/chains/solana/transaction-builder.ts +755 -0
  109. package/src/chains/solana/transfer.ts +74 -5
  110. package/src/chains/solana/types.ts +57 -6
  111. package/src/chains/solana/utils.ts +110 -0
  112. package/src/chains/solana/viewing-key.ts +807 -0
  113. package/src/compliance/fireblocks.ts +921 -0
  114. package/src/compliance/index.ts +23 -0
  115. package/src/compliance/range-sas.ts +398 -33
  116. package/src/config/endpoints.ts +100 -0
  117. package/src/crypto.ts +11 -8
  118. package/src/errors.ts +82 -0
  119. package/src/evm/erc4337-relayer.ts +830 -0
  120. package/src/evm/index.ts +47 -0
  121. package/src/fees/calculator.ts +396 -0
  122. package/src/fees/index.ts +87 -0
  123. package/src/fees/near-contract.ts +429 -0
  124. package/src/fees/types.ts +268 -0
  125. package/src/index.ts +686 -1
  126. package/src/intent.ts +6 -3
  127. package/src/logger.ts +324 -0
  128. package/src/network/index.ts +80 -0
  129. package/src/network/proxy.ts +691 -0
  130. package/src/optimizations/index.ts +541 -0
  131. package/src/oracle/types.ts +1 -0
  132. package/src/privacy-backends/arcium-types.ts +727 -0
  133. package/src/privacy-backends/arcium.ts +719 -0
  134. package/src/privacy-backends/combined-privacy.ts +866 -0
  135. package/src/privacy-backends/cspl-token.ts +595 -0
  136. package/src/privacy-backends/cspl-types.ts +512 -0
  137. package/src/privacy-backends/cspl.ts +907 -0
  138. package/src/privacy-backends/health.ts +488 -0
  139. package/src/privacy-backends/inco-types.ts +323 -0
  140. package/src/privacy-backends/inco.ts +616 -0
  141. package/src/privacy-backends/index.ts +254 -4
  142. package/src/privacy-backends/interface.ts +649 -6
  143. package/src/privacy-backends/lru-cache.ts +343 -0
  144. package/src/privacy-backends/magicblock.ts +458 -0
  145. package/src/privacy-backends/mock.ts +258 -0
  146. package/src/privacy-backends/privacycash.ts +13 -17
  147. package/src/privacy-backends/private-swap.ts +570 -0
  148. package/src/privacy-backends/rate-limiter.ts +683 -0
  149. package/src/privacy-backends/registry.ts +414 -2
  150. package/src/privacy-backends/router.ts +283 -3
  151. package/src/privacy-backends/shadowwire.ts +449 -0
  152. package/src/privacy-backends/sip-native.ts +3 -0
  153. package/src/privacy-logger.ts +191 -0
  154. package/src/production-safety.ts +373 -0
  155. package/src/proofs/aggregator.ts +1029 -0
  156. package/src/proofs/browser-composer.ts +1150 -0
  157. package/src/proofs/browser.ts +113 -25
  158. package/src/proofs/cache/index.ts +127 -0
  159. package/src/proofs/cache/interface.ts +545 -0
  160. package/src/proofs/cache/key-generator.ts +188 -0
  161. package/src/proofs/cache/lru-cache.ts +481 -0
  162. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  163. package/src/proofs/cache/persistent-cache.ts +788 -0
  164. package/src/proofs/compliance-proof.ts +872 -0
  165. package/src/proofs/composer/base.ts +923 -0
  166. package/src/proofs/composer/index.ts +25 -0
  167. package/src/proofs/composer/interface.ts +518 -0
  168. package/src/proofs/composer/types.ts +383 -0
  169. package/src/proofs/converters/halo2.ts +452 -0
  170. package/src/proofs/converters/index.ts +208 -0
  171. package/src/proofs/converters/interface.ts +363 -0
  172. package/src/proofs/converters/kimchi.ts +462 -0
  173. package/src/proofs/converters/noir.ts +451 -0
  174. package/src/proofs/fallback.ts +888 -0
  175. package/src/proofs/halo2.ts +42 -0
  176. package/src/proofs/index.ts +471 -0
  177. package/src/proofs/interface.ts +13 -0
  178. package/src/proofs/kimchi.ts +42 -0
  179. package/src/proofs/lazy.ts +1004 -0
  180. package/src/proofs/mock.ts +25 -1
  181. package/src/proofs/noir.ts +110 -29
  182. package/src/proofs/orchestrator.ts +960 -0
  183. package/src/proofs/parallel/concurrency.ts +297 -0
  184. package/src/proofs/parallel/dependency-graph.ts +602 -0
  185. package/src/proofs/parallel/executor.ts +420 -0
  186. package/src/proofs/parallel/index.ts +131 -0
  187. package/src/proofs/parallel/interface.ts +685 -0
  188. package/src/proofs/parallel/worker-pool.ts +644 -0
  189. package/src/proofs/providers/halo2.ts +560 -0
  190. package/src/proofs/providers/index.ts +34 -0
  191. package/src/proofs/providers/kimchi.ts +641 -0
  192. package/src/proofs/validator.ts +881 -0
  193. package/src/proofs/verifier.ts +867 -0
  194. package/src/quantum/index.ts +112 -0
  195. package/src/quantum/winternitz-vault.ts +639 -0
  196. package/src/quantum/wots.ts +611 -0
  197. package/src/settlement/backends/direct-chain.ts +1 -0
  198. package/src/settlement/index.ts +9 -0
  199. package/src/settlement/router.ts +732 -46
  200. package/src/solana/index.ts +72 -0
  201. package/src/solana/jito-relayer.ts +687 -0
  202. package/src/solana/noir-verifier-types.ts +430 -0
  203. package/src/solana/noir-verifier.ts +816 -0
  204. package/src/stealth/address-derivation.ts +193 -0
  205. package/src/stealth/ed25519.ts +431 -0
  206. package/src/stealth/index.ts +233 -0
  207. package/src/stealth/meta-address.ts +221 -0
  208. package/src/stealth/secp256k1.ts +368 -0
  209. package/src/stealth/utils.ts +194 -0
  210. package/src/stealth.ts +50 -1504
  211. package/src/sync/index.ts +106 -0
  212. package/src/sync/manager.ts +504 -0
  213. package/src/sync/mock-provider.ts +318 -0
  214. package/src/sync/oblivious.ts +625 -0
  215. package/src/tokens/index.ts +15 -0
  216. package/src/tokens/registry.ts +301 -0
  217. package/src/utils/deprecation.ts +94 -0
  218. package/src/utils/index.ts +9 -0
  219. package/src/wallet/ethereum/index.ts +68 -0
  220. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  221. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  222. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  223. package/src/wallet/ethereum/types.ts +3 -1
  224. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  225. package/src/wallet/hardware/index.ts +10 -0
  226. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  227. package/src/wallet/index.ts +71 -0
  228. package/src/wallet/near/adapter.ts +626 -0
  229. package/src/wallet/near/index.ts +86 -0
  230. package/src/wallet/near/meteor-wallet.ts +1153 -0
  231. package/src/wallet/near/my-near-wallet.ts +790 -0
  232. package/src/wallet/near/wallet-selector.ts +702 -0
  233. package/src/wallet/solana/adapter.ts +6 -4
  234. package/src/wallet/solana/index.ts +13 -0
  235. package/src/wallet/solana/privacy-adapter.ts +567 -0
  236. package/src/wallet/sui/types.ts +6 -4
  237. package/src/zcash/rpc-client.ts +13 -6
  238. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  239. package/dist/chunk-3INS3PR5.mjs +0 -884
  240. package/dist/chunk-3OVABDRH.mjs +0 -17096
  241. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  242. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  243. package/dist/chunk-E6SZWREQ.mjs +0 -57
  244. package/dist/chunk-F6F73W35.mjs +0 -16166
  245. package/dist/chunk-G33LB27A.mjs +0 -16166
  246. package/dist/chunk-HGU6HZRC.mjs +0 -231
  247. package/dist/chunk-L2K34JCU.mjs +0 -1496
  248. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  249. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  250. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  251. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  252. package/dist/constants-VOI7BSLK.mjs +0 -27
  253. package/dist/index-B71aXVzk.d.ts +0 -13264
  254. package/dist/index-BYZbDjal.d.ts +0 -11390
  255. package/dist/index-CHB3KuOB.d.mts +0 -11859
  256. package/dist/index-CzWPI6Le.d.ts +0 -11859
  257. package/dist/index-pOIIuwfV.d.mts +0 -13264
  258. package/dist/index-xbWjohNq.d.mts +0 -11390
  259. package/dist/solana-4O4K45VU.mjs +0 -46
  260. package/dist/solana-5EMCTPTS.mjs +0 -46
  261. package/dist/solana-NDABAZ6P.mjs +0 -56
  262. package/dist/solana-Q4NAVBTS.mjs +0 -46
  263. package/dist/solana-ZYO63LY5.mjs +0 -46
@@ -0,0 +1,1016 @@
1
+ /**
2
+ * NEAR RPC Client for Privacy Transactions
3
+ *
4
+ * Provides robust RPC integration for submitting and monitoring
5
+ * privacy-enhanced transactions on NEAR Protocol.
6
+ *
7
+ * @module chains/near/rpc
8
+ */
9
+
10
+ import { ValidationError } from '../../errors'
11
+ import {
12
+ NEAR_RPC_ENDPOINTS,
13
+ isValidAccountId,
14
+ type NEARNetwork,
15
+ } from './constants'
16
+
17
+ // ─── Types ────────────────────────────────────────────────────────────────────
18
+
19
+ /**
20
+ * NEAR RPC error codes
21
+ */
22
+ export enum NEARErrorCode {
23
+ // Network errors
24
+ NETWORK_ERROR = 'NETWORK_ERROR',
25
+ TIMEOUT = 'TIMEOUT',
26
+ RPC_ERROR = 'RPC_ERROR',
27
+
28
+ // Transaction errors
29
+ INVALID_TRANSACTION = 'INVALID_TRANSACTION',
30
+ INVALID_NONCE = 'INVALID_NONCE',
31
+ INSUFFICIENT_BALANCE = 'INSUFFICIENT_BALANCE',
32
+ ACCOUNT_NOT_FOUND = 'ACCOUNT_NOT_FOUND',
33
+ ACCESS_KEY_NOT_FOUND = 'ACCESS_KEY_NOT_FOUND',
34
+
35
+ // Execution errors
36
+ ACTION_ERROR = 'ACTION_ERROR',
37
+ RECEIPT_VALIDATION_ERROR = 'RECEIPT_VALIDATION_ERROR',
38
+
39
+ // Unknown
40
+ UNKNOWN = 'UNKNOWN',
41
+ }
42
+
43
+ /**
44
+ * NEAR RPC error
45
+ */
46
+ export class NEARRpcClientError extends Error {
47
+ constructor(
48
+ message: string,
49
+ public readonly code: NEARErrorCode,
50
+ public readonly cause?: unknown,
51
+ public readonly data?: unknown
52
+ ) {
53
+ super(message)
54
+ this.name = 'NEARRpcClientError'
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Transaction finality levels
60
+ */
61
+ export type NEARFinality = 'optimistic' | 'near-final' | 'final'
62
+
63
+ /**
64
+ * Transaction status
65
+ */
66
+ export type NEARTransactionStatus =
67
+ | 'pending'
68
+ | 'included'
69
+ | 'executed'
70
+ | 'final'
71
+ | 'failed'
72
+
73
+ /**
74
+ * RPC client configuration
75
+ */
76
+ export interface NEARRpcConfig {
77
+ /**
78
+ * Primary RPC URL
79
+ */
80
+ rpcUrl: string
81
+
82
+ /**
83
+ * Fallback RPC URLs for redundancy
84
+ */
85
+ fallbackUrls?: string[]
86
+
87
+ /**
88
+ * Network type
89
+ * @default 'mainnet'
90
+ */
91
+ network?: NEARNetwork
92
+
93
+ /**
94
+ * Request timeout in milliseconds
95
+ * @default 30000
96
+ */
97
+ timeout?: number
98
+
99
+ /**
100
+ * Maximum retry attempts
101
+ * @default 3
102
+ */
103
+ maxRetries?: number
104
+
105
+ /**
106
+ * Initial retry delay in milliseconds
107
+ * @default 1000
108
+ */
109
+ retryDelay?: number
110
+
111
+ /**
112
+ * Retry delay multiplier for exponential backoff
113
+ * @default 2
114
+ */
115
+ retryMultiplier?: number
116
+ }
117
+
118
+ /**
119
+ * Access key information
120
+ */
121
+ export interface NEARAccessKey {
122
+ nonce: bigint
123
+ permission: 'FullAccess' | {
124
+ FunctionCall: {
125
+ allowance: string | null
126
+ receiver_id: string
127
+ method_names: string[]
128
+ }
129
+ }
130
+ blockHeight: number
131
+ blockHash: string
132
+ }
133
+
134
+ /**
135
+ * Account information
136
+ */
137
+ export interface NEARAccountInfo {
138
+ amount: bigint
139
+ locked: bigint
140
+ codeHash: string
141
+ storageUsage: number
142
+ storagePaidAt: number
143
+ blockHeight: number
144
+ blockHash: string
145
+ }
146
+
147
+ /**
148
+ * Block information
149
+ */
150
+ export interface NEARBlockInfo {
151
+ height: number
152
+ hash: string
153
+ timestamp: number
154
+ prevHash: string
155
+ gasPrice: bigint
156
+ }
157
+
158
+ /**
159
+ * Transaction outcome
160
+ */
161
+ export interface NEARTransactionOutcome {
162
+ txHash: string
163
+ signerId: string
164
+ receiverId: string
165
+ status: NEARTransactionStatus
166
+ finalityStatus: NEARFinality
167
+ blockHash: string
168
+ blockHeight: number
169
+ gasUsed: bigint
170
+ tokensBurnt: bigint
171
+ logs: string[]
172
+ receipts: NEARReceiptOutcome[]
173
+ error?: string
174
+ }
175
+
176
+ /**
177
+ * Receipt outcome
178
+ */
179
+ export interface NEARReceiptOutcome {
180
+ receiptId: string
181
+ receiverId: string
182
+ status: 'success' | 'failure'
183
+ gasUsed: bigint
184
+ tokensBurnt: bigint
185
+ logs: string[]
186
+ error?: string
187
+ }
188
+
189
+ /**
190
+ * Signed transaction for broadcast
191
+ */
192
+ export interface NEARSignedTransaction {
193
+ /**
194
+ * Base64 encoded signed transaction
195
+ */
196
+ signedTx: string
197
+
198
+ /**
199
+ * Transaction hash (computed from signed tx)
200
+ */
201
+ txHash: string
202
+ }
203
+
204
+ /**
205
+ * Transaction status result
206
+ */
207
+ export interface NEARTxStatusResult {
208
+ status: NEARTransactionStatus
209
+ finality: NEARFinality
210
+ outcome?: NEARTransactionOutcome
211
+ error?: string
212
+ }
213
+
214
+ /**
215
+ * Poll options for transaction status
216
+ */
217
+ export interface NEARPollOptions {
218
+ /**
219
+ * Maximum time to poll in milliseconds
220
+ * @default 60000
221
+ */
222
+ maxWaitMs?: number
223
+
224
+ /**
225
+ * Initial poll interval in milliseconds
226
+ * @default 1000
227
+ */
228
+ initialIntervalMs?: number
229
+
230
+ /**
231
+ * Maximum poll interval in milliseconds
232
+ * @default 5000
233
+ */
234
+ maxIntervalMs?: number
235
+
236
+ /**
237
+ * Target finality level to wait for
238
+ * @default 'final'
239
+ */
240
+ targetFinality?: NEARFinality
241
+ }
242
+
243
+ // ─── NEARRpcClient Class ──────────────────────────────────────────────────────
244
+
245
+ /**
246
+ * NEAR RPC Client
247
+ *
248
+ * Provides robust RPC integration for NEAR privacy transactions:
249
+ * - Transaction broadcasting with nonce management
250
+ * - Status polling with exponential backoff
251
+ * - Finality detection
252
+ * - RPC failover for reliability
253
+ *
254
+ * @example Basic usage
255
+ * ```typescript
256
+ * const rpc = new NEARRpcClient({
257
+ * rpcUrl: 'https://rpc.mainnet.near.org',
258
+ * network: 'mainnet',
259
+ * })
260
+ *
261
+ * // Get account info
262
+ * const account = await rpc.getAccount('alice.near')
263
+ *
264
+ * // Get access key for nonce
265
+ * const accessKey = await rpc.getAccessKey('alice.near', publicKey)
266
+ *
267
+ * // Broadcast and wait for finality
268
+ * const result = await rpc.broadcastTxAwait(signedTx)
269
+ * ```
270
+ */
271
+ export class NEARRpcClient {
272
+ private urls: string[]
273
+ private currentUrlIndex: number = 0
274
+ private timeout: number
275
+ private maxRetries: number
276
+ private retryDelay: number
277
+ private retryMultiplier: number
278
+ private network: NEARNetwork
279
+
280
+ constructor(config: NEARRpcConfig) {
281
+ this.urls = [config.rpcUrl, ...(config.fallbackUrls ?? [])]
282
+ this.network = config.network ?? 'mainnet'
283
+ this.timeout = config.timeout ?? 30000
284
+ this.maxRetries = config.maxRetries ?? 3
285
+ this.retryDelay = config.retryDelay ?? 1000
286
+ this.retryMultiplier = config.retryMultiplier ?? 2
287
+ }
288
+
289
+ // ─── Core RPC Methods ───────────────────────────────────────────────────────
290
+
291
+ /**
292
+ * Make an RPC call with retry and failover
293
+ */
294
+ async call<T>(method: string, params: unknown): Promise<T> {
295
+ let lastError: Error | undefined
296
+ let delay = this.retryDelay
297
+
298
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
299
+ for (let urlIdx = 0; urlIdx < this.urls.length; urlIdx++) {
300
+ const url = this.urls[(this.currentUrlIndex + urlIdx) % this.urls.length]
301
+
302
+ try {
303
+ return await this.makeRequest<T>(url, method, params)
304
+ } catch (error) {
305
+ lastError = error as Error
306
+
307
+ // Check if error is retryable
308
+ if (!this.isRetryableError(error)) {
309
+ throw error
310
+ }
311
+ }
312
+ }
313
+
314
+ // Wait before next retry with exponential backoff
315
+ if (attempt < this.maxRetries - 1) {
316
+ await this.sleep(delay)
317
+ delay *= this.retryMultiplier
318
+ }
319
+ }
320
+
321
+ throw lastError ?? new NEARRpcClientError(
322
+ 'RPC call failed after all retries',
323
+ NEARErrorCode.RPC_ERROR
324
+ )
325
+ }
326
+
327
+ /**
328
+ * Make a single RPC request
329
+ */
330
+ private async makeRequest<T>(
331
+ url: string,
332
+ method: string,
333
+ params: unknown
334
+ ): Promise<T> {
335
+ const controller = new AbortController()
336
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout)
337
+
338
+ try {
339
+ const response = await fetch(url, {
340
+ method: 'POST',
341
+ headers: { 'Content-Type': 'application/json' },
342
+ body: JSON.stringify({
343
+ jsonrpc: '2.0',
344
+ id: `sip-${Date.now()}-${Math.random().toString(36).slice(2)}`,
345
+ method,
346
+ params,
347
+ }),
348
+ signal: controller.signal,
349
+ })
350
+
351
+ if (!response.ok) {
352
+ throw new NEARRpcClientError(
353
+ `HTTP error: ${response.status} ${response.statusText}`,
354
+ NEARErrorCode.NETWORK_ERROR
355
+ )
356
+ }
357
+
358
+ const json = await response.json() as {
359
+ result?: T
360
+ error?: { code: number; message: string; data?: unknown }
361
+ }
362
+
363
+ if (json.error) {
364
+ throw this.parseRpcError(json.error)
365
+ }
366
+
367
+ return json.result as T
368
+ } catch (error) {
369
+ if ((error as Error).name === 'AbortError') {
370
+ throw new NEARRpcClientError('Request timeout', NEARErrorCode.TIMEOUT)
371
+ }
372
+ if (error instanceof NEARRpcClientError) {
373
+ throw error
374
+ }
375
+ throw new NEARRpcClientError(
376
+ `Network error: ${(error as Error).message}`,
377
+ NEARErrorCode.NETWORK_ERROR,
378
+ error
379
+ )
380
+ } finally {
381
+ clearTimeout(timeoutId)
382
+ }
383
+ }
384
+
385
+ // ─── Account Methods ────────────────────────────────────────────────────────
386
+
387
+ /**
388
+ * Get account information
389
+ */
390
+ async getAccount(accountId: string): Promise<NEARAccountInfo> {
391
+ if (!isValidAccountId(accountId)) {
392
+ throw new ValidationError('Invalid account ID', 'accountId')
393
+ }
394
+
395
+ interface AccountView {
396
+ amount: string
397
+ locked: string
398
+ code_hash: string
399
+ storage_usage: number
400
+ storage_paid_at: number
401
+ block_height: number
402
+ block_hash: string
403
+ }
404
+
405
+ const result = await this.call<AccountView>('query', {
406
+ request_type: 'view_account',
407
+ finality: 'final',
408
+ account_id: accountId,
409
+ })
410
+
411
+ return {
412
+ amount: BigInt(result.amount),
413
+ locked: BigInt(result.locked),
414
+ codeHash: result.code_hash,
415
+ storageUsage: result.storage_usage,
416
+ storagePaidAt: result.storage_paid_at,
417
+ blockHeight: result.block_height,
418
+ blockHash: result.block_hash,
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Get account balance
424
+ */
425
+ async getBalance(accountId: string): Promise<bigint> {
426
+ const account = await this.getAccount(accountId)
427
+ return account.amount
428
+ }
429
+
430
+ /**
431
+ * Check if account exists
432
+ */
433
+ async accountExists(accountId: string): Promise<boolean> {
434
+ try {
435
+ await this.getAccount(accountId)
436
+ return true
437
+ } catch (error) {
438
+ if (
439
+ error instanceof NEARRpcClientError &&
440
+ error.code === NEARErrorCode.ACCOUNT_NOT_FOUND
441
+ ) {
442
+ return false
443
+ }
444
+ throw error
445
+ }
446
+ }
447
+
448
+ // ─── Access Key Methods ─────────────────────────────────────────────────────
449
+
450
+ /**
451
+ * Get access key information (includes nonce)
452
+ */
453
+ async getAccessKey(
454
+ accountId: string,
455
+ publicKey: string
456
+ ): Promise<NEARAccessKey> {
457
+ if (!isValidAccountId(accountId)) {
458
+ throw new ValidationError('Invalid account ID', 'accountId')
459
+ }
460
+
461
+ interface AccessKeyView {
462
+ nonce: number
463
+ permission: 'FullAccess' | {
464
+ FunctionCall: {
465
+ allowance: string | null
466
+ receiver_id: string
467
+ method_names: string[]
468
+ }
469
+ }
470
+ block_height: number
471
+ block_hash: string
472
+ }
473
+
474
+ const result = await this.call<AccessKeyView>('query', {
475
+ request_type: 'view_access_key',
476
+ finality: 'final',
477
+ account_id: accountId,
478
+ public_key: publicKey,
479
+ })
480
+
481
+ return {
482
+ nonce: BigInt(result.nonce),
483
+ permission: result.permission,
484
+ blockHeight: result.block_height,
485
+ blockHash: result.block_hash,
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Get current nonce for an access key
491
+ */
492
+ async getNonce(accountId: string, publicKey: string): Promise<bigint> {
493
+ const accessKey = await this.getAccessKey(accountId, publicKey)
494
+ return accessKey.nonce
495
+ }
496
+
497
+ /**
498
+ * Get next nonce (current + 1)
499
+ */
500
+ async getNextNonce(accountId: string, publicKey: string): Promise<bigint> {
501
+ const nonce = await this.getNonce(accountId, publicKey)
502
+ return nonce + 1n
503
+ }
504
+
505
+ // ─── Block Methods ──────────────────────────────────────────────────────────
506
+
507
+ /**
508
+ * Get block information
509
+ */
510
+ async getBlock(
511
+ blockReference: 'final' | 'optimistic' | { blockId: string | number }
512
+ ): Promise<NEARBlockInfo> {
513
+ interface BlockView {
514
+ header: {
515
+ height: number
516
+ hash: string
517
+ timestamp: number
518
+ prev_hash: string
519
+ gas_price: string
520
+ }
521
+ }
522
+
523
+ const params = typeof blockReference === 'string'
524
+ ? { finality: blockReference }
525
+ : typeof blockReference.blockId === 'number'
526
+ ? { block_id: blockReference.blockId }
527
+ : { block_id: blockReference.blockId }
528
+
529
+ const result = await this.call<BlockView>('block', params)
530
+
531
+ return {
532
+ height: result.header.height,
533
+ hash: result.header.hash,
534
+ timestamp: Math.floor(result.header.timestamp / 1_000_000), // Convert ns to ms
535
+ prevHash: result.header.prev_hash,
536
+ gasPrice: BigInt(result.header.gas_price),
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Get current block height
542
+ */
543
+ async getBlockHeight(): Promise<number> {
544
+ const block = await this.getBlock('final')
545
+ return block.height
546
+ }
547
+
548
+ /**
549
+ * Get latest block hash
550
+ */
551
+ async getLatestBlockHash(): Promise<string> {
552
+ const block = await this.getBlock('final')
553
+ return block.hash
554
+ }
555
+
556
+ // ─── Transaction Methods ────────────────────────────────────────────────────
557
+
558
+ /**
559
+ * Broadcast a signed transaction (async - returns immediately)
560
+ */
561
+ async broadcastTxAsync(signedTx: string): Promise<string> {
562
+ const result = await this.call<string>('broadcast_tx_async', [signedTx])
563
+ return result
564
+ }
565
+
566
+ /**
567
+ * Broadcast a signed transaction and wait for inclusion
568
+ */
569
+ async broadcastTxCommit(signedTx: string): Promise<NEARTransactionOutcome> {
570
+ interface TxResult {
571
+ status: {
572
+ SuccessValue?: string
573
+ SuccessReceiptId?: string
574
+ Failure?: { ActionError?: { kind: unknown } }
575
+ }
576
+ transaction: {
577
+ hash: string
578
+ signer_id: string
579
+ receiver_id: string
580
+ }
581
+ transaction_outcome: {
582
+ block_hash: string
583
+ outcome: {
584
+ gas_burnt: number
585
+ tokens_burnt: string
586
+ logs: string[]
587
+ }
588
+ }
589
+ receipts_outcome: Array<{
590
+ id: string
591
+ outcome: {
592
+ executor_id: string
593
+ gas_burnt: number
594
+ tokens_burnt: string
595
+ logs: string[]
596
+ status: {
597
+ SuccessValue?: string
598
+ SuccessReceiptId?: string
599
+ Failure?: unknown
600
+ }
601
+ }
602
+ }>
603
+ }
604
+
605
+ const result = await this.call<TxResult>('broadcast_tx_commit', [signedTx])
606
+
607
+ return this.parseTransactionResult(result)
608
+ }
609
+
610
+ /**
611
+ * Broadcast and wait for final confirmation
612
+ */
613
+ async broadcastTxAwait(
614
+ signedTx: string,
615
+ options?: NEARPollOptions
616
+ ): Promise<NEARTransactionOutcome> {
617
+ // First broadcast async to get the hash
618
+ const txHash = await this.broadcastTxAsync(signedTx)
619
+
620
+ // Then poll for finality
621
+ return this.waitForTransaction(txHash, options)
622
+ }
623
+
624
+ /**
625
+ * Get transaction status
626
+ */
627
+ async getTransactionStatus(
628
+ txHash: string,
629
+ senderId: string
630
+ ): Promise<NEARTxStatusResult> {
631
+ try {
632
+ interface TxStatusResult {
633
+ status: {
634
+ SuccessValue?: string
635
+ SuccessReceiptId?: string
636
+ Failure?: { ActionError?: { kind: unknown } }
637
+ }
638
+ transaction: {
639
+ hash: string
640
+ signer_id: string
641
+ receiver_id: string
642
+ }
643
+ transaction_outcome: {
644
+ block_hash: string
645
+ outcome: {
646
+ gas_burnt: number
647
+ tokens_burnt: string
648
+ logs: string[]
649
+ }
650
+ }
651
+ receipts_outcome: Array<{
652
+ id: string
653
+ outcome: {
654
+ executor_id: string
655
+ gas_burnt: number
656
+ tokens_burnt: string
657
+ logs: string[]
658
+ status: {
659
+ SuccessValue?: string
660
+ SuccessReceiptId?: string
661
+ Failure?: unknown
662
+ }
663
+ }
664
+ }>
665
+ }
666
+
667
+ // First try with EXPERIMENTAL_tx_status for finality info
668
+ const result = await this.call<TxStatusResult>(
669
+ 'EXPERIMENTAL_tx_status',
670
+ [txHash, senderId]
671
+ )
672
+
673
+ const outcome = this.parseTransactionResult(result)
674
+
675
+ return {
676
+ status: outcome.status,
677
+ finality: outcome.finalityStatus,
678
+ outcome,
679
+ }
680
+ } catch (error) {
681
+ // Transaction not found yet
682
+ if (
683
+ error instanceof NEARRpcClientError &&
684
+ (error.message.includes('not found') ||
685
+ error.message.includes('UNKNOWN_TRANSACTION'))
686
+ ) {
687
+ return {
688
+ status: 'pending',
689
+ finality: 'optimistic',
690
+ }
691
+ }
692
+ throw error
693
+ }
694
+ }
695
+
696
+ /**
697
+ * Wait for transaction with polling
698
+ */
699
+ async waitForTransaction(
700
+ txHash: string,
701
+ options?: NEARPollOptions,
702
+ senderId?: string
703
+ ): Promise<NEARTransactionOutcome> {
704
+ const maxWaitMs = options?.maxWaitMs ?? 60000
705
+ const initialIntervalMs = options?.initialIntervalMs ?? 1000
706
+ const maxIntervalMs = options?.maxIntervalMs ?? 5000
707
+ const targetFinality = options?.targetFinality ?? 'final'
708
+
709
+ const startTime = Date.now()
710
+ let intervalMs = initialIntervalMs
711
+
712
+ // We need senderId to query status - if not provided, try to extract from hash
713
+ // In practice, caller should provide senderId
714
+ const sender = senderId ?? ''
715
+
716
+ while (Date.now() - startTime < maxWaitMs) {
717
+ try {
718
+ const result = await this.getTransactionStatus(txHash, sender)
719
+
720
+ if (result.status === 'failed') {
721
+ throw new NEARRpcClientError(
722
+ `Transaction failed: ${result.error ?? 'Unknown error'}`,
723
+ NEARErrorCode.ACTION_ERROR,
724
+ undefined,
725
+ result.outcome
726
+ )
727
+ }
728
+
729
+ // Check if we've reached target finality
730
+ if (this.isFinalityReached(result.finality, targetFinality)) {
731
+ if (result.outcome) {
732
+ return result.outcome
733
+ }
734
+ }
735
+ } catch (error) {
736
+ // Ignore "not found" errors - tx may not be indexed yet
737
+ if (
738
+ !(error instanceof NEARRpcClientError) ||
739
+ !error.message.includes('not found')
740
+ ) {
741
+ throw error
742
+ }
743
+ }
744
+
745
+ // Wait before next poll with exponential backoff (capped)
746
+ await this.sleep(intervalMs)
747
+ intervalMs = Math.min(intervalMs * 1.5, maxIntervalMs)
748
+ }
749
+
750
+ throw new NEARRpcClientError(
751
+ `Transaction did not reach ${targetFinality} finality within ${maxWaitMs}ms`,
752
+ NEARErrorCode.TIMEOUT
753
+ )
754
+ }
755
+
756
+ // ─── Contract View Methods ──────────────────────────────────────────────────
757
+
758
+ /**
759
+ * Call a view function on a contract
760
+ */
761
+ async viewFunction<T>(
762
+ contractId: string,
763
+ methodName: string,
764
+ args: Record<string, unknown> = {}
765
+ ): Promise<T> {
766
+ if (!isValidAccountId(contractId)) {
767
+ throw new ValidationError('Invalid contract ID', 'contractId')
768
+ }
769
+
770
+ interface ViewResult {
771
+ result: number[]
772
+ logs: string[]
773
+ block_height: number
774
+ block_hash: string
775
+ }
776
+
777
+ const result = await this.call<ViewResult>('query', {
778
+ request_type: 'call_function',
779
+ finality: 'final',
780
+ account_id: contractId,
781
+ method_name: methodName,
782
+ args_base64: Buffer.from(JSON.stringify(args)).toString('base64'),
783
+ })
784
+
785
+ // Decode result from bytes
786
+ const decoded = Buffer.from(result.result).toString('utf-8')
787
+ return JSON.parse(decoded) as T
788
+ }
789
+
790
+ /**
791
+ * Get token balance for a NEP-141 token
792
+ */
793
+ async getTokenBalance(
794
+ tokenContract: string,
795
+ accountId: string
796
+ ): Promise<bigint> {
797
+ try {
798
+ const balance = await this.viewFunction<string>(
799
+ tokenContract,
800
+ 'ft_balance_of',
801
+ { account_id: accountId }
802
+ )
803
+ return BigInt(balance)
804
+ } catch {
805
+ // Account may not be registered
806
+ return 0n
807
+ }
808
+ }
809
+
810
+ /**
811
+ * Check if account has storage deposit for a token
812
+ */
813
+ async hasStorageDeposit(
814
+ tokenContract: string,
815
+ accountId: string
816
+ ): Promise<boolean> {
817
+ try {
818
+ const result = await this.viewFunction<{
819
+ total: string
820
+ available: string
821
+ } | null>(tokenContract, 'storage_balance_of', { account_id: accountId })
822
+
823
+ return result !== null && BigInt(result.total) > 0n
824
+ } catch {
825
+ return false
826
+ }
827
+ }
828
+
829
+ // ─── Utility Methods ────────────────────────────────────────────────────────
830
+
831
+ /**
832
+ * Get the current network
833
+ */
834
+ getNetwork(): NEARNetwork {
835
+ return this.network
836
+ }
837
+
838
+ /**
839
+ * Get the primary RPC URL
840
+ */
841
+ getRpcUrl(): string {
842
+ return this.urls[0]
843
+ }
844
+
845
+ /**
846
+ * Switch to next RPC endpoint (for manual failover)
847
+ */
848
+ switchEndpoint(): void {
849
+ this.currentUrlIndex = (this.currentUrlIndex + 1) % this.urls.length
850
+ }
851
+
852
+ // ─── Private Helpers ────────────────────────────────────────────────────────
853
+
854
+ private parseRpcError(error: {
855
+ code: number
856
+ message: string
857
+ data?: unknown
858
+ }): NEARRpcClientError {
859
+ const message = error.message
860
+
861
+ // Categorize error based on message content
862
+ if (message.includes('does not exist')) {
863
+ if (message.includes('account')) {
864
+ return new NEARRpcClientError(message, NEARErrorCode.ACCOUNT_NOT_FOUND, undefined, error.data)
865
+ }
866
+ if (message.includes('access key')) {
867
+ return new NEARRpcClientError(message, NEARErrorCode.ACCESS_KEY_NOT_FOUND, undefined, error.data)
868
+ }
869
+ }
870
+
871
+ if (message.includes('InvalidNonce')) {
872
+ return new NEARRpcClientError(message, NEARErrorCode.INVALID_NONCE, undefined, error.data)
873
+ }
874
+
875
+ if (message.includes('NotEnoughBalance') || message.includes('LackBalanceForState')) {
876
+ return new NEARRpcClientError(message, NEARErrorCode.INSUFFICIENT_BALANCE, undefined, error.data)
877
+ }
878
+
879
+ if (message.includes('InvalidTransaction')) {
880
+ return new NEARRpcClientError(message, NEARErrorCode.INVALID_TRANSACTION, undefined, error.data)
881
+ }
882
+
883
+ return new NEARRpcClientError(message, NEARErrorCode.RPC_ERROR, undefined, error.data)
884
+ }
885
+
886
+ private parseTransactionResult(result: {
887
+ status: {
888
+ SuccessValue?: string
889
+ SuccessReceiptId?: string
890
+ Failure?: { ActionError?: { kind: unknown } }
891
+ }
892
+ transaction: {
893
+ hash: string
894
+ signer_id: string
895
+ receiver_id: string
896
+ }
897
+ transaction_outcome: {
898
+ block_hash: string
899
+ outcome: {
900
+ gas_burnt: number
901
+ tokens_burnt: string
902
+ logs: string[]
903
+ }
904
+ }
905
+ receipts_outcome: Array<{
906
+ id: string
907
+ outcome: {
908
+ executor_id: string
909
+ gas_burnt: number
910
+ tokens_burnt: string
911
+ logs: string[]
912
+ status: {
913
+ SuccessValue?: string
914
+ SuccessReceiptId?: string
915
+ Failure?: unknown
916
+ }
917
+ }
918
+ }>
919
+ }): NEARTransactionOutcome {
920
+ const failed = !!result.status.Failure
921
+ const receipts: NEARReceiptOutcome[] = result.receipts_outcome.map((r) => ({
922
+ receiptId: r.id,
923
+ receiverId: r.outcome.executor_id,
924
+ status: r.outcome.status.Failure ? 'failure' : 'success',
925
+ gasUsed: BigInt(r.outcome.gas_burnt),
926
+ tokensBurnt: BigInt(r.outcome.tokens_burnt),
927
+ logs: r.outcome.logs,
928
+ error: r.outcome.status.Failure
929
+ ? JSON.stringify(r.outcome.status.Failure)
930
+ : undefined,
931
+ }))
932
+
933
+ return {
934
+ txHash: result.transaction.hash,
935
+ signerId: result.transaction.signer_id,
936
+ receiverId: result.transaction.receiver_id,
937
+ status: failed ? 'failed' : 'final',
938
+ finalityStatus: 'final', // broadcast_tx_commit waits for finality
939
+ blockHash: result.transaction_outcome.block_hash,
940
+ blockHeight: 0, // Not provided in this response
941
+ gasUsed: BigInt(result.transaction_outcome.outcome.gas_burnt),
942
+ tokensBurnt: BigInt(result.transaction_outcome.outcome.tokens_burnt),
943
+ logs: result.transaction_outcome.outcome.logs,
944
+ receipts,
945
+ error: failed
946
+ ? JSON.stringify(result.status.Failure)
947
+ : undefined,
948
+ }
949
+ }
950
+
951
+ private isRetryableError(error: unknown): boolean {
952
+ if (error instanceof NEARRpcClientError) {
953
+ // Retry network and timeout errors
954
+ return (
955
+ error.code === NEARErrorCode.NETWORK_ERROR ||
956
+ error.code === NEARErrorCode.TIMEOUT ||
957
+ error.code === NEARErrorCode.RPC_ERROR
958
+ )
959
+ }
960
+ return false
961
+ }
962
+
963
+ private isFinalityReached(
964
+ current: NEARFinality,
965
+ target: NEARFinality
966
+ ): boolean {
967
+ const levels: NEARFinality[] = ['optimistic', 'near-final', 'final']
968
+ return levels.indexOf(current) >= levels.indexOf(target)
969
+ }
970
+
971
+ private sleep(ms: number): Promise<void> {
972
+ return new Promise((resolve) => setTimeout(resolve, ms))
973
+ }
974
+ }
975
+
976
+ // ─── Factory Functions ────────────────────────────────────────────────────────
977
+
978
+ /**
979
+ * Create a NEAR RPC client
980
+ */
981
+ export function createNEARRpcClient(config: NEARRpcConfig): NEARRpcClient {
982
+ return new NEARRpcClient(config)
983
+ }
984
+
985
+ /**
986
+ * Create a mainnet RPC client with default configuration
987
+ */
988
+ export function createMainnetRpcClient(
989
+ options?: Partial<Omit<NEARRpcConfig, 'rpcUrl' | 'network'>>
990
+ ): NEARRpcClient {
991
+ return new NEARRpcClient({
992
+ rpcUrl: NEAR_RPC_ENDPOINTS.mainnet,
993
+ fallbackUrls: [
994
+ 'https://rpc.fastnear.com',
995
+ 'https://near-mainnet.api.pagoda.co/rpc/v1',
996
+ ],
997
+ network: 'mainnet',
998
+ ...options,
999
+ })
1000
+ }
1001
+
1002
+ /**
1003
+ * Create a testnet RPC client with default configuration
1004
+ */
1005
+ export function createTestnetRpcClient(
1006
+ options?: Partial<Omit<NEARRpcConfig, 'rpcUrl' | 'network'>>
1007
+ ): NEARRpcClient {
1008
+ return new NEARRpcClient({
1009
+ rpcUrl: NEAR_RPC_ENDPOINTS.testnet,
1010
+ fallbackUrls: [
1011
+ 'https://rpc.testnet.fastnear.com',
1012
+ ],
1013
+ network: 'testnet',
1014
+ ...options,
1015
+ })
1016
+ }