@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.
- package/README.md +267 -0
- package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
- package/dist/browser.d.mts +10 -4
- package/dist/browser.d.ts +10 -4
- package/dist/browser.js +47556 -19603
- package/dist/browser.mjs +628 -48
- package/dist/chunk-4GRJ5MAW.mjs +152 -0
- package/dist/chunk-5D7A3L3W.mjs +717 -0
- package/dist/chunk-64AYA5F5.mjs +7834 -0
- package/dist/chunk-GMDGB22A.mjs +379 -0
- package/dist/chunk-I534WKN7.mjs +328 -0
- package/dist/chunk-IBZVA5Y7.mjs +1003 -0
- package/dist/chunk-PRRZAWJE.mjs +223 -0
- package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
- package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
- package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
- package/dist/constants-LHAAUC2T.mjs +51 -0
- package/dist/dist-2OGQ7FED.mjs +3957 -0
- package/dist/dist-IFHPYLDX.mjs +254 -0
- package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
- package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
- package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
- package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48396 -19623
- package/dist/index.mjs +537 -19
- package/dist/interface-Bf7w1PLW.d.mts +679 -0
- package/dist/interface-Bf7w1PLW.d.ts +679 -0
- package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
- package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
- package/dist/proofs/halo2.d.mts +151 -0
- package/dist/proofs/halo2.d.ts +151 -0
- package/dist/proofs/halo2.js +350 -0
- package/dist/proofs/halo2.mjs +11 -0
- package/dist/proofs/kimchi.d.mts +160 -0
- package/dist/proofs/kimchi.d.ts +160 -0
- package/dist/proofs/kimchi.js +431 -0
- package/dist/proofs/kimchi.mjs +13 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +74 -18
- package/dist/proofs/noir.mjs +84 -24
- package/dist/solana-U3MEGU7W.mjs +280 -0
- package/dist/validity_proof-3POXLPNY.mjs +21 -0
- package/package.json +44 -11
- package/src/adapters/index.ts +41 -0
- package/src/adapters/jupiter.ts +571 -0
- package/src/adapters/near-intents.ts +135 -0
- package/src/advisor/advisor.ts +653 -0
- package/src/advisor/index.ts +54 -0
- package/src/advisor/tools.ts +303 -0
- package/src/advisor/types.ts +164 -0
- package/src/chains/ethereum/announcement.ts +536 -0
- package/src/chains/ethereum/bnb-optimizations.ts +474 -0
- package/src/chains/ethereum/commitment.ts +522 -0
- package/src/chains/ethereum/constants.ts +462 -0
- package/src/chains/ethereum/deployment.ts +596 -0
- package/src/chains/ethereum/gas-estimation.ts +538 -0
- package/src/chains/ethereum/index.ts +268 -0
- package/src/chains/ethereum/optimizations.ts +614 -0
- package/src/chains/ethereum/privacy-adapter.ts +855 -0
- package/src/chains/ethereum/registry.ts +584 -0
- package/src/chains/ethereum/rpc.ts +905 -0
- package/src/chains/ethereum/stealth.ts +491 -0
- package/src/chains/ethereum/token.ts +790 -0
- package/src/chains/ethereum/transfer.ts +637 -0
- package/src/chains/ethereum/types.ts +456 -0
- package/src/chains/ethereum/viewing-key.ts +455 -0
- package/src/chains/near/commitment.ts +608 -0
- package/src/chains/near/constants.ts +284 -0
- package/src/chains/near/function-call.ts +871 -0
- package/src/chains/near/history.ts +654 -0
- package/src/chains/near/implicit-account.ts +840 -0
- package/src/chains/near/index.ts +393 -0
- package/src/chains/near/native-transfer.ts +658 -0
- package/src/chains/near/nep141.ts +775 -0
- package/src/chains/near/privacy-adapter.ts +889 -0
- package/src/chains/near/resolver.ts +971 -0
- package/src/chains/near/rpc.ts +1016 -0
- package/src/chains/near/stealth.ts +419 -0
- package/src/chains/near/types.ts +317 -0
- package/src/chains/near/viewing-key.ts +876 -0
- package/src/chains/solana/anchor-transfer.ts +386 -0
- package/src/chains/solana/commitment.ts +577 -0
- package/src/chains/solana/constants.ts +126 -12
- package/src/chains/solana/ephemeral-keys.ts +543 -0
- package/src/chains/solana/index.ts +252 -1
- package/src/chains/solana/key-derivation.ts +418 -0
- package/src/chains/solana/kit-compat.ts +334 -0
- package/src/chains/solana/optimizations.ts +560 -0
- package/src/chains/solana/privacy-adapter.ts +605 -0
- package/src/chains/solana/providers/generic.ts +47 -6
- package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
- package/src/chains/solana/providers/helius-enhanced.ts +623 -0
- package/src/chains/solana/providers/helius.ts +186 -33
- package/src/chains/solana/providers/index.ts +31 -0
- package/src/chains/solana/providers/interface.ts +61 -18
- package/src/chains/solana/providers/quicknode.ts +409 -0
- package/src/chains/solana/providers/triton.ts +426 -0
- package/src/chains/solana/providers/webhook.ts +338 -67
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +83 -66
- package/src/chains/solana/sol-transfer.ts +732 -0
- package/src/chains/solana/spl-transfer.ts +886 -0
- package/src/chains/solana/stealth-scanner.ts +703 -0
- package/src/chains/solana/sunspot-verifier.ts +453 -0
- package/src/chains/solana/transaction-builder.ts +755 -0
- package/src/chains/solana/transfer.ts +74 -5
- package/src/chains/solana/types.ts +57 -6
- package/src/chains/solana/utils.ts +110 -0
- package/src/chains/solana/viewing-key.ts +807 -0
- package/src/compliance/fireblocks.ts +921 -0
- package/src/compliance/index.ts +23 -0
- package/src/compliance/range-sas.ts +398 -33
- package/src/config/endpoints.ts +100 -0
- package/src/crypto.ts +11 -8
- package/src/errors.ts +82 -0
- package/src/evm/erc4337-relayer.ts +830 -0
- package/src/evm/index.ts +47 -0
- package/src/fees/calculator.ts +396 -0
- package/src/fees/index.ts +87 -0
- package/src/fees/near-contract.ts +429 -0
- package/src/fees/types.ts +268 -0
- package/src/index.ts +686 -1
- package/src/intent.ts +6 -3
- package/src/logger.ts +324 -0
- package/src/network/index.ts +80 -0
- package/src/network/proxy.ts +691 -0
- package/src/optimizations/index.ts +541 -0
- package/src/oracle/types.ts +1 -0
- package/src/privacy-backends/arcium-types.ts +727 -0
- package/src/privacy-backends/arcium.ts +719 -0
- package/src/privacy-backends/combined-privacy.ts +866 -0
- package/src/privacy-backends/cspl-token.ts +595 -0
- package/src/privacy-backends/cspl-types.ts +512 -0
- package/src/privacy-backends/cspl.ts +907 -0
- package/src/privacy-backends/health.ts +488 -0
- package/src/privacy-backends/inco-types.ts +323 -0
- package/src/privacy-backends/inco.ts +616 -0
- package/src/privacy-backends/index.ts +254 -4
- package/src/privacy-backends/interface.ts +649 -6
- package/src/privacy-backends/lru-cache.ts +343 -0
- package/src/privacy-backends/magicblock.ts +458 -0
- package/src/privacy-backends/mock.ts +258 -0
- package/src/privacy-backends/privacycash.ts +13 -17
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +414 -2
- package/src/privacy-backends/router.ts +283 -3
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +3 -0
- package/src/privacy-logger.ts +191 -0
- package/src/production-safety.ts +373 -0
- package/src/proofs/aggregator.ts +1029 -0
- package/src/proofs/browser-composer.ts +1150 -0
- package/src/proofs/browser.ts +113 -25
- package/src/proofs/cache/index.ts +127 -0
- package/src/proofs/cache/interface.ts +545 -0
- package/src/proofs/cache/key-generator.ts +188 -0
- package/src/proofs/cache/lru-cache.ts +481 -0
- package/src/proofs/cache/multi-tier-cache.ts +575 -0
- package/src/proofs/cache/persistent-cache.ts +788 -0
- package/src/proofs/compliance-proof.ts +872 -0
- package/src/proofs/composer/base.ts +923 -0
- package/src/proofs/composer/index.ts +25 -0
- package/src/proofs/composer/interface.ts +518 -0
- package/src/proofs/composer/types.ts +383 -0
- package/src/proofs/converters/halo2.ts +452 -0
- package/src/proofs/converters/index.ts +208 -0
- package/src/proofs/converters/interface.ts +363 -0
- package/src/proofs/converters/kimchi.ts +462 -0
- package/src/proofs/converters/noir.ts +451 -0
- package/src/proofs/fallback.ts +888 -0
- package/src/proofs/halo2.ts +42 -0
- package/src/proofs/index.ts +471 -0
- package/src/proofs/interface.ts +13 -0
- package/src/proofs/kimchi.ts +42 -0
- package/src/proofs/lazy.ts +1004 -0
- package/src/proofs/mock.ts +25 -1
- package/src/proofs/noir.ts +110 -29
- package/src/proofs/orchestrator.ts +960 -0
- package/src/proofs/parallel/concurrency.ts +297 -0
- package/src/proofs/parallel/dependency-graph.ts +602 -0
- package/src/proofs/parallel/executor.ts +420 -0
- package/src/proofs/parallel/index.ts +131 -0
- package/src/proofs/parallel/interface.ts +685 -0
- package/src/proofs/parallel/worker-pool.ts +644 -0
- package/src/proofs/providers/halo2.ts +560 -0
- package/src/proofs/providers/index.ts +34 -0
- package/src/proofs/providers/kimchi.ts +641 -0
- package/src/proofs/validator.ts +881 -0
- package/src/proofs/verifier.ts +867 -0
- package/src/quantum/index.ts +112 -0
- package/src/quantum/winternitz-vault.ts +639 -0
- package/src/quantum/wots.ts +611 -0
- package/src/settlement/backends/direct-chain.ts +1 -0
- package/src/settlement/index.ts +9 -0
- package/src/settlement/router.ts +732 -46
- package/src/solana/index.ts +72 -0
- package/src/solana/jito-relayer.ts +687 -0
- package/src/solana/noir-verifier-types.ts +430 -0
- package/src/solana/noir-verifier.ts +816 -0
- package/src/stealth/address-derivation.ts +193 -0
- package/src/stealth/ed25519.ts +431 -0
- package/src/stealth/index.ts +233 -0
- package/src/stealth/meta-address.ts +221 -0
- package/src/stealth/secp256k1.ts +368 -0
- package/src/stealth/utils.ts +194 -0
- package/src/stealth.ts +50 -1504
- package/src/sync/index.ts +106 -0
- package/src/sync/manager.ts +504 -0
- package/src/sync/mock-provider.ts +318 -0
- package/src/sync/oblivious.ts +625 -0
- package/src/tokens/index.ts +15 -0
- package/src/tokens/registry.ts +301 -0
- package/src/utils/deprecation.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/wallet/ethereum/index.ts +68 -0
- package/src/wallet/ethereum/metamask-privacy.ts +420 -0
- package/src/wallet/ethereum/multi-wallet.ts +646 -0
- package/src/wallet/ethereum/privacy-adapter.ts +700 -0
- package/src/wallet/ethereum/types.ts +3 -1
- package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
- package/src/wallet/hardware/index.ts +10 -0
- package/src/wallet/hardware/ledger-privacy.ts +414 -0
- package/src/wallet/index.ts +71 -0
- package/src/wallet/near/adapter.ts +626 -0
- package/src/wallet/near/index.ts +86 -0
- package/src/wallet/near/meteor-wallet.ts +1153 -0
- package/src/wallet/near/my-near-wallet.ts +790 -0
- package/src/wallet/near/wallet-selector.ts +702 -0
- package/src/wallet/solana/adapter.ts +6 -4
- package/src/wallet/solana/index.ts +13 -0
- package/src/wallet/solana/privacy-adapter.ts +567 -0
- package/src/wallet/sui/types.ts +6 -4
- package/src/zcash/rpc-client.ts +13 -6
- package/dist/chunk-2XIVXWHA.mjs +0 -1930
- package/dist/chunk-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-7RFRWDCW.mjs +0 -1504
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-F6F73W35.mjs +0 -16166
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-OFDBEIEK.mjs +0 -16166
- package/dist/chunk-SF7YSLF5.mjs +0 -1515
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/chunk-WWUSGOXE.mjs +0 -17129
- package/dist/constants-VOI7BSLK.mjs +0 -27
- package/dist/index-B71aXVzk.d.ts +0 -13264
- package/dist/index-BYZbDjal.d.ts +0 -11390
- package/dist/index-CHB3KuOB.d.mts +0 -11859
- package/dist/index-CzWPI6Le.d.ts +0 -11859
- package/dist/index-pOIIuwfV.d.mts +0 -13264
- package/dist/index-xbWjohNq.d.mts +0 -11390
- package/dist/solana-4O4K45VU.mjs +0 -46
- package/dist/solana-5EMCTPTS.mjs +0 -46
- package/dist/solana-NDABAZ6P.mjs +0 -56
- package/dist/solana-Q4NAVBTS.mjs +0 -46
- 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
|
+
}
|