@upshiftfinance/sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -23
- package/lib/adapters/sui/client.d.ts +83 -0
- package/lib/adapters/sui/client.js +216 -0
- package/lib/adapters/sui/constants.d.ts +32 -0
- package/lib/adapters/sui/constants.js +23 -1
- package/lib/adapters/sui/index.d.ts +4 -0
- package/lib/adapters/sui/index.js +4 -0
- package/lib/adapters/sui/targets.d.ts +171 -0
- package/lib/adapters/sui/targets.js +225 -0
- package/lib/core/analytics/version.d.ts +1 -1
- package/lib/core/analytics/version.js +1 -1
- package/lib/sdk.d.ts +21 -2
- package/lib/types/web3.d.ts +21 -2
- package/package.json +6 -5
- package/lib/adapters/sui/getters.d.ts +0 -24
- package/lib/adapters/sui/getters.js +0 -134
- package/lib/adapters/sui/transformer.d.ts +0 -10
- package/lib/adapters/sui/transformer.js +0 -115
- package/lib/adapters/sui/types.d.ts +0 -70
- package/lib/adapters/sui/types.js +0 -3
package/README.md
CHANGED
|
@@ -25,9 +25,9 @@ The SDK automatically converts viem `WalletClient` to an ethers-compatible signe
|
|
|
25
25
|
## Quick Start
|
|
26
26
|
|
|
27
27
|
```typescript
|
|
28
|
-
import
|
|
28
|
+
import UpshiftSDK from '@upshiftfinance/sdk';
|
|
29
29
|
|
|
30
|
-
const sdk = new
|
|
30
|
+
const sdk = new UpshiftSDK({
|
|
31
31
|
// Required: stable kebab-case slug identifying your application.
|
|
32
32
|
appName: 'acme-trader',
|
|
33
33
|
providers: {
|
|
@@ -36,7 +36,7 @@ const sdk = new AugustSDK({
|
|
|
36
36
|
-1: 'https://api.mainnet-beta.solana.com', // Solana
|
|
37
37
|
},
|
|
38
38
|
keys: {
|
|
39
|
-
|
|
39
|
+
upshift: 'YOUR_API_KEY', // Optional: vault reads, allocations and vault-linked sub-account reads work without it
|
|
40
40
|
},
|
|
41
41
|
monitoring: {
|
|
42
42
|
env: 'DEV', // Optional: 'DEV' enables console logging (defaults to 'PROD')
|
|
@@ -61,10 +61,10 @@ const positions = await sdk.getVaultPositions({
|
|
|
61
61
|
|
|
62
62
|
### App Name
|
|
63
63
|
|
|
64
|
-
`appName` is required on every `
|
|
64
|
+
`appName` is required on every `UpshiftSDK` constructor call. Pass a stable kebab-case slug
|
|
65
65
|
identifying your application (e.g. `'acme-trader'`, `'my-defi-app'`):
|
|
66
66
|
|
|
67
|
-
- **What it's used for.**
|
|
67
|
+
- **What it's used for.** Upshift tags analytics events with `app.name = <yourSlug>` to
|
|
68
68
|
attribute error spikes and prioritize bug fixes by consuming app.
|
|
69
69
|
- **What it is not.** Not a secret, not a license key, not a display label. Pick a slug once
|
|
70
70
|
and reuse it across deployments.
|
|
@@ -73,13 +73,13 @@ identifying your application (e.g. `'acme-trader'`, `'my-defi-app'`):
|
|
|
73
73
|
|
|
74
74
|
```typescript
|
|
75
75
|
// Will throw — appName is required.
|
|
76
|
-
new
|
|
76
|
+
new UpshiftSDK({ providers: { /* ... */ } } as any);
|
|
77
77
|
|
|
78
78
|
// Will throw — spaces not allowed. Use 'acme-trader'.
|
|
79
|
-
new
|
|
79
|
+
new UpshiftSDK({ appName: 'Acme Trader', providers: { /* ... */ } });
|
|
80
80
|
|
|
81
81
|
// Correct.
|
|
82
|
-
new
|
|
82
|
+
new UpshiftSDK({ appName: 'acme-trader', providers: { /* ... */ } });
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
### Origin-Code Attribution (SwapRouter)
|
|
@@ -88,7 +88,7 @@ An integrator with an origin (referral / fee-sharing) code registered on the Swa
|
|
|
88
88
|
SDK instance. Deposits made through the SDK then carry it on-chain:
|
|
89
89
|
|
|
90
90
|
```typescript
|
|
91
|
-
const sdk = new
|
|
91
|
+
const sdk = new UpshiftSDK({
|
|
92
92
|
appName: 'acme-trader',
|
|
93
93
|
providers: { 1: 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY' },
|
|
94
94
|
attribution: {
|
|
@@ -120,10 +120,10 @@ const { registered, originFeeBps, collector } = await sdk.getOriginInfo({
|
|
|
120
120
|
`vaultDeposit` takes the vault's native path and logs
|
|
121
121
|
`vaultDeposit:attribution-skipped` with the reason (Sentry tag `sdk.attribution.origin=skipped`). The router
|
|
122
122
|
methods send the zero sentinel for an unregistered instance code and log `<method>:attribution-skipped`.
|
|
123
|
-
With `strictOrigin: true` an unregistered instance code throws `
|
|
123
|
+
With `strictOrigin: true` an unregistered instance code throws `UpshiftValidationError` before any approval
|
|
124
124
|
instead; a paused router falls back to native even then. An `originCode` passed to a router method that is
|
|
125
|
-
not registered always throws `
|
|
126
|
-
router methods throw `
|
|
125
|
+
not registered always throws `UpshiftValidationError` before any approval. While the router is paused, the
|
|
126
|
+
router methods throw `UpshiftSDKError` (`context.reason: 'router-paused'`) before any approval.
|
|
127
127
|
- **Approve `route.spender`, not the vault.** A routed deposit is pulled by the SwapRouter. `vaultDeposit` and
|
|
128
128
|
`approve` handle this; if you send approvals yourself, read the spender first:
|
|
129
129
|
|
|
@@ -138,14 +138,14 @@ const { registered, originFeeBps, collector } = await sdk.getOriginInfo({
|
|
|
138
138
|
- **Zero-fee codes.** A code registered with a 0 bps fee is valid for attribution only, but the router emits no
|
|
139
139
|
`OriginFeeApplied` event for it. Its deposits are attributable only from the transaction calldata and SDK
|
|
140
140
|
telemetry.
|
|
141
|
-
- **Process-global.** `attribution` is process-global: the last `
|
|
141
|
+
- **Process-global.** `attribution` is process-global: the last `UpshiftSDK` constructed wins, and one
|
|
142
142
|
constructed without `attribution` clears it. Run one attribution configuration per process.
|
|
143
143
|
|
|
144
144
|
## Architecture
|
|
145
145
|
|
|
146
146
|
```
|
|
147
147
|
src.ts/
|
|
148
|
-
├── main.ts # Main SDK class (
|
|
148
|
+
├── main.ts # Main SDK class (UpshiftSDK)
|
|
149
149
|
├── core/ # Base utilities
|
|
150
150
|
│ ├── base.class.ts # Base SDK functionality
|
|
151
151
|
│ ├── fetcher.ts # API client with retry logic
|
|
@@ -159,7 +159,7 @@ src.ts/
|
|
|
159
159
|
├── modules/ # Feature modules
|
|
160
160
|
│ ├── vaults/ # Vault read operations
|
|
161
161
|
│ ├── sub-accounts/ # Sub-account queries
|
|
162
|
-
│ └── api/ #
|
|
162
|
+
│ └── api/ # Upshift backend API integration
|
|
163
163
|
├── services/ # External service integrations
|
|
164
164
|
│ ├── debank/ # DeFi allocation data
|
|
165
165
|
│ ├── coingecko/ # Token pricing
|
|
@@ -339,7 +339,7 @@ if (walletClient) sdk.evm.setSigner(walletClient);
|
|
|
339
339
|
| --- | --- |
|
|
340
340
|
| `isSuiAddress(address)` | Whether a string is a Sui address / object ID (`0x` + 64 hex) |
|
|
341
341
|
| `isSuiVault(chainId)` | Whether a chain ID is Sui's (`-2`) |
|
|
342
|
-
| `network`, `rpcUrl` | The `sui` config passed to `new
|
|
342
|
+
| `network`, `rpcUrl` | The `sui` config passed to `new UpshiftSDK({ sui })` |
|
|
343
343
|
|
|
344
344
|
## Code Conventions
|
|
345
345
|
|
|
@@ -359,17 +359,17 @@ Search the codebase for these to find important areas:
|
|
|
359
359
|
|
|
360
360
|
### Error Handling
|
|
361
361
|
|
|
362
|
-
Every public method throws typed errors that subclass `
|
|
362
|
+
Every public method throws typed errors that subclass `UpshiftSDKError`:
|
|
363
363
|
|
|
364
364
|
```typescript
|
|
365
|
-
import {
|
|
365
|
+
import { UpshiftValidationError, UpshiftTimeoutError, UpshiftSDKError } from '@upshiftfinance/sdk';
|
|
366
366
|
|
|
367
367
|
try {
|
|
368
368
|
await sdk.evm.vaultDeposit({ target: '0x...', wallet: owner, amount: '1' });
|
|
369
369
|
} catch (err) {
|
|
370
|
-
if (err instanceof
|
|
371
|
-
else if (err instanceof
|
|
372
|
-
else if (err instanceof
|
|
370
|
+
if (err instanceof UpshiftValidationError) showFormError(err.message);
|
|
371
|
+
else if (err instanceof UpshiftTimeoutError) scheduleRetry(err.timeoutMs);
|
|
372
|
+
else if (err instanceof UpshiftSDKError) reportError(err.code, err.cause);
|
|
373
373
|
else throw err;
|
|
374
374
|
}
|
|
375
375
|
```
|
|
@@ -382,10 +382,10 @@ try {
|
|
|
382
382
|
|
|
383
383
|
```typescript
|
|
384
384
|
// Development mode — enables console logging
|
|
385
|
-
const sdk = new
|
|
385
|
+
const sdk = new UpshiftSDK({ appName: 'my-app', providers: { /* ... */ }, monitoring: { env: 'DEV' } });
|
|
386
386
|
|
|
387
387
|
// Production mode — default, no console logs
|
|
388
|
-
const sdk = new
|
|
388
|
+
const sdk = new UpshiftSDK({ appName: 'my-app', providers: { /* ... */ } });
|
|
389
389
|
```
|
|
390
390
|
|
|
391
391
|
## Development
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sui gRPC client factory with endpoint failover.
|
|
3
|
+
*
|
|
4
|
+
* Sui removed JSON-RPC (Sui v1.82), so the SDK talks to fullnodes over gRPC.
|
|
5
|
+
* It uses gRPC-web — `@mysten/sui`'s `SuiGrpcClient` — so one code path works
|
|
6
|
+
* in browsers and Node.js.
|
|
7
|
+
*
|
|
8
|
+
* `@mysten/sui` is ESM-only and this SDK is published as CommonJS. The module
|
|
9
|
+
* is loaded on first use, not when the SDK is imported, so importing the SDK
|
|
10
|
+
* never requires it. That keeps Jest (which cannot `require` an ES module) and
|
|
11
|
+
* other CommonJS test runners working for integrators that never call Sui.
|
|
12
|
+
*
|
|
13
|
+
* @module SuiClient
|
|
14
|
+
*/
|
|
15
|
+
import type { SuiGrpcClient } from '@mysten/sui/grpc';
|
|
16
|
+
import type { ISuiConfig, ISuiNetwork } from '../../types';
|
|
17
|
+
/** Network and ordered endpoints for Sui calls: primary first, failover after. */
|
|
18
|
+
export interface ISuiNetworkConfig {
|
|
19
|
+
network: ISuiNetwork;
|
|
20
|
+
/** gRPC-web endpoints in priority order. */
|
|
21
|
+
rpcUrls: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the network and ordered endpoints for a Sui config.
|
|
25
|
+
*
|
|
26
|
+
* A consumer `rpcUrl` becomes the primary, with the public fullnode kept behind
|
|
27
|
+
* it as failover, so a premium provider still degrades to the public network
|
|
28
|
+
* instead of becoming a single point of failure.
|
|
29
|
+
*
|
|
30
|
+
* @param config - The `sui` config passed to `new UpshiftSDK({ sui })`.
|
|
31
|
+
* @returns The network (`'mainnet'` by default) and its endpoints.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveSuiNetworkConfig(config?: ISuiConfig): ISuiNetworkConfig;
|
|
34
|
+
/**
|
|
35
|
+
* Host-only view of an endpoint, safe for logs and errors: keyed provider URLs
|
|
36
|
+
* carry their key in the path.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export declare function suiEndpointHost(url: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Whether an error from a Sui call means the endpoint failed and the next one
|
|
42
|
+
* should be tried.
|
|
43
|
+
*
|
|
44
|
+
* @param err - What the call rejected with.
|
|
45
|
+
* @returns `true` for transport failures, gRPC statuses in
|
|
46
|
+
* {@link RETRYABLE_GRPC_CODES}, a bare `NOT_FOUND` (the endpoint has no Sui
|
|
47
|
+
* gRPC service at that path) and the SDK's own call timeout.
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export declare function isRetryableSuiError(err: unknown): boolean;
|
|
51
|
+
type SuiClientFactory = (baseUrl: string, network: ISuiNetwork) => Promise<SuiGrpcClient>;
|
|
52
|
+
/**
|
|
53
|
+
* Replace the client factory and clear the caches. Test-only seam.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export declare function setSuiClientFactoryForTests(factory: SuiClientFactory | undefined): void;
|
|
57
|
+
/**
|
|
58
|
+
* Run a Sui call against the first endpoint that answers.
|
|
59
|
+
*
|
|
60
|
+
* Endpoints are tried in order, starting with the one that last worked (for
|
|
61
|
+
* {@link PREFERRED_ENDPOINT_TTL_MS}). An endpoint failure — see
|
|
62
|
+
* {@link isRetryableSuiError} — moves on to the next endpoint; any other error
|
|
63
|
+
* is thrown at once. Each attempt is bounded by a 15 s timeout.
|
|
64
|
+
*
|
|
65
|
+
* Worst case: one attempt per endpoint (two with a consumer `rpcUrl`).
|
|
66
|
+
*
|
|
67
|
+
* @param config - Network and endpoints from {@link resolveSuiNetworkConfig}.
|
|
68
|
+
* @param call - The call to make with a client; it may run more than once, so
|
|
69
|
+
* it must not have side effects before its RPC completes.
|
|
70
|
+
* @returns What `call` resolves to.
|
|
71
|
+
* @throws {@link UpshiftNetworkError} when every endpoint fails, naming their
|
|
72
|
+
* hosts (never their full URLs); the last failure is the `cause`.
|
|
73
|
+
* @throws Whatever `call` throws for a non-endpoint error.
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const config = resolveSuiNetworkConfig({ network: 'testnet' });
|
|
77
|
+
* const { object } = await withSuiClient(config, (client) =>
|
|
78
|
+
* client.core.getObject({ objectId: vaultId }),
|
|
79
|
+
* );
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare function withSuiClient<T>(config: ISuiNetworkConfig, call: (client: SuiGrpcClient) => Promise<T>): Promise<T>;
|
|
83
|
+
export {};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveSuiNetworkConfig = resolveSuiNetworkConfig;
|
|
37
|
+
exports.suiEndpointHost = suiEndpointHost;
|
|
38
|
+
exports.isRetryableSuiError = isRetryableSuiError;
|
|
39
|
+
exports.setSuiClientFactoryForTests = setSuiClientFactoryForTests;
|
|
40
|
+
exports.withSuiClient = withSuiClient;
|
|
41
|
+
const logger_1 = require("../../core/logger");
|
|
42
|
+
const errors_1 = require("../../core/errors");
|
|
43
|
+
const concurrency_1 = require("../../core/helpers/concurrency");
|
|
44
|
+
const constants_1 = require("./constants");
|
|
45
|
+
/** Bound on one Sui call, so a hung endpoint fails over instead of blocking. */
|
|
46
|
+
const SUI_CALL_TIMEOUT_MS = 15_000;
|
|
47
|
+
/** How long a working endpoint is tried first before the order resets. */
|
|
48
|
+
const PREFERRED_ENDPOINT_TTL_MS = 30_000;
|
|
49
|
+
/**
|
|
50
|
+
* gRPC status codes that mean the endpoint, not the request, failed: the next
|
|
51
|
+
* endpoint may succeed. `fetch failed` (refused connection, DNS) arrives as
|
|
52
|
+
* `INTERNAL`; a provider that does not serve gRPC-web answers `UNAUTHENTICATED`
|
|
53
|
+
* or similar. Request errors such as `INVALID_ARGUMENT` would fail the same way
|
|
54
|
+
* everywhere, so they are thrown at once.
|
|
55
|
+
*/
|
|
56
|
+
const RETRYABLE_GRPC_CODES = new Set([
|
|
57
|
+
'INTERNAL',
|
|
58
|
+
'UNAVAILABLE',
|
|
59
|
+
'UNKNOWN',
|
|
60
|
+
'DEADLINE_EXCEEDED',
|
|
61
|
+
'RESOURCE_EXHAUSTED',
|
|
62
|
+
'UNAUTHENTICATED',
|
|
63
|
+
'PERMISSION_DENIED',
|
|
64
|
+
'UNIMPLEMENTED',
|
|
65
|
+
]);
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the network and ordered endpoints for a Sui config.
|
|
68
|
+
*
|
|
69
|
+
* A consumer `rpcUrl` becomes the primary, with the public fullnode kept behind
|
|
70
|
+
* it as failover, so a premium provider still degrades to the public network
|
|
71
|
+
* instead of becoming a single point of failure.
|
|
72
|
+
*
|
|
73
|
+
* @param config - The `sui` config passed to `new UpshiftSDK({ sui })`.
|
|
74
|
+
* @returns The network (`'mainnet'` by default) and its endpoints.
|
|
75
|
+
*/
|
|
76
|
+
function resolveSuiNetworkConfig(config = {}) {
|
|
77
|
+
const network = config.network ?? 'mainnet';
|
|
78
|
+
const publicUrl = constants_1.SUI_GRPC_URLS[network];
|
|
79
|
+
const override = config.rpcUrl?.trim();
|
|
80
|
+
const rpcUrls = override && override !== publicUrl ? [override, publicUrl] : [publicUrl];
|
|
81
|
+
return { network, rpcUrls };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Host-only view of an endpoint, safe for logs and errors: keyed provider URLs
|
|
85
|
+
* carry their key in the path.
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
function suiEndpointHost(url) {
|
|
89
|
+
try {
|
|
90
|
+
return new URL(url).host;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return '<invalid url>';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Whether an error from a Sui call means the endpoint failed and the next one
|
|
98
|
+
* should be tried.
|
|
99
|
+
*
|
|
100
|
+
* @param err - What the call rejected with.
|
|
101
|
+
* @returns `true` for transport failures, gRPC statuses in
|
|
102
|
+
* {@link RETRYABLE_GRPC_CODES}, a bare `NOT_FOUND` (the endpoint has no Sui
|
|
103
|
+
* gRPC service at that path) and the SDK's own call timeout.
|
|
104
|
+
* @internal
|
|
105
|
+
*/
|
|
106
|
+
function isRetryableSuiError(err) {
|
|
107
|
+
if (!(err instanceof Error))
|
|
108
|
+
return false;
|
|
109
|
+
if (err.message.startsWith('sui call timed out'))
|
|
110
|
+
return true;
|
|
111
|
+
if (err.name !== 'RpcError')
|
|
112
|
+
return false;
|
|
113
|
+
const code = err.code;
|
|
114
|
+
if (typeof code !== 'string')
|
|
115
|
+
return false;
|
|
116
|
+
if (RETRYABLE_GRPC_CODES.has(code))
|
|
117
|
+
return true;
|
|
118
|
+
// A path with no gRPC service answers HTTP 404, which the transport reports
|
|
119
|
+
// as NOT_FOUND with no message; a missing object is a different error type.
|
|
120
|
+
return code === 'NOT_FOUND' && err.message === '';
|
|
121
|
+
}
|
|
122
|
+
/** Default factory: loads `@mysten/sui/grpc` on first use. */
|
|
123
|
+
const defaultClientFactory = async (baseUrl, network) => {
|
|
124
|
+
const { SuiGrpcClient } = await Promise.resolve().then(() => __importStar(require('@mysten/sui/grpc')));
|
|
125
|
+
return new SuiGrpcClient({ baseUrl, network });
|
|
126
|
+
};
|
|
127
|
+
let clientFactory = defaultClientFactory;
|
|
128
|
+
const clientCache = new Map();
|
|
129
|
+
const preferredEndpoint = new Map();
|
|
130
|
+
/**
|
|
131
|
+
* Replace the client factory and clear the caches. Test-only seam.
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
function setSuiClientFactoryForTests(factory) {
|
|
135
|
+
clientFactory = factory ?? defaultClientFactory;
|
|
136
|
+
clientCache.clear();
|
|
137
|
+
preferredEndpoint.clear();
|
|
138
|
+
}
|
|
139
|
+
function clientFor(url, network) {
|
|
140
|
+
const key = `${network}|${url}`;
|
|
141
|
+
let client = clientCache.get(key);
|
|
142
|
+
if (!client) {
|
|
143
|
+
client = clientFactory(url, network);
|
|
144
|
+
clientCache.set(key, client);
|
|
145
|
+
// A failed construction must not be cached forever.
|
|
146
|
+
client.catch(() => clientCache.delete(key));
|
|
147
|
+
}
|
|
148
|
+
return client;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Run a Sui call against the first endpoint that answers.
|
|
152
|
+
*
|
|
153
|
+
* Endpoints are tried in order, starting with the one that last worked (for
|
|
154
|
+
* {@link PREFERRED_ENDPOINT_TTL_MS}). An endpoint failure — see
|
|
155
|
+
* {@link isRetryableSuiError} — moves on to the next endpoint; any other error
|
|
156
|
+
* is thrown at once. Each attempt is bounded by a 15 s timeout.
|
|
157
|
+
*
|
|
158
|
+
* Worst case: one attempt per endpoint (two with a consumer `rpcUrl`).
|
|
159
|
+
*
|
|
160
|
+
* @param config - Network and endpoints from {@link resolveSuiNetworkConfig}.
|
|
161
|
+
* @param call - The call to make with a client; it may run more than once, so
|
|
162
|
+
* it must not have side effects before its RPC completes.
|
|
163
|
+
* @returns What `call` resolves to.
|
|
164
|
+
* @throws {@link UpshiftNetworkError} when every endpoint fails, naming their
|
|
165
|
+
* hosts (never their full URLs); the last failure is the `cause`.
|
|
166
|
+
* @throws Whatever `call` throws for a non-endpoint error.
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* const config = resolveSuiNetworkConfig({ network: 'testnet' });
|
|
170
|
+
* const { object } = await withSuiClient(config, (client) =>
|
|
171
|
+
* client.core.getObject({ objectId: vaultId }),
|
|
172
|
+
* );
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
async function withSuiClient(config, call) {
|
|
176
|
+
const cacheKey = `${config.network}|${config.rpcUrls.join('|')}`;
|
|
177
|
+
const preferred = preferredEndpoint.get(cacheKey);
|
|
178
|
+
const order = preferred && Date.now() - preferred.at < PREFERRED_ENDPOINT_TTL_MS
|
|
179
|
+
? [preferred.url, ...config.rpcUrls.filter((u) => u !== preferred.url)]
|
|
180
|
+
: [...config.rpcUrls];
|
|
181
|
+
let lastError;
|
|
182
|
+
for (const url of order) {
|
|
183
|
+
let client;
|
|
184
|
+
try {
|
|
185
|
+
client = await clientFor(url, config.network);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
// A malformed endpoint (e.g. a typo'd consumer rpcUrl) must not disable
|
|
189
|
+
// the endpoints behind it.
|
|
190
|
+
lastError = err;
|
|
191
|
+
logger_1.Logger.log.warn('withSuiClient', 'Invalid Sui endpoint; trying next', {
|
|
192
|
+
host: suiEndpointHost(url),
|
|
193
|
+
});
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const result = await (0, concurrency_1.withTimeout)(call(client), SUI_CALL_TIMEOUT_MS, 'sui call');
|
|
198
|
+
preferredEndpoint.set(cacheKey, { url, at: Date.now() });
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
if (!isRetryableSuiError(err))
|
|
203
|
+
throw err;
|
|
204
|
+
lastError = err;
|
|
205
|
+
logger_1.Logger.log.warn('withSuiClient', 'Sui endpoint failed; trying next', {
|
|
206
|
+
host: suiEndpointHost(url),
|
|
207
|
+
code: err.code,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
preferredEndpoint.delete(cacheKey);
|
|
212
|
+
throw new errors_1.UpshiftNetworkError(`Every Sui ${config.network} endpoint failed: ${order
|
|
213
|
+
.map(suiEndpointHost)
|
|
214
|
+
.join(', ')}`, { cause: lastError, context: { network: config.network } });
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1,33 @@
|
|
|
1
|
+
import type { ISuiNetwork } from '../../types';
|
|
1
2
|
export { SUI_CHAIN_ID } from '../../core/constants/web3';
|
|
3
|
+
/** Sui's shared `Clock` system object, passed to time-dependent vault calls. */
|
|
4
|
+
export declare const SUI_CLOCK_ID = "0x6";
|
|
5
|
+
/** Sui's shared `CoinRegistry` system object (coin metadata / currencies). */
|
|
6
|
+
export declare const SUI_COIN_REGISTRY_ID = "0xc";
|
|
7
|
+
/**
|
|
8
|
+
* Mysten's public fullnodes, which serve gRPC-web. The SDK talks gRPC-web so
|
|
9
|
+
* the same code runs in browsers; a consumer `rpcUrl` goes in front of these as
|
|
10
|
+
* the primary (see `resolveSuiNetworkConfig`).
|
|
11
|
+
*/
|
|
12
|
+
export declare const SUI_GRPC_URLS: Record<ISuiNetwork, string>;
|
|
13
|
+
/**
|
|
14
|
+
* IDs of the deployed Upshift vault contracts on one Sui network.
|
|
15
|
+
*
|
|
16
|
+
* `packageId` is the latest published package (what move calls target);
|
|
17
|
+
* `originalPackageId` is the first published one, which Move keeps in type
|
|
18
|
+
* names across upgrades, so it names the vault's on-chain type.
|
|
19
|
+
*/
|
|
20
|
+
export interface ISuiVaultContracts {
|
|
21
|
+
/** Latest `upshift_vault` package ID; move-call targets use it. */
|
|
22
|
+
packageId: string;
|
|
23
|
+
/** Original `upshift_vault` package ID; vault and key types use it. */
|
|
24
|
+
originalPackageId: string;
|
|
25
|
+
/** Shared `upshift_vault::registry::Registry` object ID. */
|
|
26
|
+
registryId: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Built-in contract IDs per network. Empty until the contracts are published
|
|
30
|
+
* there: until then callers pass `packageId`, `originalPackageId` and
|
|
31
|
+
* `registryId` through `new UpshiftSDK({ sui })`.
|
|
32
|
+
*/
|
|
33
|
+
export declare const SUI_VAULT_CONTRACTS: Record<ISuiNetwork, Partial<ISuiVaultContracts>>;
|
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SUI_CHAIN_ID = void 0;
|
|
3
|
+
exports.SUI_VAULT_CONTRACTS = exports.SUI_GRPC_URLS = exports.SUI_COIN_REGISTRY_ID = exports.SUI_CLOCK_ID = exports.SUI_CHAIN_ID = void 0;
|
|
4
4
|
// Canonical definition lives in `core/constants/web3.ts` so `core/` can
|
|
5
5
|
// recognise the Sui chain ID without importing the adapter layer. Re-exported
|
|
6
6
|
// here to keep `adapters/sui/constants` import sites unchanged.
|
|
7
7
|
var web3_1 = require("../../core/constants/web3");
|
|
8
8
|
Object.defineProperty(exports, "SUI_CHAIN_ID", { enumerable: true, get: function () { return web3_1.SUI_CHAIN_ID; } });
|
|
9
|
+
/** Sui's shared `Clock` system object, passed to time-dependent vault calls. */
|
|
10
|
+
exports.SUI_CLOCK_ID = '0x6';
|
|
11
|
+
/** Sui's shared `CoinRegistry` system object (coin metadata / currencies). */
|
|
12
|
+
exports.SUI_COIN_REGISTRY_ID = '0xc';
|
|
13
|
+
/**
|
|
14
|
+
* Mysten's public fullnodes, which serve gRPC-web. The SDK talks gRPC-web so
|
|
15
|
+
* the same code runs in browsers; a consumer `rpcUrl` goes in front of these as
|
|
16
|
+
* the primary (see `resolveSuiNetworkConfig`).
|
|
17
|
+
*/
|
|
18
|
+
exports.SUI_GRPC_URLS = {
|
|
19
|
+
mainnet: 'https://fullnode.mainnet.sui.io:443',
|
|
20
|
+
testnet: 'https://fullnode.testnet.sui.io:443',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Built-in contract IDs per network. Empty until the contracts are published
|
|
24
|
+
* there: until then callers pass `packageId`, `originalPackageId` and
|
|
25
|
+
* `registryId` through `new UpshiftSDK({ sui })`.
|
|
26
|
+
*/
|
|
27
|
+
exports.SUI_VAULT_CONTRACTS = {
|
|
28
|
+
mainnet: {},
|
|
29
|
+
testnet: {},
|
|
30
|
+
};
|
|
9
31
|
//# sourceMappingURL=constants.js.map
|
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
* @module SuiAdapter
|
|
5
5
|
*/
|
|
6
6
|
import type { ISuiConfig, ISuiNetwork } from '../../types';
|
|
7
|
+
import * as SuiClient from './client';
|
|
7
8
|
import * as SuiConstants from './constants';
|
|
9
|
+
import * as SuiTargets from './targets';
|
|
8
10
|
import * as SuiUtils from './utils';
|
|
9
11
|
export declare const Sui: {
|
|
10
12
|
utils: typeof SuiUtils;
|
|
11
13
|
constants: typeof SuiConstants;
|
|
14
|
+
client: typeof SuiClient;
|
|
15
|
+
targets: typeof SuiTargets;
|
|
12
16
|
};
|
|
13
17
|
/**
|
|
14
18
|
* Sui Adapter for Upshift SDK.
|
|
@@ -34,11 +34,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.Sui = void 0;
|
|
37
|
+
const SuiClient = __importStar(require("./client"));
|
|
37
38
|
const SuiConstants = __importStar(require("./constants"));
|
|
39
|
+
const SuiTargets = __importStar(require("./targets"));
|
|
38
40
|
const SuiUtils = __importStar(require("./utils"));
|
|
39
41
|
exports.Sui = {
|
|
40
42
|
utils: SuiUtils,
|
|
41
43
|
constants: SuiConstants,
|
|
44
|
+
client: SuiClient,
|
|
45
|
+
targets: SuiTargets,
|
|
42
46
|
};
|
|
43
47
|
/**
|
|
44
48
|
* Sui Adapter for Upshift SDK.
|