@xian-tech/client 0.1.5 → 0.1.7
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 +12 -0
- package/dist/client.d.ts +18 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +238 -51
- package/dist/client.js.map +1 -1
- package/dist/compiler.d.ts +11 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +40 -0
- package/dist/compiler.js.map +1 -0
- package/dist/encoding.d.ts +14 -0
- package/dist/encoding.d.ts.map +1 -1
- package/dist/encoding.js +47 -4
- package/dist/encoding.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/relayer.d.ts +35 -0
- package/dist/relayer.d.ts.map +1 -0
- package/dist/relayer.js +473 -0
- package/dist/relayer.js.map +1 -0
- package/dist/shielded.d.ts +3 -0
- package/dist/shielded.d.ts.map +1 -0
- package/dist/shielded.js +19 -0
- package/dist/shielded.js.map +1 -0
- package/dist/types.d.ts +218 -35
- package/dist/types.d.ts.map +1 -1
- package/dist/watch.d.ts +4 -4
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +31 -9
- package/dist/watch.js.map +1 -1
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -14,3 +14,15 @@ It does not own:
|
|
|
14
14
|
- browser wallet discovery
|
|
15
15
|
- wallet-provider event contracts
|
|
16
16
|
- framework-specific bindings
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
flowchart LR
|
|
20
|
+
App["JS or TS app"] --> Client["XianClient"]
|
|
21
|
+
Client --> Queries["ABCI and HTTP queries"]
|
|
22
|
+
Client --> Tx["Build, sign, and broadcast tx"]
|
|
23
|
+
Client --> Watch["Websocket subscriptions"]
|
|
24
|
+
Signer["Ed25519Signer"] --> Tx
|
|
25
|
+
Queries --> Node["Xian node"]
|
|
26
|
+
Tx --> Node
|
|
27
|
+
Watch --> Dashboard["Dashboard websocket"]
|
|
28
|
+
```
|
package/dist/client.d.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { WatchApi } from "./watch.js";
|
|
2
|
-
import type { BroadcastMode, BroadcastTxOptions, BuildTxRequest, ContractSendOptions,
|
|
2
|
+
import type { BroadcastMode, BroadcastTxOptions, BuildTxRequest, ContractSendOptions, DeployContractOptions, SubmitContractOptions, EstimateChiResult, GetShieldedWalletHistoryOptions, GetTokenBalancesOptions, SimulateRequest, TokenApproveOptions, TokenTransferOptions, TransactionReceipt, TransactionSubmission, WaitForTxOptions, XianClientOptions, XianShieldedWalletHistoryResult, XianTokenBalancesResult, XianSignedTransaction, XianSigner, XianUnsignedTransaction } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Combine a caller-provided AbortSignal with a timeout-backed internal
|
|
5
|
+
* signal so either one triggering aborts the underlying fetch. We prefer
|
|
6
|
+
* AbortSignal.any when available; on older runtimes we stitch them
|
|
7
|
+
* together manually.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mergeAbortSignals(a: AbortSignal | undefined, b: AbortSignal | undefined): AbortSignal | undefined;
|
|
3
10
|
export declare class XianClient {
|
|
4
11
|
readonly rpcUrl: string;
|
|
5
12
|
readonly dashboardUrl?: string;
|
|
6
13
|
readonly watch: WatchApi;
|
|
7
14
|
private readonly fetchFn;
|
|
15
|
+
private readonly requestTimeoutMs;
|
|
8
16
|
private chainIdCache?;
|
|
9
17
|
constructor(options: XianClientOptions);
|
|
10
18
|
private requestJson;
|
|
@@ -27,11 +35,13 @@ export declare class XianClient {
|
|
|
27
35
|
name: string | null;
|
|
28
36
|
symbol: string | null;
|
|
29
37
|
logoUrl: string | null;
|
|
38
|
+
logoSvg: string | null;
|
|
30
39
|
}>;
|
|
31
40
|
getTokenBalances(address: string, options?: GetTokenBalancesOptions): Promise<XianTokenBalancesResult>;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
getShieldedWalletHistory(tagValue: string, options?: GetShieldedWalletHistoryOptions): Promise<XianShieldedWalletHistoryResult>;
|
|
42
|
+
getChiRate(): Promise<number | bigint | null>;
|
|
43
|
+
getContractSource(contract: string): Promise<string | null>;
|
|
44
|
+
getContractIr(contract: string): Promise<string | null>;
|
|
35
45
|
getContractMethods(contract: string): Promise<{
|
|
36
46
|
name: string;
|
|
37
47
|
arguments: {
|
|
@@ -41,7 +51,7 @@ export declare class XianClient {
|
|
|
41
51
|
}[]>;
|
|
42
52
|
simulate(request: SimulateRequest): Promise<Record<string, unknown>>;
|
|
43
53
|
call(request: SimulateRequest): Promise<unknown>;
|
|
44
|
-
|
|
54
|
+
estimateChi(request: SimulateRequest): Promise<EstimateChiResult>;
|
|
45
55
|
buildTx(request: BuildTxRequest): Promise<XianUnsignedTransaction>;
|
|
46
56
|
signTx(tx: XianUnsignedTransaction, signer: XianSigner): Promise<XianSignedTransaction>;
|
|
47
57
|
broadcastTx(tx: XianSignedTransaction, options?: BroadcastTxOptions): Promise<TransactionSubmission>;
|
|
@@ -53,6 +63,8 @@ export declare class XianClient {
|
|
|
53
63
|
timeoutMs?: number;
|
|
54
64
|
pollIntervalMs?: number;
|
|
55
65
|
}): Promise<TransactionSubmission>;
|
|
66
|
+
submitContract(request: SubmitContractOptions): Promise<TransactionSubmission>;
|
|
67
|
+
deployContract(request: DeployContractOptions): Promise<TransactionSubmission>;
|
|
56
68
|
contract(name: string): ContractClient;
|
|
57
69
|
token(name?: string): TokenClient;
|
|
58
70
|
}
|
|
@@ -75,6 +87,7 @@ export declare class TokenClient {
|
|
|
75
87
|
name: string | null;
|
|
76
88
|
symbol: string | null;
|
|
77
89
|
logoUrl: string | null;
|
|
90
|
+
logoSvg: string | null;
|
|
78
91
|
}>;
|
|
79
92
|
allowance(owner: string, spender: string): Promise<unknown>;
|
|
80
93
|
transfer(options: TokenTransferOptions): Promise<TransactionSubmission>;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { base64ToUtf8, bytesToHex, canonicalizeRuntime, decodeRuntime, encodeRuntime, parseXianNumber, sortKeysDeep, utf8ToBytes } from "./encoding.js";
|
|
1
|
+
import { base64ToUtf8, bytesToHex, canonicalizeRuntime, decodeRuntime, encodeRuntime, normalizeMaybeInteger, normalizeMaybeXianNumber, parseXianNumber, sortKeysDeep, utf8ToBytes } from "./encoding.js";
|
|
2
|
+
import { compileContractArtifacts } from "./compiler.js";
|
|
2
3
|
import { isValidEd25519Signature } from "./ed25519.js";
|
|
3
4
|
import { AbciError, RpcError, SimulationError, TransactionError, TransportError, TxTimeoutError } from "./errors.js";
|
|
4
5
|
import { WatchApi } from "./watch.js";
|
|
5
6
|
const EMPTY_ABCI_VALUE = "AA==";
|
|
6
7
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
7
8
|
const DEFAULT_POLL_INTERVAL_MS = 500;
|
|
8
|
-
const
|
|
9
|
-
const DEFAULT_MIN_STAMP_HEADROOM = 5_000;
|
|
9
|
+
const MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
|
|
10
10
|
function stripTrailingSlash(value) {
|
|
11
11
|
return value.replace(/\/+$/, "");
|
|
12
12
|
}
|
|
@@ -18,9 +18,9 @@ function isIdentifier(value) {
|
|
|
18
18
|
}
|
|
19
19
|
function isNonNegativeInteger(value) {
|
|
20
20
|
if (typeof value === "bigint") {
|
|
21
|
-
return value >= 0n;
|
|
21
|
+
return value >= 0n && value <= MAX_SAFE_BIGINT;
|
|
22
22
|
}
|
|
23
|
-
return typeof value === "number" && Number.
|
|
23
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
24
24
|
}
|
|
25
25
|
function isHexKey(value) {
|
|
26
26
|
return /^[a-fA-F0-9]{64}$/.test(value);
|
|
@@ -42,6 +42,30 @@ function asRecord(value) {
|
|
|
42
42
|
}
|
|
43
43
|
return value;
|
|
44
44
|
}
|
|
45
|
+
function isPlainObject(value) {
|
|
46
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
47
|
+
}
|
|
48
|
+
function validateTransactionJsonValue(value) {
|
|
49
|
+
if (value == null || typeof value === "string" || typeof value === "boolean") {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === "bigint") {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (typeof value === "number") {
|
|
56
|
+
return Number.isSafeInteger(value);
|
|
57
|
+
}
|
|
58
|
+
if (value instanceof Uint8Array) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
return value.every((item) => validateTransactionJsonValue(item));
|
|
63
|
+
}
|
|
64
|
+
if (isPlainObject(value)) {
|
|
65
|
+
return Object.values(value).every((item) => validateTransactionJsonValue(item));
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
45
69
|
function parseJsonResult(value) {
|
|
46
70
|
try {
|
|
47
71
|
return JSON.parse(value);
|
|
@@ -65,17 +89,40 @@ function normalizeMaybeString(value) {
|
|
|
65
89
|
}
|
|
66
90
|
return typeof value === "string" ? value : String(value);
|
|
67
91
|
}
|
|
68
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Coerce a chi_used value from a simulation response to a JS number.
|
|
94
|
+
*
|
|
95
|
+
* The simulator returns chi_used as either a number or a stringified
|
|
96
|
+
* integer. Historically we used Number.parseInt here, which silently
|
|
97
|
+
* truncates on scientific notation ("1e6" → 1), decimals ("1.5" → 1),
|
|
98
|
+
* and loses precision above 2^53 for large strings. parseXianNumber
|
|
99
|
+
* runs it through BigInt so oversized values are flagged. Chi values
|
|
100
|
+
* in practice fit comfortably inside Number.MAX_SAFE_INTEGER, so we
|
|
101
|
+
* collapse the bigint branch back to a number and throw only if the
|
|
102
|
+
* estimate is actually too large to represent safely — that can't
|
|
103
|
+
* happen today, but an explicit failure beats a silent mis-estimate.
|
|
104
|
+
*/
|
|
105
|
+
function coerceChiEstimate(value) {
|
|
106
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
69
109
|
if (value == null) {
|
|
70
|
-
|
|
110
|
+
throw new Error("simulation response is missing chi_used");
|
|
71
111
|
}
|
|
72
|
-
|
|
73
|
-
|
|
112
|
+
const raw = String(value).trim();
|
|
113
|
+
if (raw === "") {
|
|
114
|
+
throw new Error("simulation response has empty chi_used");
|
|
74
115
|
}
|
|
75
|
-
|
|
76
|
-
|
|
116
|
+
try {
|
|
117
|
+
const parsed = parseXianNumber(raw);
|
|
118
|
+
if (typeof parsed === "bigint") {
|
|
119
|
+
throw new Error(`chi_used estimate ${raw} exceeds Number.MAX_SAFE_INTEGER`);
|
|
120
|
+
}
|
|
121
|
+
return parsed;
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
throw new Error(`invalid chi_used estimate ${JSON.stringify(value)}: ${err instanceof Error ? err.message : String(err)}`);
|
|
77
125
|
}
|
|
78
|
-
return null;
|
|
79
126
|
}
|
|
80
127
|
function clampPageSize(value, fallback) {
|
|
81
128
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
@@ -92,9 +139,36 @@ function clampOffset(value) {
|
|
|
92
139
|
function isPendingLookupReceipt(receipt) {
|
|
93
140
|
return !receipt.success && receipt.txHash == null && receipt.transaction == null;
|
|
94
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Combine a caller-provided AbortSignal with a timeout-backed internal
|
|
144
|
+
* signal so either one triggering aborts the underlying fetch. We prefer
|
|
145
|
+
* AbortSignal.any when available; on older runtimes we stitch them
|
|
146
|
+
* together manually.
|
|
147
|
+
*/
|
|
148
|
+
export function mergeAbortSignals(a, b) {
|
|
149
|
+
if (!a)
|
|
150
|
+
return b;
|
|
151
|
+
if (!b)
|
|
152
|
+
return a;
|
|
153
|
+
const anyFn = AbortSignal.any;
|
|
154
|
+
if (typeof anyFn === "function") {
|
|
155
|
+
return anyFn([a, b]);
|
|
156
|
+
}
|
|
157
|
+
const controller = new AbortController();
|
|
158
|
+
const forward = (signal) => {
|
|
159
|
+
if (signal.aborted) {
|
|
160
|
+
controller.abort(signal.reason);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
|
|
164
|
+
};
|
|
165
|
+
forward(a);
|
|
166
|
+
forward(b);
|
|
167
|
+
return controller.signal;
|
|
168
|
+
}
|
|
95
169
|
function validatePayload(payload) {
|
|
96
170
|
const keys = Object.keys(payload).sort();
|
|
97
|
-
const expected = ["chain_id", "contract", "function", "kwargs", "nonce", "sender"
|
|
171
|
+
const expected = ["chain_id", "chi_supplied", "contract", "function", "kwargs", "nonce", "sender"];
|
|
98
172
|
if (keys.join(",") !== expected.join(",")) {
|
|
99
173
|
throw new TransactionError("payload must contain the canonical Xian transaction keys");
|
|
100
174
|
}
|
|
@@ -110,20 +184,27 @@ function validatePayload(payload) {
|
|
|
110
184
|
if (!isNonNegativeInteger(payload.nonce)) {
|
|
111
185
|
throw new TransactionError("nonce must be a non-negative integer");
|
|
112
186
|
}
|
|
113
|
-
if (!isNonNegativeInteger(payload.
|
|
114
|
-
throw new TransactionError("
|
|
187
|
+
if (!isNonNegativeInteger(payload.chi_supplied)) {
|
|
188
|
+
throw new TransactionError("chi_supplied must be a non-negative integer");
|
|
189
|
+
}
|
|
190
|
+
if (!isPlainObject(payload.kwargs)) {
|
|
191
|
+
throw new TransactionError("kwargs must be an object");
|
|
115
192
|
}
|
|
116
193
|
for (const key of Object.keys(payload.kwargs)) {
|
|
117
194
|
if (!isIdentifier(key)) {
|
|
118
195
|
throw new TransactionError("kwargs keys must be valid identifiers");
|
|
119
196
|
}
|
|
120
197
|
}
|
|
198
|
+
if (!validateTransactionJsonValue(payload.kwargs)) {
|
|
199
|
+
throw new TransactionError("kwargs values must be JSON-compatible transaction values");
|
|
200
|
+
}
|
|
121
201
|
}
|
|
122
202
|
export class XianClient {
|
|
123
203
|
rpcUrl;
|
|
124
204
|
dashboardUrl;
|
|
125
205
|
watch;
|
|
126
206
|
fetchFn;
|
|
207
|
+
requestTimeoutMs;
|
|
127
208
|
chainIdCache;
|
|
128
209
|
constructor(options) {
|
|
129
210
|
this.rpcUrl = stripTrailingSlash(options.rpcUrl);
|
|
@@ -132,19 +213,43 @@ export class XianClient {
|
|
|
132
213
|
: undefined;
|
|
133
214
|
this.chainIdCache = options.chainId;
|
|
134
215
|
this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
216
|
+
this.requestTimeoutMs =
|
|
217
|
+
options.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
135
218
|
this.watch = new WatchApi({
|
|
136
219
|
dashboardUrl: this.dashboardUrl,
|
|
137
220
|
webSocketFactory: options.webSocketFactory
|
|
138
221
|
});
|
|
139
222
|
}
|
|
140
|
-
async requestJson(method, url) {
|
|
223
|
+
async requestJson(method, url, options) {
|
|
224
|
+
const timeoutMs = options?.timeoutMs ?? this.requestTimeoutMs;
|
|
225
|
+
const timeoutController = timeoutMs > 0 ? new AbortController() : undefined;
|
|
226
|
+
const timeoutHandle = timeoutController
|
|
227
|
+
? setTimeout(() => timeoutController.abort(), timeoutMs)
|
|
228
|
+
: undefined;
|
|
229
|
+
const callerSignal = options?.signal;
|
|
230
|
+
const signal = mergeAbortSignals(callerSignal, timeoutController?.signal);
|
|
141
231
|
let response;
|
|
142
232
|
try {
|
|
143
|
-
response = await this.fetchFn(url, {
|
|
233
|
+
response = await this.fetchFn(url, {
|
|
234
|
+
method,
|
|
235
|
+
headers: method === "POST" ? { "content-type": "application/json" } : undefined,
|
|
236
|
+
signal,
|
|
237
|
+
});
|
|
144
238
|
}
|
|
145
239
|
catch (error) {
|
|
240
|
+
if (callerSignal?.aborted) {
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
if (timeoutController?.signal.aborted) {
|
|
244
|
+
throw new TransportError(`request timed out after ${timeoutMs}ms for ${url}`, { cause: error });
|
|
245
|
+
}
|
|
146
246
|
throw new TransportError(`request failed for ${url}`, { cause: error });
|
|
147
247
|
}
|
|
248
|
+
finally {
|
|
249
|
+
if (timeoutHandle !== undefined) {
|
|
250
|
+
clearTimeout(timeoutHandle);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
148
253
|
if (!response.ok) {
|
|
149
254
|
throw new TransportError(`${method} ${url} returned ${response.status}`);
|
|
150
255
|
}
|
|
@@ -269,24 +374,24 @@ export class XianClient {
|
|
|
269
374
|
}
|
|
270
375
|
}
|
|
271
376
|
async getTokenMetadata(contract) {
|
|
272
|
-
if (contract === "currency") {
|
|
273
|
-
return {
|
|
274
|
-
contract,
|
|
275
|
-
name: "Xian",
|
|
276
|
-
symbol: "Xian",
|
|
277
|
-
logoUrl: null
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
377
|
const [name, symbol, logoUrl] = await Promise.all([
|
|
281
378
|
this.getState(contract, "metadata", ["token_name"]),
|
|
282
379
|
this.getState(contract, "metadata", ["token_symbol"]),
|
|
283
380
|
this.getState(contract, "metadata", ["token_logo_url"])
|
|
284
381
|
]);
|
|
382
|
+
const rawLogoUrl = normalizeMaybeString(logoUrl);
|
|
383
|
+
const normalizedLogoUrl = rawLogoUrl?.trim() ? rawLogoUrl.trim() : null;
|
|
384
|
+
let logoSvg = null;
|
|
385
|
+
if (!normalizedLogoUrl) {
|
|
386
|
+
const rawLogoSvg = normalizeMaybeString(await this.getState(contract, "metadata", ["token_logo_svg"]).catch(() => null));
|
|
387
|
+
logoSvg = rawLogoSvg?.trim() ? rawLogoSvg.trim() : null;
|
|
388
|
+
}
|
|
285
389
|
return {
|
|
286
390
|
contract,
|
|
287
391
|
name: normalizeMaybeString(name),
|
|
288
392
|
symbol: normalizeMaybeString(symbol),
|
|
289
|
-
logoUrl:
|
|
393
|
+
logoUrl: normalizedLogoUrl,
|
|
394
|
+
logoSvg
|
|
290
395
|
};
|
|
291
396
|
}
|
|
292
397
|
async getTokenBalances(address, options) {
|
|
@@ -323,8 +428,47 @@ export class XianClient {
|
|
|
323
428
|
offset: Number(payload.offset ?? offset)
|
|
324
429
|
};
|
|
325
430
|
}
|
|
326
|
-
async
|
|
327
|
-
const
|
|
431
|
+
async getShieldedWalletHistory(tagValue, options) {
|
|
432
|
+
const limit = clampPageSize(options?.limit, 100);
|
|
433
|
+
const afterNoteIndex = clampOffset(options?.afterNoteIndex);
|
|
434
|
+
const kind = typeof options?.kind === "string" && options.kind.trim().length > 0
|
|
435
|
+
? options.kind.trim()
|
|
436
|
+
: "sync_hint";
|
|
437
|
+
const data = await this.abciQuery(`/shielded_wallet_history/${tagValue}/limit=${limit}/kind=${kind}/after_note_index=${afterNoteIndex}`);
|
|
438
|
+
const value = this.decodeAbciValue(asRecord(asRecord(data.result).response).value);
|
|
439
|
+
const payload = asRecord(value);
|
|
440
|
+
const items = Array.isArray(payload.items)
|
|
441
|
+
? payload.items
|
|
442
|
+
.filter((item) => item != null && typeof item === "object")
|
|
443
|
+
.map((item) => ({
|
|
444
|
+
eventId: normalizeMaybeInteger(item.event_id),
|
|
445
|
+
txHash: normalizeMaybeString(item.tx_hash),
|
|
446
|
+
blockHeight: normalizeMaybeXianNumber(item.block_height),
|
|
447
|
+
txIndex: normalizeMaybeXianNumber(item.tx_index),
|
|
448
|
+
contract: normalizeMaybeString(item.contract),
|
|
449
|
+
function: normalizeMaybeString(item.function),
|
|
450
|
+
action: normalizeMaybeString(item.action),
|
|
451
|
+
outputIndex: normalizeMaybeXianNumber(item.output_index),
|
|
452
|
+
noteIndex: normalizeMaybeXianNumber(item.note_index),
|
|
453
|
+
commitment: normalizeMaybeString(item.commitment),
|
|
454
|
+
newRoot: normalizeMaybeString(item.new_root),
|
|
455
|
+
payloadHash: normalizeMaybeString(item.payload_hash),
|
|
456
|
+
tagKind: normalizeMaybeString(item.tag_kind),
|
|
457
|
+
tagValue: normalizeMaybeString(item.tag_value),
|
|
458
|
+
outputPayload: normalizeMaybeString(item.output_payload),
|
|
459
|
+
createdAt: normalizeMaybeString(item.created_at ?? item.created),
|
|
460
|
+
raw: item
|
|
461
|
+
}))
|
|
462
|
+
: [];
|
|
463
|
+
return {
|
|
464
|
+
available: payload.available !== false,
|
|
465
|
+
items,
|
|
466
|
+
limit: Number(payload.limit ?? limit),
|
|
467
|
+
afterNoteIndex: Number(payload.after_note_index ?? afterNoteIndex)
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
async getChiRate() {
|
|
471
|
+
const value = await this.getState("chi_cost", "S", ["value"]);
|
|
328
472
|
if (value == null) {
|
|
329
473
|
return null;
|
|
330
474
|
}
|
|
@@ -333,16 +477,16 @@ export class XianClient {
|
|
|
333
477
|
}
|
|
334
478
|
return parseXianNumber(String(value));
|
|
335
479
|
}
|
|
336
|
-
async
|
|
337
|
-
const data = await this.abciQuery(`/
|
|
480
|
+
async getContractSource(contract) {
|
|
481
|
+
const data = await this.abciQuery(`/contract_source/${contract}`);
|
|
338
482
|
const value = asRecord(asRecord(data.result).response).value;
|
|
339
483
|
if (value == null || value === EMPTY_ABCI_VALUE) {
|
|
340
484
|
return null;
|
|
341
485
|
}
|
|
342
486
|
return base64ToUtf8(String(value));
|
|
343
487
|
}
|
|
344
|
-
async
|
|
345
|
-
const data = await this.abciQuery(`/
|
|
488
|
+
async getContractIr(contract) {
|
|
489
|
+
const data = await this.abciQuery(`/contract_ir/${contract}`);
|
|
346
490
|
const value = asRecord(asRecord(data.result).response).value;
|
|
347
491
|
if (value == null || value === EMPTY_ABCI_VALUE) {
|
|
348
492
|
return null;
|
|
@@ -405,34 +549,26 @@ export class XianClient {
|
|
|
405
549
|
}
|
|
406
550
|
return simulation.result;
|
|
407
551
|
}
|
|
408
|
-
async
|
|
552
|
+
async estimateChi(request) {
|
|
409
553
|
const simulation = await this.simulate(request);
|
|
410
|
-
const
|
|
411
|
-
const estimated = typeof rawStamps === "number"
|
|
412
|
-
? rawStamps
|
|
413
|
-
: Number.parseInt(String(rawStamps ?? "0"), 10);
|
|
414
|
-
const stampMargin = options?.stampMargin ?? DEFAULT_STAMP_MARGIN;
|
|
415
|
-
const minStampHeadroom = options?.minStampHeadroom ?? DEFAULT_MIN_STAMP_HEADROOM;
|
|
416
|
-
const proportional = Math.ceil(estimated * stampMargin);
|
|
417
|
-
const suggested = estimated + Math.max(proportional, minStampHeadroom);
|
|
554
|
+
const estimated = coerceChiEstimate(simulation.chi_used);
|
|
418
555
|
return {
|
|
419
556
|
estimated,
|
|
420
|
-
suggested,
|
|
421
557
|
simulation
|
|
422
558
|
};
|
|
423
559
|
}
|
|
424
560
|
async buildTx(request) {
|
|
425
561
|
const chainId = request.chainId ?? (await this.getChainId());
|
|
426
562
|
const nonce = request.nonce ?? (await this.getNonce(request.sender));
|
|
427
|
-
let
|
|
428
|
-
if (
|
|
429
|
-
const estimate = await this.
|
|
563
|
+
let chiSupplied = request.chiSupplied ?? request.chi;
|
|
564
|
+
if (chiSupplied == null) {
|
|
565
|
+
const estimate = await this.estimateChi({
|
|
430
566
|
sender: request.sender,
|
|
431
567
|
contract: request.contract,
|
|
432
568
|
function: request.function,
|
|
433
569
|
kwargs: request.kwargs
|
|
434
570
|
});
|
|
435
|
-
|
|
571
|
+
chiSupplied = estimate.estimated;
|
|
436
572
|
}
|
|
437
573
|
const payload = sortKeysDeep({
|
|
438
574
|
chain_id: chainId,
|
|
@@ -441,7 +577,7 @@ export class XianClient {
|
|
|
441
577
|
kwargs: request.kwargs,
|
|
442
578
|
nonce,
|
|
443
579
|
sender: request.sender,
|
|
444
|
-
|
|
580
|
+
chi_supplied: chiSupplied
|
|
445
581
|
});
|
|
446
582
|
validatePayload(payload);
|
|
447
583
|
return { payload };
|
|
@@ -486,7 +622,7 @@ export class XianClient {
|
|
|
486
622
|
message: "error" in data ? readErrorMessage(asRecord(data.error)) : undefined,
|
|
487
623
|
mode,
|
|
488
624
|
nonce: tx.payload.nonce,
|
|
489
|
-
|
|
625
|
+
chiSupplied: tx.payload.chi_supplied,
|
|
490
626
|
response: data
|
|
491
627
|
};
|
|
492
628
|
if ("error" in data) {
|
|
@@ -550,6 +686,57 @@ export class XianClient {
|
|
|
550
686
|
pollIntervalMs: request.pollIntervalMs
|
|
551
687
|
});
|
|
552
688
|
}
|
|
689
|
+
async submitContract(request) {
|
|
690
|
+
if (typeof request.signer.getAddress !== "function") {
|
|
691
|
+
throw new TransactionError("signer.getAddress() is required for contract submission");
|
|
692
|
+
}
|
|
693
|
+
if (!isPlainObject(request.deploymentArtifacts)) {
|
|
694
|
+
throw new TransactionError("deploymentArtifacts must be an object");
|
|
695
|
+
}
|
|
696
|
+
if ("runtime_code" in request.deploymentArtifacts) {
|
|
697
|
+
throw new TransactionError("deploymentArtifacts must not include runtime_code");
|
|
698
|
+
}
|
|
699
|
+
const hashes = request.deploymentArtifacts.hashes;
|
|
700
|
+
if (isPlainObject(hashes) && "runtime_code_sha256" in hashes) {
|
|
701
|
+
throw new TransactionError("deploymentArtifacts hashes must not include runtime_code_sha256");
|
|
702
|
+
}
|
|
703
|
+
const kwargs = {
|
|
704
|
+
name: request.name,
|
|
705
|
+
deployment_artifacts: request.deploymentArtifacts
|
|
706
|
+
};
|
|
707
|
+
if (request.args && Object.keys(request.args).length > 0) {
|
|
708
|
+
kwargs.constructor_args = request.args;
|
|
709
|
+
}
|
|
710
|
+
const sender = await request.signer.getAddress();
|
|
711
|
+
return this.sendTx({
|
|
712
|
+
sender,
|
|
713
|
+
contract: "submission",
|
|
714
|
+
function: "submit_contract",
|
|
715
|
+
kwargs,
|
|
716
|
+
signer: request.signer,
|
|
717
|
+
mode: request.mode,
|
|
718
|
+
waitForTx: request.waitForTx,
|
|
719
|
+
timeoutMs: request.timeoutMs,
|
|
720
|
+
pollIntervalMs: request.pollIntervalMs,
|
|
721
|
+
chi: request.chi,
|
|
722
|
+
nonce: request.nonce,
|
|
723
|
+
chainId: request.chainId
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
async deployContract(request) {
|
|
727
|
+
const { source, compiler, lint, vmProfile, ...submitRequest } = request;
|
|
728
|
+
const deploymentArtifacts = await compileContractArtifacts({
|
|
729
|
+
moduleName: request.name,
|
|
730
|
+
source,
|
|
731
|
+
compiler,
|
|
732
|
+
lint,
|
|
733
|
+
vmProfile
|
|
734
|
+
});
|
|
735
|
+
return this.submitContract({
|
|
736
|
+
...submitRequest,
|
|
737
|
+
deploymentArtifacts
|
|
738
|
+
});
|
|
739
|
+
}
|
|
553
740
|
contract(name) {
|
|
554
741
|
return new ContractClient(this, name);
|
|
555
742
|
}
|
|
@@ -594,7 +781,7 @@ export class ContractClient {
|
|
|
594
781
|
waitForTx: options.waitForTx,
|
|
595
782
|
timeoutMs: options.timeoutMs,
|
|
596
783
|
pollIntervalMs: options.pollIntervalMs,
|
|
597
|
-
|
|
784
|
+
chi: options.chi,
|
|
598
785
|
nonce: options.nonce,
|
|
599
786
|
chainId: options.chainId
|
|
600
787
|
});
|
|
@@ -634,7 +821,7 @@ export class TokenClient {
|
|
|
634
821
|
waitForTx: options.waitForTx,
|
|
635
822
|
timeoutMs: options.timeoutMs,
|
|
636
823
|
pollIntervalMs: options.pollIntervalMs,
|
|
637
|
-
|
|
824
|
+
chi: options.chi,
|
|
638
825
|
nonce: options.nonce,
|
|
639
826
|
chainId: options.chainId
|
|
640
827
|
});
|
|
@@ -657,7 +844,7 @@ export class TokenClient {
|
|
|
657
844
|
waitForTx: options.waitForTx,
|
|
658
845
|
timeoutMs: options.timeoutMs,
|
|
659
846
|
pollIntervalMs: options.pollIntervalMs,
|
|
660
|
-
|
|
847
|
+
chi: options.chi,
|
|
661
848
|
nonce: options.nonce,
|
|
662
849
|
chainId: options.chainId
|
|
663
850
|
});
|