@usearete/react 0.1.5 → 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 +119 -65
- package/dist/index.d.ts +93 -32
- package/dist/index.esm.js +2210 -280
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2214 -280
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.esm.js
CHANGED
|
@@ -1479,7 +1479,7 @@ const BS_FINISH_DONE = 4; /* finish done, accept no more input or output */
|
|
|
1479
1479
|
|
|
1480
1480
|
const OS_CODE = 0x03; // Unix :) . Don't detect, use this default.
|
|
1481
1481
|
|
|
1482
|
-
const err = (strm, errorCode) => {
|
|
1482
|
+
const err$1 = (strm, errorCode) => {
|
|
1483
1483
|
strm.msg = messages[errorCode];
|
|
1484
1484
|
return errorCode;
|
|
1485
1485
|
};
|
|
@@ -2796,7 +2796,7 @@ const deflateStateCheck = (strm) => {
|
|
|
2796
2796
|
const deflateResetKeep = (strm) => {
|
|
2797
2797
|
|
|
2798
2798
|
if (deflateStateCheck(strm)) {
|
|
2799
|
-
return err(strm, Z_STREAM_ERROR$2);
|
|
2799
|
+
return err$1(strm, Z_STREAM_ERROR$2);
|
|
2800
2800
|
}
|
|
2801
2801
|
|
|
2802
2802
|
strm.total_in = strm.total_out = 0;
|
|
@@ -2870,7 +2870,7 @@ const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => {
|
|
|
2870
2870
|
if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED$2 ||
|
|
2871
2871
|
windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
|
|
2872
2872
|
strategy < 0 || strategy > Z_FIXED || (windowBits === 8 && wrap !== 1)) {
|
|
2873
|
-
return err(strm, Z_STREAM_ERROR$2);
|
|
2873
|
+
return err$1(strm, Z_STREAM_ERROR$2);
|
|
2874
2874
|
}
|
|
2875
2875
|
|
|
2876
2876
|
|
|
@@ -2975,7 +2975,7 @@ const deflateInit = (strm, level) => {
|
|
|
2975
2975
|
const deflate$2 = (strm, flush) => {
|
|
2976
2976
|
|
|
2977
2977
|
if (deflateStateCheck(strm) || flush > Z_BLOCK$1 || flush < 0) {
|
|
2978
|
-
return strm ? err(strm, Z_STREAM_ERROR$2) : Z_STREAM_ERROR$2;
|
|
2978
|
+
return strm ? err$1(strm, Z_STREAM_ERROR$2) : Z_STREAM_ERROR$2;
|
|
2979
2979
|
}
|
|
2980
2980
|
|
|
2981
2981
|
const s = strm.state;
|
|
@@ -2983,7 +2983,7 @@ const deflate$2 = (strm, flush) => {
|
|
|
2983
2983
|
if (!strm.output ||
|
|
2984
2984
|
(strm.avail_in !== 0 && !strm.input) ||
|
|
2985
2985
|
(s.status === FINISH_STATE && flush !== Z_FINISH$3)) {
|
|
2986
|
-
return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR$1 : Z_STREAM_ERROR$2);
|
|
2986
|
+
return err$1(strm, (strm.avail_out === 0) ? Z_BUF_ERROR$1 : Z_STREAM_ERROR$2);
|
|
2987
2987
|
}
|
|
2988
2988
|
|
|
2989
2989
|
const old_flush = s.last_flush;
|
|
@@ -3009,12 +3009,12 @@ const deflate$2 = (strm, flush) => {
|
|
|
3009
3009
|
*/
|
|
3010
3010
|
} else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) &&
|
|
3011
3011
|
flush !== Z_FINISH$3) {
|
|
3012
|
-
return err(strm, Z_BUF_ERROR$1);
|
|
3012
|
+
return err$1(strm, Z_BUF_ERROR$1);
|
|
3013
3013
|
}
|
|
3014
3014
|
|
|
3015
3015
|
/* User must not provide more input after the first FINISH: */
|
|
3016
3016
|
if (s.status === FINISH_STATE && strm.avail_in !== 0) {
|
|
3017
|
-
return err(strm, Z_BUF_ERROR$1);
|
|
3017
|
+
return err$1(strm, Z_BUF_ERROR$1);
|
|
3018
3018
|
}
|
|
3019
3019
|
|
|
3020
3020
|
/* Write the header */
|
|
@@ -3336,7 +3336,7 @@ const deflateEnd = (strm) => {
|
|
|
3336
3336
|
|
|
3337
3337
|
strm.state = null;
|
|
3338
3338
|
|
|
3339
|
-
return status === BUSY_STATE ? err(strm, Z_DATA_ERROR$2) : Z_OK$3;
|
|
3339
|
+
return status === BUSY_STATE ? err$1(strm, Z_DATA_ERROR$2) : Z_OK$3;
|
|
3340
3340
|
};
|
|
3341
3341
|
|
|
3342
3342
|
|
|
@@ -6762,6 +6762,453 @@ var inflate_1$1 = {
|
|
|
6762
6762
|
const { Inflate, inflate, inflateRaw, ungzip } = inflate_1$1;
|
|
6763
6763
|
var inflate_1 = inflate;
|
|
6764
6764
|
|
|
6765
|
+
/*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
|
|
6766
|
+
/**
|
|
6767
|
+
* 4KB JS implementation of ed25519 EdDSA signatures.
|
|
6768
|
+
* Compliant with RFC8032, FIPS 186-5 & ZIP215.
|
|
6769
|
+
* @module
|
|
6770
|
+
* @example
|
|
6771
|
+
* ```js
|
|
6772
|
+
import * as ed from '@noble/ed25519';
|
|
6773
|
+
(async () => {
|
|
6774
|
+
const privKey = ed.utils.randomPrivateKey();
|
|
6775
|
+
const message = Uint8Array.from([0xab, 0xbc, 0xcd, 0xde]);
|
|
6776
|
+
const pubKey = await ed.getPublicKeyAsync(privKey); // Sync methods are also present
|
|
6777
|
+
const signature = await ed.signAsync(message, privKey);
|
|
6778
|
+
const isValid = await ed.verifyAsync(signature, message, pubKey);
|
|
6779
|
+
})();
|
|
6780
|
+
```
|
|
6781
|
+
*/
|
|
6782
|
+
/**
|
|
6783
|
+
* Curve params. ed25519 is twisted edwards curve. Equation is −x² + y² = -a + dx²y².
|
|
6784
|
+
* * P = `2n**255n - 19n` // field over which calculations are done
|
|
6785
|
+
* * N = `2n**252n + 27742317777372353535851937790883648493n` // group order, amount of curve points
|
|
6786
|
+
* * h = 8 // cofactor
|
|
6787
|
+
* * a = `Fp.create(BigInt(-1))` // equation param
|
|
6788
|
+
* * d = -121665/121666 a.k.a. `Fp.neg(121665 * Fp.inv(121666))` // equation param
|
|
6789
|
+
* * Gx, Gy are coordinates of Generator / base point
|
|
6790
|
+
*/
|
|
6791
|
+
const ed25519_CURVE = {
|
|
6792
|
+
p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
|
|
6793
|
+
n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
|
|
6794
|
+
h: 8n,
|
|
6795
|
+
a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
|
|
6796
|
+
d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
|
|
6797
|
+
Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
|
|
6798
|
+
Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n,
|
|
6799
|
+
};
|
|
6800
|
+
const { p: P, n: N, Gx, Gy, a: _a, d: _d } = ed25519_CURVE;
|
|
6801
|
+
const h = 8n; // cofactor
|
|
6802
|
+
const L = 32; // field / group byte length
|
|
6803
|
+
const L2 = 64;
|
|
6804
|
+
// Helpers and Precomputes sections are reused between libraries
|
|
6805
|
+
// ## Helpers
|
|
6806
|
+
// ----------
|
|
6807
|
+
// error helper, messes-up stack trace
|
|
6808
|
+
const err = (m = '') => {
|
|
6809
|
+
throw new Error(m);
|
|
6810
|
+
};
|
|
6811
|
+
const isBig = (n) => typeof n === 'bigint'; // is big integer
|
|
6812
|
+
const isStr = (s) => typeof s === 'string'; // is string
|
|
6813
|
+
const isBytes = (a) => a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
|
|
6814
|
+
/** assert is Uint8Array (of specific length) */
|
|
6815
|
+
const abytes = (a, l) => !isBytes(a) || (typeof l === 'number' && l > 0 && a.length !== l)
|
|
6816
|
+
? err('Uint8Array expected')
|
|
6817
|
+
: a;
|
|
6818
|
+
/** create Uint8Array */
|
|
6819
|
+
const u8n = (len) => new Uint8Array(len);
|
|
6820
|
+
const u8fr = (buf) => Uint8Array.from(buf);
|
|
6821
|
+
const padh = (n, pad) => n.toString(16).padStart(pad, '0');
|
|
6822
|
+
const bytesToHex = (b) => Array.from(abytes(b))
|
|
6823
|
+
.map((e) => padh(e, 2))
|
|
6824
|
+
.join('');
|
|
6825
|
+
const C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 }; // ASCII characters
|
|
6826
|
+
const _ch = (ch) => {
|
|
6827
|
+
if (ch >= C._0 && ch <= C._9)
|
|
6828
|
+
return ch - C._0; // '2' => 50-48
|
|
6829
|
+
if (ch >= C.A && ch <= C.F)
|
|
6830
|
+
return ch - (C.A - 10); // 'B' => 66-(65-10)
|
|
6831
|
+
if (ch >= C.a && ch <= C.f)
|
|
6832
|
+
return ch - (C.a - 10); // 'b' => 98-(97-10)
|
|
6833
|
+
return;
|
|
6834
|
+
};
|
|
6835
|
+
const hexToBytes = (hex) => {
|
|
6836
|
+
const e = 'hex invalid';
|
|
6837
|
+
if (!isStr(hex))
|
|
6838
|
+
return err(e);
|
|
6839
|
+
const hl = hex.length;
|
|
6840
|
+
const al = hl / 2;
|
|
6841
|
+
if (hl % 2)
|
|
6842
|
+
return err(e);
|
|
6843
|
+
const array = u8n(al);
|
|
6844
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
6845
|
+
// treat each char as ASCII
|
|
6846
|
+
const n1 = _ch(hex.charCodeAt(hi)); // parse first char, multiply it by 16
|
|
6847
|
+
const n2 = _ch(hex.charCodeAt(hi + 1)); // parse second char
|
|
6848
|
+
if (n1 === undefined || n2 === undefined)
|
|
6849
|
+
return err(e);
|
|
6850
|
+
array[ai] = n1 * 16 + n2; // example: 'A9' => 10*16 + 9
|
|
6851
|
+
}
|
|
6852
|
+
return array;
|
|
6853
|
+
};
|
|
6854
|
+
/** normalize hex or ui8a to ui8a */
|
|
6855
|
+
const toU8 = (a, len) => abytes(isStr(a) ? hexToBytes(a) : u8fr(abytes(a)), len);
|
|
6856
|
+
const big = BigInt;
|
|
6857
|
+
const arange = (n, min, max, msg = 'bad number: out of range') => isBig(n) && min <= n && n < max ? n : err(msg);
|
|
6858
|
+
/** modular division */
|
|
6859
|
+
const M = (a, b = P) => {
|
|
6860
|
+
const r = a % b;
|
|
6861
|
+
return r >= 0n ? r : b + r;
|
|
6862
|
+
};
|
|
6863
|
+
/** Modular inversion using eucledian GCD (non-CT). No negative exponent for now. */
|
|
6864
|
+
// prettier-ignore
|
|
6865
|
+
const invert = (num, md) => {
|
|
6866
|
+
if (num === 0n || md <= 0n)
|
|
6867
|
+
err('no inverse n=' + num + ' mod=' + md);
|
|
6868
|
+
let a = M(num, md), b = md, x = 0n, u = 1n;
|
|
6869
|
+
while (a !== 0n) {
|
|
6870
|
+
const q = b / a, r = b % a;
|
|
6871
|
+
const m = x - u * q;
|
|
6872
|
+
b = a, a = r, x = u, u = m;
|
|
6873
|
+
}
|
|
6874
|
+
return b === 1n ? M(x, md) : err('no inverse'); // b is gcd at this point
|
|
6875
|
+
};
|
|
6876
|
+
const apoint = (p) => (p instanceof Point ? p : err('Point expected'));
|
|
6877
|
+
// ## End of Helpers
|
|
6878
|
+
// -----------------
|
|
6879
|
+
const B256 = 2n ** 256n;
|
|
6880
|
+
/** Point in XYZT extended coordinates. */
|
|
6881
|
+
class Point {
|
|
6882
|
+
static BASE;
|
|
6883
|
+
static ZERO;
|
|
6884
|
+
ex;
|
|
6885
|
+
ey;
|
|
6886
|
+
ez;
|
|
6887
|
+
et;
|
|
6888
|
+
constructor(ex, ey, ez, et) {
|
|
6889
|
+
const max = B256;
|
|
6890
|
+
this.ex = arange(ex, 0n, max);
|
|
6891
|
+
this.ey = arange(ey, 0n, max);
|
|
6892
|
+
this.ez = arange(ez, 1n, max);
|
|
6893
|
+
this.et = arange(et, 0n, max);
|
|
6894
|
+
Object.freeze(this);
|
|
6895
|
+
}
|
|
6896
|
+
static fromAffine(p) {
|
|
6897
|
+
return new Point(p.x, p.y, 1n, M(p.x * p.y));
|
|
6898
|
+
}
|
|
6899
|
+
/** RFC8032 5.1.3: Uint8Array to Point. */
|
|
6900
|
+
static fromBytes(hex, zip215 = false) {
|
|
6901
|
+
const d = _d;
|
|
6902
|
+
// Copy array to not mess it up.
|
|
6903
|
+
const normed = u8fr(abytes(hex, L));
|
|
6904
|
+
// adjust first LE byte = last BE byte
|
|
6905
|
+
const lastByte = hex[31];
|
|
6906
|
+
normed[31] = lastByte & ~0x80;
|
|
6907
|
+
const y = bytesToNumLE(normed);
|
|
6908
|
+
// zip215=true: 0 <= y < 2^256
|
|
6909
|
+
// zip215=false, RFC8032: 0 <= y < 2^255-19
|
|
6910
|
+
const max = zip215 ? B256 : P;
|
|
6911
|
+
arange(y, 0n, max);
|
|
6912
|
+
const y2 = M(y * y); // y²
|
|
6913
|
+
const u = M(y2 - 1n); // u=y²-1
|
|
6914
|
+
const v = M(d * y2 + 1n); // v=dy²+1
|
|
6915
|
+
let { isValid, value: x } = uvRatio(u, v); // (uv³)(uv⁷)^(p-5)/8; square root
|
|
6916
|
+
if (!isValid)
|
|
6917
|
+
err('bad point: y not sqrt'); // not square root: bad point
|
|
6918
|
+
const isXOdd = (x & 1n) === 1n; // adjust sign of x coordinate
|
|
6919
|
+
const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit
|
|
6920
|
+
if (!zip215 && x === 0n && isLastByteOdd)
|
|
6921
|
+
err('bad point: x==0, isLastByteOdd'); // x=0, x_0=1
|
|
6922
|
+
if (isLastByteOdd !== isXOdd)
|
|
6923
|
+
x = M(-x);
|
|
6924
|
+
return new Point(x, y, 1n, M(x * y)); // Z=1, T=xy
|
|
6925
|
+
}
|
|
6926
|
+
/** Checks if the point is valid and on-curve. */
|
|
6927
|
+
assertValidity() {
|
|
6928
|
+
const a = _a;
|
|
6929
|
+
const d = _d;
|
|
6930
|
+
const p = this;
|
|
6931
|
+
if (p.is0())
|
|
6932
|
+
throw new Error('bad point: ZERO'); // TODO: optimize, with vars below?
|
|
6933
|
+
// Equation in affine coordinates: ax² + y² = 1 + dx²y²
|
|
6934
|
+
// Equation in projective coordinates (X/Z, Y/Z, Z): (aX² + Y²)Z² = Z⁴ + dX²Y²
|
|
6935
|
+
const { ex: X, ey: Y, ez: Z, et: T } = p;
|
|
6936
|
+
const X2 = M(X * X); // X²
|
|
6937
|
+
const Y2 = M(Y * Y); // Y²
|
|
6938
|
+
const Z2 = M(Z * Z); // Z²
|
|
6939
|
+
const Z4 = M(Z2 * Z2); // Z⁴
|
|
6940
|
+
const aX2 = M(X2 * a); // aX²
|
|
6941
|
+
const left = M(Z2 * M(aX2 + Y2)); // (aX² + Y²)Z²
|
|
6942
|
+
const right = M(Z4 + M(d * M(X2 * Y2))); // Z⁴ + dX²Y²
|
|
6943
|
+
if (left !== right)
|
|
6944
|
+
throw new Error('bad point: equation left != right (1)');
|
|
6945
|
+
// In Extended coordinates we also have T, which is x*y=T/Z: check X*Y == Z*T
|
|
6946
|
+
const XY = M(X * Y);
|
|
6947
|
+
const ZT = M(Z * T);
|
|
6948
|
+
if (XY !== ZT)
|
|
6949
|
+
throw new Error('bad point: equation left != right (2)');
|
|
6950
|
+
return this;
|
|
6951
|
+
}
|
|
6952
|
+
/** Equality check: compare points P&Q. */
|
|
6953
|
+
equals(other) {
|
|
6954
|
+
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
|
6955
|
+
const { ex: X2, ey: Y2, ez: Z2 } = apoint(other); // checks class equality
|
|
6956
|
+
const X1Z2 = M(X1 * Z2);
|
|
6957
|
+
const X2Z1 = M(X2 * Z1);
|
|
6958
|
+
const Y1Z2 = M(Y1 * Z2);
|
|
6959
|
+
const Y2Z1 = M(Y2 * Z1);
|
|
6960
|
+
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
|
6961
|
+
}
|
|
6962
|
+
is0() {
|
|
6963
|
+
return this.equals(I);
|
|
6964
|
+
}
|
|
6965
|
+
/** Flip point over y coordinate. */
|
|
6966
|
+
negate() {
|
|
6967
|
+
return new Point(M(-this.ex), this.ey, this.ez, M(-this.et));
|
|
6968
|
+
}
|
|
6969
|
+
/** Point doubling. Complete formula. Cost: `4M + 4S + 1*a + 6add + 1*2`. */
|
|
6970
|
+
double() {
|
|
6971
|
+
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
|
6972
|
+
const a = _a;
|
|
6973
|
+
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
|
|
6974
|
+
const A = M(X1 * X1);
|
|
6975
|
+
const B = M(Y1 * Y1);
|
|
6976
|
+
const C = M(2n * M(Z1 * Z1));
|
|
6977
|
+
const D = M(a * A);
|
|
6978
|
+
const x1y1 = X1 + Y1;
|
|
6979
|
+
const E = M(M(x1y1 * x1y1) - A - B);
|
|
6980
|
+
const G = D + B;
|
|
6981
|
+
const F = G - C;
|
|
6982
|
+
const H = D - B;
|
|
6983
|
+
const X3 = M(E * F);
|
|
6984
|
+
const Y3 = M(G * H);
|
|
6985
|
+
const T3 = M(E * H);
|
|
6986
|
+
const Z3 = M(F * G);
|
|
6987
|
+
return new Point(X3, Y3, Z3, T3);
|
|
6988
|
+
}
|
|
6989
|
+
/** Point addition. Complete formula. Cost: `8M + 1*k + 8add + 1*2`. */
|
|
6990
|
+
add(other) {
|
|
6991
|
+
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
|
|
6992
|
+
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = apoint(other); // doesn't check if other on-curve
|
|
6993
|
+
const a = _a;
|
|
6994
|
+
const d = _d;
|
|
6995
|
+
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3
|
|
6996
|
+
const A = M(X1 * X2);
|
|
6997
|
+
const B = M(Y1 * Y2);
|
|
6998
|
+
const C = M(T1 * d * T2);
|
|
6999
|
+
const D = M(Z1 * Z2);
|
|
7000
|
+
const E = M((X1 + Y1) * (X2 + Y2) - A - B);
|
|
7001
|
+
const F = M(D - C);
|
|
7002
|
+
const G = M(D + C);
|
|
7003
|
+
const H = M(B - a * A);
|
|
7004
|
+
const X3 = M(E * F);
|
|
7005
|
+
const Y3 = M(G * H);
|
|
7006
|
+
const T3 = M(E * H);
|
|
7007
|
+
const Z3 = M(F * G);
|
|
7008
|
+
return new Point(X3, Y3, Z3, T3);
|
|
7009
|
+
}
|
|
7010
|
+
/**
|
|
7011
|
+
* Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
|
|
7012
|
+
* Uses {@link wNAF} for base point.
|
|
7013
|
+
* Uses fake point to mitigate side-channel leakage.
|
|
7014
|
+
* @param n scalar by which point is multiplied
|
|
7015
|
+
* @param safe safe mode guards against timing attacks; unsafe mode is faster
|
|
7016
|
+
*/
|
|
7017
|
+
multiply(n, safe = true) {
|
|
7018
|
+
if (!safe && (n === 0n || this.is0()))
|
|
7019
|
+
return I;
|
|
7020
|
+
arange(n, 1n, N);
|
|
7021
|
+
if (n === 1n)
|
|
7022
|
+
return this;
|
|
7023
|
+
if (this.equals(G))
|
|
7024
|
+
return wNAF(n).p;
|
|
7025
|
+
// init result point & fake point
|
|
7026
|
+
let p = I;
|
|
7027
|
+
let f = G;
|
|
7028
|
+
for (let d = this; n > 0n; d = d.double(), n >>= 1n) {
|
|
7029
|
+
// if bit is present, add to point
|
|
7030
|
+
// if not present, add to fake, for timing safety
|
|
7031
|
+
if (n & 1n)
|
|
7032
|
+
p = p.add(d);
|
|
7033
|
+
else if (safe)
|
|
7034
|
+
f = f.add(d);
|
|
7035
|
+
}
|
|
7036
|
+
return p;
|
|
7037
|
+
}
|
|
7038
|
+
/** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
|
|
7039
|
+
toAffine() {
|
|
7040
|
+
const { ex: x, ey: y, ez: z } = this;
|
|
7041
|
+
// fast-paths for ZERO point OR Z=1
|
|
7042
|
+
if (this.equals(I))
|
|
7043
|
+
return { x: 0n, y: 1n };
|
|
7044
|
+
const iz = invert(z, P);
|
|
7045
|
+
// (Z * Z^-1) must be 1, otherwise bad math
|
|
7046
|
+
if (M(z * iz) !== 1n)
|
|
7047
|
+
err('invalid inverse');
|
|
7048
|
+
// x = X*Z^-1; y = Y*Z^-1
|
|
7049
|
+
return { x: M(x * iz), y: M(y * iz) };
|
|
7050
|
+
}
|
|
7051
|
+
toBytes() {
|
|
7052
|
+
const { x, y } = this.assertValidity().toAffine();
|
|
7053
|
+
const b = numTo32bLE(y);
|
|
7054
|
+
// store sign in first LE byte
|
|
7055
|
+
b[31] |= x & 1n ? 0x80 : 0;
|
|
7056
|
+
return b;
|
|
7057
|
+
}
|
|
7058
|
+
toHex() {
|
|
7059
|
+
return bytesToHex(this.toBytes());
|
|
7060
|
+
} // encode to hex string
|
|
7061
|
+
clearCofactor() {
|
|
7062
|
+
return this.multiply(big(h), false);
|
|
7063
|
+
}
|
|
7064
|
+
isSmallOrder() {
|
|
7065
|
+
return this.clearCofactor().is0();
|
|
7066
|
+
}
|
|
7067
|
+
isTorsionFree() {
|
|
7068
|
+
// multiply by big number CURVE.n
|
|
7069
|
+
let p = this.multiply(N / 2n, false).double(); // ensures the point is not "bad".
|
|
7070
|
+
if (N % 2n)
|
|
7071
|
+
p = p.add(this); // P^(N+1) // P*N == (P*(N/2))*2+P
|
|
7072
|
+
return p.is0();
|
|
7073
|
+
}
|
|
7074
|
+
static fromHex(hex, zip215) {
|
|
7075
|
+
return Point.fromBytes(toU8(hex), zip215);
|
|
7076
|
+
}
|
|
7077
|
+
get x() {
|
|
7078
|
+
return this.toAffine().x;
|
|
7079
|
+
}
|
|
7080
|
+
get y() {
|
|
7081
|
+
return this.toAffine().y;
|
|
7082
|
+
}
|
|
7083
|
+
toRawBytes() {
|
|
7084
|
+
return this.toBytes();
|
|
7085
|
+
}
|
|
7086
|
+
}
|
|
7087
|
+
/** Generator / base point */
|
|
7088
|
+
const G = new Point(Gx, Gy, 1n, M(Gx * Gy));
|
|
7089
|
+
/** Identity / zero point */
|
|
7090
|
+
const I = new Point(0n, 1n, 1n, 0n);
|
|
7091
|
+
// Static aliases
|
|
7092
|
+
Point.BASE = G;
|
|
7093
|
+
Point.ZERO = I;
|
|
7094
|
+
const numTo32bLE = (num) => hexToBytes(padh(arange(num, 0n, B256), L2)).reverse();
|
|
7095
|
+
const bytesToNumLE = (b) => big('0x' + bytesToHex(u8fr(abytes(b)).reverse()));
|
|
7096
|
+
const pow2 = (x, power) => {
|
|
7097
|
+
// pow2(x, 4) == x^(2^4)
|
|
7098
|
+
let r = x;
|
|
7099
|
+
while (power-- > 0n) {
|
|
7100
|
+
r *= r;
|
|
7101
|
+
r %= P;
|
|
7102
|
+
}
|
|
7103
|
+
return r;
|
|
7104
|
+
};
|
|
7105
|
+
// prettier-ignore
|
|
7106
|
+
const pow_2_252_3 = (x) => {
|
|
7107
|
+
const x2 = (x * x) % P; // x^2, bits 1
|
|
7108
|
+
const b2 = (x2 * x) % P; // x^3, bits 11
|
|
7109
|
+
const b4 = (pow2(b2, 2n) * b2) % P; // x^(2^4-1), bits 1111
|
|
7110
|
+
const b5 = (pow2(b4, 1n) * x) % P; // x^(2^5-1), bits 11111
|
|
7111
|
+
const b10 = (pow2(b5, 5n) * b5) % P; // x^(2^10)
|
|
7112
|
+
const b20 = (pow2(b10, 10n) * b10) % P; // x^(2^20)
|
|
7113
|
+
const b40 = (pow2(b20, 20n) * b20) % P; // x^(2^40)
|
|
7114
|
+
const b80 = (pow2(b40, 40n) * b40) % P; // x^(2^80)
|
|
7115
|
+
const b160 = (pow2(b80, 80n) * b80) % P; // x^(2^160)
|
|
7116
|
+
const b240 = (pow2(b160, 80n) * b80) % P; // x^(2^240)
|
|
7117
|
+
const b250 = (pow2(b240, 10n) * b10) % P; // x^(2^250)
|
|
7118
|
+
const pow_p_5_8 = (pow2(b250, 2n) * x) % P; // < To pow to (p+3)/8, multiply it by x.
|
|
7119
|
+
return { pow_p_5_8, b2 };
|
|
7120
|
+
};
|
|
7121
|
+
const RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n; // √-1
|
|
7122
|
+
// for sqrt comp
|
|
7123
|
+
// prettier-ignore
|
|
7124
|
+
const uvRatio = (u, v) => {
|
|
7125
|
+
const v3 = M(v * v * v); // v³
|
|
7126
|
+
const v7 = M(v3 * v3 * v); // v⁷
|
|
7127
|
+
const pow = pow_2_252_3(u * v7).pow_p_5_8; // (uv⁷)^(p-5)/8
|
|
7128
|
+
let x = M(u * v3 * pow); // (uv³)(uv⁷)^(p-5)/8
|
|
7129
|
+
const vx2 = M(v * x * x); // vx²
|
|
7130
|
+
const root1 = x; // First root candidate
|
|
7131
|
+
const root2 = M(x * RM1); // Second root candidate; RM1 is √-1
|
|
7132
|
+
const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root
|
|
7133
|
+
const useRoot2 = vx2 === M(-u); // If vx² = -u, set x <-- x * 2^((p-1)/4)
|
|
7134
|
+
const noRoot = vx2 === M(-u * RM1); // There is no valid root, vx² = -u√-1
|
|
7135
|
+
if (useRoot1)
|
|
7136
|
+
x = root1;
|
|
7137
|
+
if (useRoot2 || noRoot)
|
|
7138
|
+
x = root2; // We return root2 anyway, for const-time
|
|
7139
|
+
if ((M(x) & 1n) === 1n)
|
|
7140
|
+
x = M(-x); // edIsNegative
|
|
7141
|
+
return { isValid: useRoot1 || useRoot2, value: x };
|
|
7142
|
+
};
|
|
7143
|
+
// ## Precomputes
|
|
7144
|
+
// --------------
|
|
7145
|
+
const W = 8; // W is window size
|
|
7146
|
+
const scalarBits = 256;
|
|
7147
|
+
const pwindows = Math.ceil(scalarBits / W) + 1; // 33 for W=8
|
|
7148
|
+
const pwindowSize = 2 ** (W - 1); // 128 for W=8
|
|
7149
|
+
const precompute = () => {
|
|
7150
|
+
const points = [];
|
|
7151
|
+
let p = G;
|
|
7152
|
+
let b = p;
|
|
7153
|
+
for (let w = 0; w < pwindows; w++) {
|
|
7154
|
+
b = p;
|
|
7155
|
+
points.push(b);
|
|
7156
|
+
for (let i = 1; i < pwindowSize; i++) {
|
|
7157
|
+
b = b.add(p);
|
|
7158
|
+
points.push(b);
|
|
7159
|
+
} // i=1, bc we skip 0
|
|
7160
|
+
p = b.double();
|
|
7161
|
+
}
|
|
7162
|
+
return points;
|
|
7163
|
+
};
|
|
7164
|
+
let Gpows = undefined; // precomputes for base point G
|
|
7165
|
+
// const-time negate
|
|
7166
|
+
const ctneg = (cnd, p) => {
|
|
7167
|
+
const n = p.negate();
|
|
7168
|
+
return cnd ? n : p;
|
|
7169
|
+
};
|
|
7170
|
+
/**
|
|
7171
|
+
* Precomputes give 12x faster getPublicKey(), 10x sign(), 2x verify() by
|
|
7172
|
+
* caching multiples of G (base point). Cache is stored in 32MB of RAM.
|
|
7173
|
+
* Any time `G.multiply` is done, precomputes are used.
|
|
7174
|
+
* Not used for getSharedSecret, which instead multiplies random pubkey `P.multiply`.
|
|
7175
|
+
*
|
|
7176
|
+
* w-ary non-adjacent form (wNAF) precomputation method is 10% slower than windowed method,
|
|
7177
|
+
* but takes 2x less RAM. RAM reduction is possible by utilizing `.subtract`.
|
|
7178
|
+
*
|
|
7179
|
+
* !! Precomputes can be disabled by commenting-out call of the wNAF() inside Point#multiply().
|
|
7180
|
+
*/
|
|
7181
|
+
const wNAF = (n) => {
|
|
7182
|
+
const comp = Gpows || (Gpows = precompute());
|
|
7183
|
+
let p = I;
|
|
7184
|
+
let f = G; // f must be G, or could become I in the end
|
|
7185
|
+
const pow_2_w = 2 ** W; // 256 for W=8
|
|
7186
|
+
const maxNum = pow_2_w; // 256 for W=8
|
|
7187
|
+
const mask = big(pow_2_w - 1); // 255 for W=8 == mask 0b11111111
|
|
7188
|
+
const shiftBy = big(W); // 8 for W=8
|
|
7189
|
+
for (let w = 0; w < pwindows; w++) {
|
|
7190
|
+
let wbits = Number(n & mask); // extract W bits.
|
|
7191
|
+
n >>= shiftBy; // shift number by W bits.
|
|
7192
|
+
if (wbits > pwindowSize) {
|
|
7193
|
+
wbits -= maxNum;
|
|
7194
|
+
n += 1n;
|
|
7195
|
+
} // split if bits > max: +224 => 256-32
|
|
7196
|
+
const off = w * pwindowSize;
|
|
7197
|
+
const offF = off; // offsets, evaluate both
|
|
7198
|
+
const offP = off + Math.abs(wbits) - 1;
|
|
7199
|
+
const isEven = w % 2 !== 0; // conditions, evaluate both
|
|
7200
|
+
const isNeg = wbits < 0;
|
|
7201
|
+
if (wbits === 0) {
|
|
7202
|
+
// off == I: can't add it. Adding random offF instead.
|
|
7203
|
+
f = f.add(ctneg(isEven, comp[offF])); // bits are 0: add garbage to fake point
|
|
7204
|
+
}
|
|
7205
|
+
else {
|
|
7206
|
+
p = p.add(ctneg(isNeg, comp[offP])); // bits are 1: add to result point
|
|
7207
|
+
}
|
|
7208
|
+
}
|
|
7209
|
+
return { p, f }; // return both real and fake points for JIT
|
|
7210
|
+
};
|
|
7211
|
+
|
|
6765
7212
|
const DEFAULT_MAX_ENTRIES_PER_VIEW = 10000;
|
|
6766
7213
|
const DEFAULT_CONFIG = {
|
|
6767
7214
|
reconnectIntervals: [1000, 2000, 4000, 8000, 16000],
|
|
@@ -6944,6 +7391,11 @@ function isHostedAreteWebsocketUrl(websocketUrl) {
|
|
|
6944
7391
|
return false;
|
|
6945
7392
|
}
|
|
6946
7393
|
}
|
|
7394
|
+
/**
|
|
7395
|
+
* Historical sentinel accepted for back-compat: connecting with this URL is
|
|
7396
|
+
* treated the same as passing no WebSocket URL at all (HTTP-only mode).
|
|
7397
|
+
*/
|
|
7398
|
+
const DISABLED_WEBSOCKET_URL = 'ws://127.0.0.1/__arete_disabled__';
|
|
6947
7399
|
class ConnectionManager {
|
|
6948
7400
|
constructor(config) {
|
|
6949
7401
|
this.ws = null;
|
|
@@ -6959,11 +7411,11 @@ class ConnectionManager {
|
|
|
6959
7411
|
this.stateHandlers = new Set();
|
|
6960
7412
|
this.socketIssueHandlers = new Set();
|
|
6961
7413
|
this.reconnectForTokenRefresh = false;
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
this.websocketUrl =
|
|
6966
|
-
this.hostedAreteUrl = isHostedAreteWebsocketUrl(
|
|
7414
|
+
const websocketUrl = config.websocketUrl && config.websocketUrl !== DISABLED_WEBSOCKET_URL
|
|
7415
|
+
? config.websocketUrl
|
|
7416
|
+
: null;
|
|
7417
|
+
this.websocketUrl = websocketUrl;
|
|
7418
|
+
this.hostedAreteUrl = websocketUrl !== null && isHostedAreteWebsocketUrl(websocketUrl);
|
|
6967
7419
|
this.reconnectIntervals = config.reconnectIntervals ?? DEFAULT_CONFIG.reconnectIntervals;
|
|
6968
7420
|
this.maxReconnectAttempts =
|
|
6969
7421
|
config.maxReconnectAttempts ?? DEFAULT_CONFIG.maxReconnectAttempts;
|
|
@@ -7055,7 +7507,7 @@ class ConnectionManager {
|
|
|
7055
7507
|
}
|
|
7056
7508
|
createTokenEndpointRequestBody() {
|
|
7057
7509
|
return {
|
|
7058
|
-
websocket_url: this.websocketUrl,
|
|
7510
|
+
websocket_url: this.websocketUrl ?? '',
|
|
7059
7511
|
};
|
|
7060
7512
|
}
|
|
7061
7513
|
async fetchTokenFromEndpoint(tokenEndpoint) {
|
|
@@ -7209,14 +7661,21 @@ class ConnectionManager {
|
|
|
7209
7661
|
this.ws.close(1000, 'token refresh');
|
|
7210
7662
|
}
|
|
7211
7663
|
buildAuthUrl(token) {
|
|
7664
|
+
const websocketUrl = this.requireWebsocketUrl();
|
|
7212
7665
|
if (this.authConfig?.tokenTransport === 'bearer') {
|
|
7213
|
-
return
|
|
7666
|
+
return websocketUrl;
|
|
7214
7667
|
}
|
|
7215
7668
|
if (!token) {
|
|
7216
|
-
return
|
|
7669
|
+
return websocketUrl;
|
|
7670
|
+
}
|
|
7671
|
+
const separator = websocketUrl.includes('?') ? '&' : '?';
|
|
7672
|
+
return `${websocketUrl}${separator}${DEFAULT_QUERY_PARAMETER}=${encodeURIComponent(token)}`;
|
|
7673
|
+
}
|
|
7674
|
+
requireWebsocketUrl() {
|
|
7675
|
+
if (this.websocketUrl === null) {
|
|
7676
|
+
throw new AreteError('WebSocket transport is disabled (client was connected with transport: "http"); views and subscriptions are unavailable', 'WEBSOCKET_DISABLED');
|
|
7217
7677
|
}
|
|
7218
|
-
|
|
7219
|
-
return `${this.websocketUrl}${separator}${DEFAULT_QUERY_PARAMETER}=${encodeURIComponent(token)}`;
|
|
7678
|
+
return this.websocketUrl;
|
|
7220
7679
|
}
|
|
7221
7680
|
createWebSocket(url, token) {
|
|
7222
7681
|
if (this.authConfig?.tokenTransport === 'bearer') {
|
|
@@ -7236,6 +7695,12 @@ class ConnectionManager {
|
|
|
7236
7695
|
getState() {
|
|
7237
7696
|
return this.currentState;
|
|
7238
7697
|
}
|
|
7698
|
+
async getHttpAuthToken(forceRefresh = false) {
|
|
7699
|
+
return this.getOrRefreshToken(forceRefresh);
|
|
7700
|
+
}
|
|
7701
|
+
clearHttpAuthToken() {
|
|
7702
|
+
this.clearTokenState();
|
|
7703
|
+
}
|
|
7239
7704
|
onFrame(handler) {
|
|
7240
7705
|
this.frameHandlers.add(handler);
|
|
7241
7706
|
return () => {
|
|
@@ -7271,6 +7736,7 @@ class ConnectionManager {
|
|
|
7271
7736
|
return issue;
|
|
7272
7737
|
}
|
|
7273
7738
|
async connect() {
|
|
7739
|
+
this.requireWebsocketUrl();
|
|
7274
7740
|
if (this.ws?.readyState === WebSocket.OPEN ||
|
|
7275
7741
|
this.ws?.readyState === WebSocket.CONNECTING ||
|
|
7276
7742
|
this.currentState === 'connecting') {
|
|
@@ -7415,6 +7881,7 @@ class ConnectionManager {
|
|
|
7415
7881
|
}
|
|
7416
7882
|
}
|
|
7417
7883
|
subscribe(subscription) {
|
|
7884
|
+
this.requireWebsocketUrl();
|
|
7418
7885
|
const subKey = this.makeSubKey(subscription);
|
|
7419
7886
|
if (this.currentState === 'connected' && this.ws?.readyState === WebSocket.OPEN) {
|
|
7420
7887
|
if (this.activeSubscriptions.has(subKey)) {
|
|
@@ -7518,16 +7985,20 @@ class ConnectionManager {
|
|
|
7518
7985
|
}
|
|
7519
7986
|
}
|
|
7520
7987
|
|
|
7521
|
-
|
|
7988
|
+
const INTERNAL_SEQ_FIELD = '__seq';
|
|
7989
|
+
function isObject(item) {
|
|
7522
7990
|
return item !== null && typeof item === 'object' && !Array.isArray(item);
|
|
7523
7991
|
}
|
|
7524
|
-
function deepMergeWithAppend
|
|
7525
|
-
if (!isObject
|
|
7992
|
+
function deepMergeWithAppend(target, source, appendPaths, currentPath = '') {
|
|
7993
|
+
if (!isObject(target) || !isObject(source)) {
|
|
7526
7994
|
return source;
|
|
7527
7995
|
}
|
|
7528
7996
|
const result = { ...target };
|
|
7529
7997
|
for (const key in source) {
|
|
7530
7998
|
const sourceValue = source[key];
|
|
7999
|
+
if (sourceValue === undefined) {
|
|
8000
|
+
continue;
|
|
8001
|
+
}
|
|
7531
8002
|
const targetValue = result[key];
|
|
7532
8003
|
const fieldPath = currentPath ? `${currentPath}.${key}` : key;
|
|
7533
8004
|
if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
|
|
@@ -7538,8 +8009,8 @@ function deepMergeWithAppend$1(target, source, appendPaths, currentPath = '') {
|
|
|
7538
8009
|
result[key] = sourceValue;
|
|
7539
8010
|
}
|
|
7540
8011
|
}
|
|
7541
|
-
else if (isObject
|
|
7542
|
-
result[key] = deepMergeWithAppend
|
|
8012
|
+
else if (isObject(sourceValue) && isObject(targetValue)) {
|
|
8013
|
+
result[key] = deepMergeWithAppend(targetValue, sourceValue, appendPaths, fieldPath);
|
|
7543
8014
|
}
|
|
7544
8015
|
else {
|
|
7545
8016
|
result[key] = sourceValue;
|
|
@@ -7547,6 +8018,36 @@ function deepMergeWithAppend$1(target, source, appendPaths, currentPath = '') {
|
|
|
7547
8018
|
}
|
|
7548
8019
|
return result;
|
|
7549
8020
|
}
|
|
8021
|
+
function stripUndefinedProperties(value) {
|
|
8022
|
+
if (Array.isArray(value)) {
|
|
8023
|
+
return value.map(item => stripUndefinedProperties(item));
|
|
8024
|
+
}
|
|
8025
|
+
if (!isObject(value)) {
|
|
8026
|
+
return value;
|
|
8027
|
+
}
|
|
8028
|
+
const result = {};
|
|
8029
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
8030
|
+
if (nestedValue === undefined) {
|
|
8031
|
+
continue;
|
|
8032
|
+
}
|
|
8033
|
+
result[key] = stripUndefinedProperties(nestedValue);
|
|
8034
|
+
}
|
|
8035
|
+
return result;
|
|
8036
|
+
}
|
|
8037
|
+
function toCamelCaseSegment(value) {
|
|
8038
|
+
if (value === '_seq') {
|
|
8039
|
+
return INTERNAL_SEQ_FIELD;
|
|
8040
|
+
}
|
|
8041
|
+
const pascal = value
|
|
8042
|
+
.split(/[_.-]/)
|
|
8043
|
+
.filter(segment => segment.length > 0)
|
|
8044
|
+
.map(segment => segment[0].toUpperCase() + segment.slice(1))
|
|
8045
|
+
.join('');
|
|
8046
|
+
if (pascal.length === 0) {
|
|
8047
|
+
return value;
|
|
8048
|
+
}
|
|
8049
|
+
return pascal[0].toLowerCase() + pascal.slice(1);
|
|
8050
|
+
}
|
|
7550
8051
|
class FrameProcessor {
|
|
7551
8052
|
constructor(storage, config = {}) {
|
|
7552
8053
|
this.pendingUpdates = [];
|
|
@@ -7558,9 +8059,10 @@ class FrameProcessor {
|
|
|
7558
8059
|
: config.maxEntriesPerView;
|
|
7559
8060
|
this.flushIntervalMs = config.flushIntervalMs ?? 0;
|
|
7560
8061
|
this.schemas = config.schemas;
|
|
8062
|
+
this.patchSchemas = config.patchSchemas;
|
|
7561
8063
|
}
|
|
7562
|
-
getSchema(viewPath) {
|
|
7563
|
-
const schemas = this.schemas;
|
|
8064
|
+
getSchema(viewPath, patch = false) {
|
|
8065
|
+
const schemas = patch ? this.patchSchemas : this.schemas;
|
|
7564
8066
|
if (!schemas)
|
|
7565
8067
|
return null;
|
|
7566
8068
|
const entityName = viewPath.split('/')[0];
|
|
@@ -7569,19 +8071,86 @@ class FrameProcessor {
|
|
|
7569
8071
|
const entityKey = entityName;
|
|
7570
8072
|
return schemas[entityKey] ?? null;
|
|
7571
8073
|
}
|
|
7572
|
-
|
|
7573
|
-
const schema = this.getSchema(viewPath)
|
|
8074
|
+
normalizeEntity(viewPath, data, patch = false) {
|
|
8075
|
+
const schema = this.getSchema(viewPath, patch)
|
|
8076
|
+
?? (!patch ? null : this.getSchema(viewPath));
|
|
7574
8077
|
if (!schema)
|
|
7575
|
-
return
|
|
8078
|
+
return data;
|
|
7576
8079
|
const result = schema.safeParse(data);
|
|
7577
8080
|
if (!result.success) {
|
|
7578
8081
|
console.warn('[Arete] Frame validation failed:', {
|
|
7579
8082
|
view: viewPath,
|
|
7580
8083
|
error: result.error,
|
|
7581
8084
|
});
|
|
7582
|
-
return
|
|
8085
|
+
return null;
|
|
7583
8086
|
}
|
|
7584
|
-
return
|
|
8087
|
+
return stripUndefinedProperties(result.data);
|
|
8088
|
+
}
|
|
8089
|
+
hasSchema(viewPath) {
|
|
8090
|
+
return this.getSchema(viewPath) !== null;
|
|
8091
|
+
}
|
|
8092
|
+
normalizePathSegment(viewPath, segment) {
|
|
8093
|
+
if (!this.hasSchema(viewPath)) {
|
|
8094
|
+
return segment;
|
|
8095
|
+
}
|
|
8096
|
+
return toCamelCaseSegment(segment);
|
|
8097
|
+
}
|
|
8098
|
+
normalizePath(viewPath, path) {
|
|
8099
|
+
if (!this.hasSchema(viewPath)) {
|
|
8100
|
+
return path;
|
|
8101
|
+
}
|
|
8102
|
+
return path
|
|
8103
|
+
.split('.')
|
|
8104
|
+
.filter(segment => segment.length > 0)
|
|
8105
|
+
.map(segment => this.normalizePathSegment(viewPath, segment))
|
|
8106
|
+
.join('.');
|
|
8107
|
+
}
|
|
8108
|
+
normalizeSortConfig(viewPath, sort) {
|
|
8109
|
+
if (!this.hasSchema(viewPath)) {
|
|
8110
|
+
return sort;
|
|
8111
|
+
}
|
|
8112
|
+
return {
|
|
8113
|
+
...sort,
|
|
8114
|
+
field: sort.field.map(segment => this.normalizePathSegment(viewPath, segment)),
|
|
8115
|
+
};
|
|
8116
|
+
}
|
|
8117
|
+
normalizeAppendPaths(viewPath, append) {
|
|
8118
|
+
if (!append || append.length === 0 || !this.hasSchema(viewPath)) {
|
|
8119
|
+
return append ?? [];
|
|
8120
|
+
}
|
|
8121
|
+
return append.map(path => this.normalizePath(viewPath, path));
|
|
8122
|
+
}
|
|
8123
|
+
extractSeq(data) {
|
|
8124
|
+
if (!isObject(data)) {
|
|
8125
|
+
return undefined;
|
|
8126
|
+
}
|
|
8127
|
+
const seq = data._seq;
|
|
8128
|
+
if (typeof seq === 'string') {
|
|
8129
|
+
return seq;
|
|
8130
|
+
}
|
|
8131
|
+
if (typeof seq === 'number' && Number.isFinite(seq)) {
|
|
8132
|
+
return String(seq);
|
|
8133
|
+
}
|
|
8134
|
+
return undefined;
|
|
8135
|
+
}
|
|
8136
|
+
getInternalSeq(data) {
|
|
8137
|
+
if (!isObject(data)) {
|
|
8138
|
+
return undefined;
|
|
8139
|
+
}
|
|
8140
|
+
const seq = data[INTERNAL_SEQ_FIELD];
|
|
8141
|
+
return typeof seq === 'string' ? seq : undefined;
|
|
8142
|
+
}
|
|
8143
|
+
attachInternalSeq(viewPath, data, seq) {
|
|
8144
|
+
if (!seq || !this.hasSchema(viewPath) || !isObject(data)) {
|
|
8145
|
+
return data;
|
|
8146
|
+
}
|
|
8147
|
+
Object.defineProperty(data, INTERNAL_SEQ_FIELD, {
|
|
8148
|
+
value: seq,
|
|
8149
|
+
enumerable: false,
|
|
8150
|
+
configurable: true,
|
|
8151
|
+
writable: true,
|
|
8152
|
+
});
|
|
8153
|
+
return data;
|
|
7585
8154
|
}
|
|
7586
8155
|
handleFrame(frame) {
|
|
7587
8156
|
if (this.flushIntervalMs === 0) {
|
|
@@ -7667,7 +8236,9 @@ class FrameProcessor {
|
|
|
7667
8236
|
}
|
|
7668
8237
|
handleSubscribedFrame(frame) {
|
|
7669
8238
|
if (this.storage.setViewConfig && frame.sort) {
|
|
7670
|
-
this.storage.setViewConfig(frame.view, {
|
|
8239
|
+
this.storage.setViewConfig(frame.view, {
|
|
8240
|
+
sort: this.normalizeSortConfig(frame.view, frame.sort),
|
|
8241
|
+
});
|
|
7671
8242
|
}
|
|
7672
8243
|
}
|
|
7673
8244
|
handleSnapshotFrame(frame) {
|
|
@@ -7677,17 +8248,19 @@ class FrameProcessor {
|
|
|
7677
8248
|
handleSnapshotFrameWithoutEnforce(frame) {
|
|
7678
8249
|
const viewPath = frame.entity;
|
|
7679
8250
|
for (const entity of frame.data) {
|
|
7680
|
-
|
|
8251
|
+
const normalized = this.normalizeEntity(viewPath, entity.data);
|
|
8252
|
+
if (normalized === null) {
|
|
7681
8253
|
continue;
|
|
7682
8254
|
}
|
|
8255
|
+
const nextValue = this.attachInternalSeq(viewPath, normalized, this.extractSeq(entity.data));
|
|
7683
8256
|
const previousValue = this.storage.get(viewPath, entity.key);
|
|
7684
|
-
this.storage.set(viewPath, entity.key,
|
|
8257
|
+
this.storage.set(viewPath, entity.key, nextValue);
|
|
7685
8258
|
this.storage.notifyUpdate(viewPath, entity.key, {
|
|
7686
8259
|
type: 'upsert',
|
|
7687
8260
|
key: entity.key,
|
|
7688
|
-
data:
|
|
8261
|
+
data: nextValue,
|
|
7689
8262
|
});
|
|
7690
|
-
this.emitRichUpdate(viewPath, entity.key, previousValue,
|
|
8263
|
+
this.emitRichUpdate(viewPath, entity.key, previousValue, nextValue, 'upsert');
|
|
7691
8264
|
}
|
|
7692
8265
|
}
|
|
7693
8266
|
handleEntityFrame(frame) {
|
|
@@ -7700,33 +8273,39 @@ class FrameProcessor {
|
|
|
7700
8273
|
switch (frame.op) {
|
|
7701
8274
|
case 'create':
|
|
7702
8275
|
case 'upsert':
|
|
7703
|
-
|
|
8276
|
+
{
|
|
8277
|
+
const normalized = this.normalizeEntity(viewPath, frame.data);
|
|
8278
|
+
if (normalized === null) {
|
|
8279
|
+
break;
|
|
8280
|
+
}
|
|
8281
|
+
const nextValue = this.attachInternalSeq(viewPath, normalized, frame.seq ?? this.extractSeq(frame.data));
|
|
8282
|
+
this.storage.set(viewPath, frame.key, nextValue);
|
|
8283
|
+
this.storage.notifyUpdate(viewPath, frame.key, {
|
|
8284
|
+
type: 'upsert',
|
|
8285
|
+
key: frame.key,
|
|
8286
|
+
data: nextValue,
|
|
8287
|
+
});
|
|
8288
|
+
this.emitRichUpdate(viewPath, frame.key, previousValue, nextValue, frame.op);
|
|
7704
8289
|
break;
|
|
7705
8290
|
}
|
|
7706
|
-
this.storage.set(viewPath, frame.key, frame.data);
|
|
7707
|
-
this.storage.notifyUpdate(viewPath, frame.key, {
|
|
7708
|
-
type: 'upsert',
|
|
7709
|
-
key: frame.key,
|
|
7710
|
-
data: frame.data,
|
|
7711
|
-
});
|
|
7712
|
-
this.emitRichUpdate(viewPath, frame.key, previousValue, frame.data, frame.op);
|
|
7713
|
-
break;
|
|
7714
8291
|
case 'patch': {
|
|
7715
8292
|
const existing = this.storage.get(viewPath, frame.key);
|
|
7716
|
-
const
|
|
7717
|
-
|
|
7718
|
-
? deepMergeWithAppend$1(existing, frame.data, appendPaths)
|
|
7719
|
-
: frame.data;
|
|
7720
|
-
if (!this.validateEntity(viewPath, merged)) {
|
|
8293
|
+
const normalizedPatch = this.normalizeEntity(viewPath, frame.data, true);
|
|
8294
|
+
if (normalizedPatch === null) {
|
|
7721
8295
|
break;
|
|
7722
8296
|
}
|
|
7723
|
-
this.
|
|
8297
|
+
const appendPaths = this.normalizeAppendPaths(viewPath, frame.append);
|
|
8298
|
+
const merged = existing
|
|
8299
|
+
? deepMergeWithAppend(existing, normalizedPatch, appendPaths)
|
|
8300
|
+
: normalizedPatch;
|
|
8301
|
+
const nextValue = this.attachInternalSeq(viewPath, merged, frame.seq ?? this.extractSeq(frame.data) ?? this.getInternalSeq(existing));
|
|
8302
|
+
this.storage.set(viewPath, frame.key, nextValue);
|
|
7724
8303
|
this.storage.notifyUpdate(viewPath, frame.key, {
|
|
7725
8304
|
type: 'patch',
|
|
7726
8305
|
key: frame.key,
|
|
7727
|
-
data:
|
|
8306
|
+
data: normalizedPatch,
|
|
7728
8307
|
});
|
|
7729
|
-
this.emitRichUpdate(viewPath, frame.key, previousValue,
|
|
8308
|
+
this.emitRichUpdate(viewPath, frame.key, previousValue, nextValue, 'patch', normalizedPatch);
|
|
7730
8309
|
break;
|
|
7731
8310
|
}
|
|
7732
8311
|
case 'delete':
|
|
@@ -7759,7 +8338,7 @@ class FrameProcessor {
|
|
|
7759
8338
|
}
|
|
7760
8339
|
}
|
|
7761
8340
|
|
|
7762
|
-
|
|
8341
|
+
class ViewData {
|
|
7763
8342
|
constructor() {
|
|
7764
8343
|
this.entities = new Map();
|
|
7765
8344
|
this.accessOrder = [];
|
|
@@ -7813,7 +8392,7 @@ let ViewData$1 = class ViewData {
|
|
|
7813
8392
|
this.entities.clear();
|
|
7814
8393
|
this.accessOrder = [];
|
|
7815
8394
|
}
|
|
7816
|
-
}
|
|
8395
|
+
}
|
|
7817
8396
|
class MemoryAdapter {
|
|
7818
8397
|
constructor(_config = {}) {
|
|
7819
8398
|
this.views = new Map();
|
|
@@ -7861,7 +8440,7 @@ class MemoryAdapter {
|
|
|
7861
8440
|
set(viewPath, key, data) {
|
|
7862
8441
|
let view = this.views.get(viewPath);
|
|
7863
8442
|
if (!view) {
|
|
7864
|
-
view = new ViewData
|
|
8443
|
+
view = new ViewData();
|
|
7865
8444
|
this.views.set(viewPath, view);
|
|
7866
8445
|
}
|
|
7867
8446
|
view.set(key, data);
|
|
@@ -7901,7 +8480,7 @@ class MemoryAdapter {
|
|
|
7901
8480
|
}
|
|
7902
8481
|
}
|
|
7903
8482
|
|
|
7904
|
-
function getNestedValue
|
|
8483
|
+
function getNestedValue(obj, path) {
|
|
7905
8484
|
let current = obj;
|
|
7906
8485
|
for (const segment of path) {
|
|
7907
8486
|
if (current === null || current === undefined)
|
|
@@ -7912,16 +8491,25 @@ function getNestedValue$1(obj, path) {
|
|
|
7912
8491
|
}
|
|
7913
8492
|
return current;
|
|
7914
8493
|
}
|
|
7915
|
-
function compareSortValues
|
|
8494
|
+
function compareSortValues(a, b) {
|
|
7916
8495
|
if (a === b)
|
|
7917
8496
|
return 0;
|
|
7918
8497
|
if (a === undefined || a === null)
|
|
7919
8498
|
return -1;
|
|
7920
8499
|
if (b === undefined || b === null)
|
|
7921
8500
|
return 1;
|
|
8501
|
+
if (typeof a === 'bigint' && typeof b === 'bigint') {
|
|
8502
|
+
return a < b ? -1 : 1;
|
|
8503
|
+
}
|
|
7922
8504
|
if (typeof a === 'number' && typeof b === 'number') {
|
|
7923
8505
|
return a - b;
|
|
7924
8506
|
}
|
|
8507
|
+
if (typeof a === 'bigint' && typeof b === 'number' && Number.isInteger(b)) {
|
|
8508
|
+
return a < BigInt(b) ? -1 : 1;
|
|
8509
|
+
}
|
|
8510
|
+
if (typeof a === 'number' && typeof b === 'bigint' && Number.isInteger(a)) {
|
|
8511
|
+
return BigInt(a) < b ? -1 : 1;
|
|
8512
|
+
}
|
|
7925
8513
|
if (typeof a === 'string' && typeof b === 'string') {
|
|
7926
8514
|
return a.localeCompare(b);
|
|
7927
8515
|
}
|
|
@@ -8048,7 +8636,7 @@ class SortedStorageDecorator {
|
|
|
8048
8636
|
sortedKeys.splice(insertIdx, 0, key);
|
|
8049
8637
|
}
|
|
8050
8638
|
binarySearchInsertPosition(viewPath, sortedKeys, sortConfig, newKey, newValue) {
|
|
8051
|
-
const newSortValue = getNestedValue
|
|
8639
|
+
const newSortValue = getNestedValue(newValue, sortConfig.field);
|
|
8052
8640
|
const isDesc = sortConfig.order === 'desc';
|
|
8053
8641
|
let low = 0;
|
|
8054
8642
|
let high = sortedKeys.length;
|
|
@@ -8056,8 +8644,8 @@ class SortedStorageDecorator {
|
|
|
8056
8644
|
const mid = Math.floor((low + high) / 2);
|
|
8057
8645
|
const midKey = sortedKeys[mid];
|
|
8058
8646
|
const midEntity = this.inner.get(viewPath, midKey);
|
|
8059
|
-
const midValue = getNestedValue
|
|
8060
|
-
let cmp = compareSortValues
|
|
8647
|
+
const midValue = getNestedValue(midEntity, sortConfig.field);
|
|
8648
|
+
let cmp = compareSortValues(newSortValue, midValue);
|
|
8061
8649
|
if (isDesc)
|
|
8062
8650
|
cmp = -cmp;
|
|
8063
8651
|
if (cmp === 0) {
|
|
@@ -8079,9 +8667,9 @@ class SortedStorageDecorator {
|
|
|
8079
8667
|
const isDesc = sortConfig.order === 'desc';
|
|
8080
8668
|
const entries = allKeys.map(k => [k, this.inner.get(viewPath, k)]);
|
|
8081
8669
|
entries.sort((a, b) => {
|
|
8082
|
-
const aValue = getNestedValue
|
|
8083
|
-
const bValue = getNestedValue
|
|
8084
|
-
let cmp = compareSortValues
|
|
8670
|
+
const aValue = getNestedValue(a[1], sortConfig.field);
|
|
8671
|
+
const bValue = getNestedValue(b[1], sortConfig.field);
|
|
8672
|
+
let cmp = compareSortValues(aValue, bValue);
|
|
8085
8673
|
if (isDesc)
|
|
8086
8674
|
cmp = -cmp;
|
|
8087
8675
|
if (cmp === 0) {
|
|
@@ -8411,6 +8999,137 @@ function createTypedViews(stack, storage, subscriptionRegistry) {
|
|
|
8411
8999
|
return views;
|
|
8412
9000
|
}
|
|
8413
9001
|
|
|
9002
|
+
class ReadRequestError extends Error {
|
|
9003
|
+
constructor(input) {
|
|
9004
|
+
super(`Read request to '${input.path}' failed (${input.status}): ${input.body}`);
|
|
9005
|
+
this.name = 'ReadRequestError';
|
|
9006
|
+
this.status = input.status;
|
|
9007
|
+
this.path = input.path;
|
|
9008
|
+
this.body = input.body;
|
|
9009
|
+
this.serverErrorCode = input.serverErrorCode;
|
|
9010
|
+
}
|
|
9011
|
+
}
|
|
9012
|
+
function getServerErrorCode(response, body) {
|
|
9013
|
+
const headerCode = response.headers.get('X-Error-Code');
|
|
9014
|
+
if (headerCode) {
|
|
9015
|
+
return headerCode;
|
|
9016
|
+
}
|
|
9017
|
+
try {
|
|
9018
|
+
const parsed = JSON.parse(body);
|
|
9019
|
+
return typeof parsed.code === 'string' ? parsed.code : undefined;
|
|
9020
|
+
}
|
|
9021
|
+
catch {
|
|
9022
|
+
return undefined;
|
|
9023
|
+
}
|
|
9024
|
+
}
|
|
9025
|
+
async function parseReadResponse(response, path) {
|
|
9026
|
+
if (!response.ok) {
|
|
9027
|
+
const body = await response.text();
|
|
9028
|
+
throw new ReadRequestError({
|
|
9029
|
+
status: response.status,
|
|
9030
|
+
path,
|
|
9031
|
+
body,
|
|
9032
|
+
serverErrorCode: getServerErrorCode(response, body),
|
|
9033
|
+
});
|
|
9034
|
+
}
|
|
9035
|
+
return response.json();
|
|
9036
|
+
}
|
|
9037
|
+
|
|
9038
|
+
function joinUrl(baseUrl, path) {
|
|
9039
|
+
return `${baseUrl.replace(/\/$/, '')}${path.startsWith('/') ? path : `/${path}`}`;
|
|
9040
|
+
}
|
|
9041
|
+
function decodeBase64(encoded) {
|
|
9042
|
+
if (typeof atob === 'function') {
|
|
9043
|
+
const binary = atob(encoded);
|
|
9044
|
+
const bytes = new Uint8Array(binary.length);
|
|
9045
|
+
for (let i = 0; i < binary.length; i++) {
|
|
9046
|
+
bytes[i] = binary.charCodeAt(i);
|
|
9047
|
+
}
|
|
9048
|
+
return bytes;
|
|
9049
|
+
}
|
|
9050
|
+
const bufferCtor = globalThis.Buffer;
|
|
9051
|
+
if (bufferCtor) {
|
|
9052
|
+
return new Uint8Array(bufferCtor.from(encoded, 'base64'));
|
|
9053
|
+
}
|
|
9054
|
+
throw new Error('No base64 decoder available in this environment');
|
|
9055
|
+
}
|
|
9056
|
+
function deriveHttpEndpoint(wsUrl) {
|
|
9057
|
+
try {
|
|
9058
|
+
const parsed = new URL(wsUrl);
|
|
9059
|
+
if (parsed.protocol === 'ws:') {
|
|
9060
|
+
parsed.protocol = 'http:';
|
|
9061
|
+
}
|
|
9062
|
+
else if (parsed.protocol === 'wss:') {
|
|
9063
|
+
parsed.protocol = 'https:';
|
|
9064
|
+
}
|
|
9065
|
+
return parsed.toString().replace(/\/$/, '');
|
|
9066
|
+
}
|
|
9067
|
+
catch {
|
|
9068
|
+
return wsUrl.replace(/^wss?:/i, (protocol) => protocol.toLowerCase() === 'wss:' ? 'https:' : 'http:');
|
|
9069
|
+
}
|
|
9070
|
+
}
|
|
9071
|
+
function createChainClient(httpBaseUrl, fetchImpl) {
|
|
9072
|
+
return {
|
|
9073
|
+
async exists(address) {
|
|
9074
|
+
const path = `/chain/exists/${encodeURIComponent(address)}`;
|
|
9075
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
9076
|
+
const body = await parseReadResponse(response, path);
|
|
9077
|
+
return body.exists;
|
|
9078
|
+
},
|
|
9079
|
+
async lamports(address) {
|
|
9080
|
+
const path = `/chain/lamports/${encodeURIComponent(address)}`;
|
|
9081
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
9082
|
+
const body = await parseReadResponse(response, path);
|
|
9083
|
+
return body.lamports;
|
|
9084
|
+
},
|
|
9085
|
+
async minimumBalanceForRentExemption(space) {
|
|
9086
|
+
const path = `/chain/rent-exemption/${encodeURIComponent(String(space))}`;
|
|
9087
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
9088
|
+
const body = await parseReadResponse(response, path);
|
|
9089
|
+
return body.lamports;
|
|
9090
|
+
},
|
|
9091
|
+
async clock() {
|
|
9092
|
+
const path = '/chain/clock';
|
|
9093
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
9094
|
+
return parseReadResponse(response, path);
|
|
9095
|
+
},
|
|
9096
|
+
async account(address) {
|
|
9097
|
+
const path = `/chain/accounts/${encodeURIComponent(address)}`;
|
|
9098
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
9099
|
+
const body = await parseReadResponse(response, path);
|
|
9100
|
+
if (!body) {
|
|
9101
|
+
return null;
|
|
9102
|
+
}
|
|
9103
|
+
return {
|
|
9104
|
+
address: body.address,
|
|
9105
|
+
ownerProgram: body.ownerProgram,
|
|
9106
|
+
lamports: body.lamports,
|
|
9107
|
+
executable: body.executable,
|
|
9108
|
+
data: decodeBase64(body.data),
|
|
9109
|
+
};
|
|
9110
|
+
},
|
|
9111
|
+
async mint(address) {
|
|
9112
|
+
const path = `/chain/mints/${encodeURIComponent(address)}`;
|
|
9113
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
9114
|
+
return parseReadResponse(response, path);
|
|
9115
|
+
},
|
|
9116
|
+
async tokenAccount(address) {
|
|
9117
|
+
const path = `/chain/token-accounts/${encodeURIComponent(address)}`;
|
|
9118
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
9119
|
+
return parseReadResponse(response, path);
|
|
9120
|
+
},
|
|
9121
|
+
async balance(input) {
|
|
9122
|
+
const path = '/chain/balances';
|
|
9123
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path), {
|
|
9124
|
+
method: 'POST',
|
|
9125
|
+
headers: { 'content-type': 'application/json' },
|
|
9126
|
+
body: JSON.stringify(input),
|
|
9127
|
+
});
|
|
9128
|
+
return parseReadResponse(response, path);
|
|
9129
|
+
},
|
|
9130
|
+
};
|
|
9131
|
+
}
|
|
9132
|
+
|
|
8414
9133
|
/**
|
|
8415
9134
|
* PDA (Program Derived Address) derivation utilities.
|
|
8416
9135
|
*
|
|
@@ -8425,7 +9144,10 @@ function decodeBase58(str) {
|
|
|
8425
9144
|
if (str.length === 0) {
|
|
8426
9145
|
return new Uint8Array(0);
|
|
8427
9146
|
}
|
|
8428
|
-
|
|
9147
|
+
// Big-endian byte accumulator (stored little-endian here, reversed at the
|
|
9148
|
+
// end). Must start empty: a leading `[0]` produces a spurious extra byte for
|
|
9149
|
+
// all-zero values such as the System Program ("111...1").
|
|
9150
|
+
const bytes = [];
|
|
8429
9151
|
for (const char of str) {
|
|
8430
9152
|
const value = BASE58_ALPHABET.indexOf(char);
|
|
8431
9153
|
if (value === -1) {
|
|
@@ -8457,7 +9179,9 @@ function encodeBase58(bytes) {
|
|
|
8457
9179
|
if (bytes.length === 0) {
|
|
8458
9180
|
return '';
|
|
8459
9181
|
}
|
|
8460
|
-
|
|
9182
|
+
// Must start empty for the same reason as `decodeBase58`: a leading `[0]`
|
|
9183
|
+
// yields an extra '1' character when encoding all-zero inputs.
|
|
9184
|
+
const digits = [];
|
|
8461
9185
|
for (const byte of bytes) {
|
|
8462
9186
|
let carry = byte;
|
|
8463
9187
|
for (let i = 0; i < digits.length; i++) {
|
|
@@ -8478,25 +9202,125 @@ function encodeBase58(bytes) {
|
|
|
8478
9202
|
}
|
|
8479
9203
|
return digits.reverse().map(d => BASE58_ALPHABET[d]).join('');
|
|
8480
9204
|
}
|
|
9205
|
+
// SHA-256 round constants (first 32 bits of the fractional parts of the cube
|
|
9206
|
+
// roots of the first 64 primes).
|
|
9207
|
+
const SHA256_K = new Uint32Array([
|
|
9208
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
9209
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
9210
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
9211
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
9212
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
9213
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
9214
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
9215
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
9216
|
+
]);
|
|
9217
|
+
/**
|
|
9218
|
+
* Dependency-free, synchronous SHA-256.
|
|
9219
|
+
*
|
|
9220
|
+
* Used so PDA derivation works identically in browsers, Node (both CJS and
|
|
9221
|
+
* ESM), and bundlers without relying on `require('crypto')` or async WebCrypto.
|
|
9222
|
+
*/
|
|
9223
|
+
function sha256Pure(data) {
|
|
9224
|
+
const h = new Uint32Array([
|
|
9225
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
9226
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
9227
|
+
]);
|
|
9228
|
+
const bitLen = data.length * 8;
|
|
9229
|
+
// Pad: append 0x80, then zeros, until length ≡ 56 (mod 64), then 8-byte length.
|
|
9230
|
+
const paddedLen = ((data.length + 8) >> 6) * 64 + 64;
|
|
9231
|
+
const msg = new Uint8Array(paddedLen);
|
|
9232
|
+
msg.set(data);
|
|
9233
|
+
msg[data.length] = 0x80;
|
|
9234
|
+
// 64-bit big-endian bit length (high 32 bits assumed 0 for our seed sizes).
|
|
9235
|
+
const dv = new DataView(msg.buffer);
|
|
9236
|
+
dv.setUint32(paddedLen - 4, bitLen >>> 0, false);
|
|
9237
|
+
dv.setUint32(paddedLen - 8, Math.floor(bitLen / 0x100000000), false);
|
|
9238
|
+
const w = new Uint32Array(64);
|
|
9239
|
+
for (let offset = 0; offset < paddedLen; offset += 64) {
|
|
9240
|
+
for (let i = 0; i < 16; i++) {
|
|
9241
|
+
w[i] = dv.getUint32(offset + i * 4, false);
|
|
9242
|
+
}
|
|
9243
|
+
for (let i = 16; i < 64; i++) {
|
|
9244
|
+
const w15 = w[i - 15];
|
|
9245
|
+
const w2 = w[i - 2];
|
|
9246
|
+
const s0 = ((w15 >>> 7) | (w15 << 25)) ^ ((w15 >>> 18) | (w15 << 14)) ^ (w15 >>> 3);
|
|
9247
|
+
const s1 = ((w2 >>> 17) | (w2 << 15)) ^ ((w2 >>> 19) | (w2 << 13)) ^ (w2 >>> 10);
|
|
9248
|
+
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
|
|
9249
|
+
}
|
|
9250
|
+
let a = h[0], b = h[1], c = h[2], d = h[3];
|
|
9251
|
+
let e = h[4], f = h[5], g = h[6], hh = h[7];
|
|
9252
|
+
for (let i = 0; i < 64; i++) {
|
|
9253
|
+
const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
|
|
9254
|
+
const ch = (e & f) ^ (~e & g);
|
|
9255
|
+
const t1 = (hh + S1 + ch + SHA256_K[i] + w[i]) >>> 0;
|
|
9256
|
+
const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
|
|
9257
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
9258
|
+
const t2 = (S0 + maj) >>> 0;
|
|
9259
|
+
hh = g;
|
|
9260
|
+
g = f;
|
|
9261
|
+
f = e;
|
|
9262
|
+
e = (d + t1) >>> 0;
|
|
9263
|
+
d = c;
|
|
9264
|
+
c = b;
|
|
9265
|
+
b = a;
|
|
9266
|
+
a = (t1 + t2) >>> 0;
|
|
9267
|
+
}
|
|
9268
|
+
h[0] = (h[0] + a) >>> 0;
|
|
9269
|
+
h[1] = (h[1] + b) >>> 0;
|
|
9270
|
+
h[2] = (h[2] + c) >>> 0;
|
|
9271
|
+
h[3] = (h[3] + d) >>> 0;
|
|
9272
|
+
h[4] = (h[4] + e) >>> 0;
|
|
9273
|
+
h[5] = (h[5] + f) >>> 0;
|
|
9274
|
+
h[6] = (h[6] + g) >>> 0;
|
|
9275
|
+
h[7] = (h[7] + hh) >>> 0;
|
|
9276
|
+
}
|
|
9277
|
+
const out = new Uint8Array(32);
|
|
9278
|
+
const outView = new DataView(out.buffer);
|
|
9279
|
+
for (let i = 0; i < 8; i++) {
|
|
9280
|
+
outView.setUint32(i * 4, h[i], false);
|
|
9281
|
+
}
|
|
9282
|
+
return out;
|
|
9283
|
+
}
|
|
8481
9284
|
/**
|
|
8482
|
-
* SHA-256 hash function (synchronous
|
|
9285
|
+
* SHA-256 hash function (synchronous).
|
|
8483
9286
|
*/
|
|
8484
9287
|
function sha256Sync(data) {
|
|
8485
|
-
|
|
8486
|
-
const { createHash } = require('crypto');
|
|
8487
|
-
return new Uint8Array(createHash('sha256').update(Buffer.from(data)).digest());
|
|
9288
|
+
return sha256Pure(data);
|
|
8488
9289
|
}
|
|
8489
9290
|
/**
|
|
8490
|
-
* SHA-256 hash function (async
|
|
9291
|
+
* SHA-256 hash function (async). Uses WebCrypto when available for speed,
|
|
9292
|
+
* otherwise falls back to the pure implementation.
|
|
8491
9293
|
*/
|
|
8492
9294
|
async function sha256Async(data) {
|
|
8493
9295
|
if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.subtle) {
|
|
8494
|
-
// Create a copy of the data to ensure we have an ArrayBuffer
|
|
8495
9296
|
const copy = new Uint8Array(data);
|
|
8496
9297
|
const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', copy);
|
|
8497
9298
|
return new Uint8Array(hashBuffer);
|
|
8498
9299
|
}
|
|
8499
|
-
return
|
|
9300
|
+
return sha256Pure(data);
|
|
9301
|
+
}
|
|
9302
|
+
/**
|
|
9303
|
+
* Check if a 32-byte value is a valid point on the ed25519 curve.
|
|
9304
|
+
*
|
|
9305
|
+
* A valid PDA must be OFF the curve (it must NOT correspond to a real
|
|
9306
|
+
* ed25519 public key, so that no private key can ever sign for it).
|
|
9307
|
+
*
|
|
9308
|
+
* We determine on-curve status by attempting to decompress the candidate
|
|
9309
|
+
* as a compressed Edwards point. If decompression succeeds the value lies
|
|
9310
|
+
* on the curve; if it throws, the value is off-curve and is a valid PDA.
|
|
9311
|
+
* This matches the behaviour of `PublicKey.isOnCurve` in @solana/web3.js.
|
|
9312
|
+
*/
|
|
9313
|
+
function isOnCurve(publicKey) {
|
|
9314
|
+
if (publicKey.length !== 32) {
|
|
9315
|
+
return false;
|
|
9316
|
+
}
|
|
9317
|
+
try {
|
|
9318
|
+
Point.fromHex(publicKey);
|
|
9319
|
+
return true;
|
|
9320
|
+
}
|
|
9321
|
+
catch {
|
|
9322
|
+
return false;
|
|
9323
|
+
}
|
|
8500
9324
|
}
|
|
8501
9325
|
/**
|
|
8502
9326
|
* PDA marker bytes appended to seeds before hashing.
|
|
@@ -8564,7 +9388,7 @@ async function findProgramAddress(seeds, programId) {
|
|
|
8564
9388
|
for (let bump = 255; bump >= 0; bump--) {
|
|
8565
9389
|
const buffer = buildPdaBuffer(seeds, programIdBytes, bump);
|
|
8566
9390
|
const hash = await sha256Async(buffer);
|
|
8567
|
-
{
|
|
9391
|
+
if (!isOnCurve(hash)) {
|
|
8568
9392
|
return [encodeBase58(hash), bump];
|
|
8569
9393
|
}
|
|
8570
9394
|
}
|
|
@@ -8584,7 +9408,7 @@ function findProgramAddressSync(seeds, programId) {
|
|
|
8584
9408
|
for (let bump = 255; bump >= 0; bump--) {
|
|
8585
9409
|
const buffer = buildPdaBuffer(seeds, programIdBytes, bump);
|
|
8586
9410
|
const hash = sha256Sync(buffer);
|
|
8587
|
-
{
|
|
9411
|
+
if (!isOnCurve(hash)) {
|
|
8588
9412
|
return [encodeBase58(hash), bump];
|
|
8589
9413
|
}
|
|
8590
9414
|
}
|
|
@@ -8633,6 +9457,134 @@ function createPublicKeySeed(address) {
|
|
|
8633
9457
|
return decoded;
|
|
8634
9458
|
}
|
|
8635
9459
|
|
|
9460
|
+
/**
|
|
9461
|
+
* Typed serialization of PDA seed values.
|
|
9462
|
+
*
|
|
9463
|
+
* Shared by the standalone PDA DSL (`pda-dsl.ts`) and instruction account
|
|
9464
|
+
* resolution (`account-resolver.ts`). When a seed carries a declared type
|
|
9465
|
+
* (from the IDL or the `pdas!` registry), encoding is exact: pubkeys are
|
|
9466
|
+
* base58-decoded to 32 bytes, integers are little-endian at the declared
|
|
9467
|
+
* width. Without a type, legacy heuristics apply for backward compatibility.
|
|
9468
|
+
*/
|
|
9469
|
+
/**
|
|
9470
|
+
* Normalizes the type-name variants that IDLs and codegen produce
|
|
9471
|
+
* ("Pubkey", "publicKey", "solana_pubkey::Pubkey", "String", ...) to a
|
|
9472
|
+
* canonical seed type. Returns undefined for types that cannot be a seed.
|
|
9473
|
+
*/
|
|
9474
|
+
function normalizeSeedType(argType) {
|
|
9475
|
+
if (!argType)
|
|
9476
|
+
return undefined;
|
|
9477
|
+
// Strip any path qualifier (e.g. solana_pubkey::Pubkey).
|
|
9478
|
+
const parts = argType.split('::');
|
|
9479
|
+
const t = (parts[parts.length - 1] ?? argType).trim();
|
|
9480
|
+
if (/^[ui](8|16|32|64|128)$/.test(t)) {
|
|
9481
|
+
return t;
|
|
9482
|
+
}
|
|
9483
|
+
switch (t) {
|
|
9484
|
+
case 'pubkey':
|
|
9485
|
+
case 'Pubkey':
|
|
9486
|
+
case 'publicKey':
|
|
9487
|
+
case 'PublicKey':
|
|
9488
|
+
return 'pubkey';
|
|
9489
|
+
case 'string':
|
|
9490
|
+
case 'String':
|
|
9491
|
+
case 'str':
|
|
9492
|
+
return 'string';
|
|
9493
|
+
default:
|
|
9494
|
+
return undefined;
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
/**
|
|
9498
|
+
* Serializes a PDA seed value.
|
|
9499
|
+
*
|
|
9500
|
+
* With a recognized `argType`, encoding is strict and width-exact; an
|
|
9501
|
+
* incompatible value throws rather than deriving a wrong address. Without
|
|
9502
|
+
* one, the legacy heuristics apply: raw bytes pass through, 43/44-character
|
|
9503
|
+
* strings are tried as base58, other strings are utf-8, numbers are 8-byte
|
|
9504
|
+
* little-endian u64.
|
|
9505
|
+
*/
|
|
9506
|
+
function serializeSeedValue(value, argType) {
|
|
9507
|
+
if (value instanceof Uint8Array) {
|
|
9508
|
+
return value;
|
|
9509
|
+
}
|
|
9510
|
+
const t = normalizeSeedType(argType);
|
|
9511
|
+
if (t === 'pubkey') {
|
|
9512
|
+
if (typeof value !== 'string') {
|
|
9513
|
+
throw new Error(`Pubkey seed requires a base58 string, got ${typeof value}`);
|
|
9514
|
+
}
|
|
9515
|
+
const decoded = decodeBase58(value);
|
|
9516
|
+
if (decoded.length !== 32) {
|
|
9517
|
+
throw new Error(`Pubkey seed '${value}' decoded to ${decoded.length} bytes, expected 32`);
|
|
9518
|
+
}
|
|
9519
|
+
return decoded;
|
|
9520
|
+
}
|
|
9521
|
+
if (t === 'string') {
|
|
9522
|
+
if (typeof value !== 'string') {
|
|
9523
|
+
throw new Error(`String seed requires a string value, got ${typeof value}`);
|
|
9524
|
+
}
|
|
9525
|
+
return new TextEncoder().encode(value);
|
|
9526
|
+
}
|
|
9527
|
+
if (t !== undefined) {
|
|
9528
|
+
// Numeric type at a declared width.
|
|
9529
|
+
if (typeof value !== 'bigint' && typeof value !== 'number') {
|
|
9530
|
+
throw new Error(`Numeric seed of type ${t} requires a number/bigint, got ${typeof value}`);
|
|
9531
|
+
}
|
|
9532
|
+
const bits = parseInt(t.slice(1), 10);
|
|
9533
|
+
return serializeNumber(value, bits / 8, t.startsWith('i'));
|
|
9534
|
+
}
|
|
9535
|
+
// --- Untyped: legacy heuristics. ---
|
|
9536
|
+
if (typeof value === 'string') {
|
|
9537
|
+
if (value.length === 43 || value.length === 44) {
|
|
9538
|
+
try {
|
|
9539
|
+
return decodeBase58(value);
|
|
9540
|
+
}
|
|
9541
|
+
catch {
|
|
9542
|
+
return new TextEncoder().encode(value);
|
|
9543
|
+
}
|
|
9544
|
+
}
|
|
9545
|
+
return new TextEncoder().encode(value);
|
|
9546
|
+
}
|
|
9547
|
+
if (typeof value === 'bigint' || typeof value === 'number') {
|
|
9548
|
+
return serializeNumber(value, 8, true);
|
|
9549
|
+
}
|
|
9550
|
+
throw new Error(`Cannot serialize value for PDA seed: ${typeof value}`);
|
|
9551
|
+
}
|
|
9552
|
+
/**
|
|
9553
|
+
* Little-endian two's-complement encoding at a fixed byte width, with an
|
|
9554
|
+
* overflow check so out-of-range values fail instead of silently truncating.
|
|
9555
|
+
*/
|
|
9556
|
+
function serializeNumber(value, size, signed) {
|
|
9557
|
+
const buffer = new Uint8Array(size);
|
|
9558
|
+
let n = typeof value === 'bigint' ? value : BigInt(value);
|
|
9559
|
+
const original = n;
|
|
9560
|
+
for (let i = 0; i < size; i++) {
|
|
9561
|
+
buffer[i] = Number(n & BigInt(0xff));
|
|
9562
|
+
n >>= BigInt(8);
|
|
9563
|
+
}
|
|
9564
|
+
const fits = signed ? n === BigInt(0) || n === BigInt(-1) : n === BigInt(0);
|
|
9565
|
+
if (!fits) {
|
|
9566
|
+
throw new Error(`Seed value ${original} does not fit in ${size * 8} bits`);
|
|
9567
|
+
}
|
|
9568
|
+
return buffer;
|
|
9569
|
+
}
|
|
9570
|
+
|
|
9571
|
+
function getValueByPath(source, path) {
|
|
9572
|
+
if (!source) {
|
|
9573
|
+
return undefined;
|
|
9574
|
+
}
|
|
9575
|
+
if (Object.prototype.hasOwnProperty.call(source, path)) {
|
|
9576
|
+
return source[path];
|
|
9577
|
+
}
|
|
9578
|
+
let current = source;
|
|
9579
|
+
for (const segment of path.split('.')) {
|
|
9580
|
+
if (current === null || typeof current !== 'object') {
|
|
9581
|
+
return undefined;
|
|
9582
|
+
}
|
|
9583
|
+
current = current[segment];
|
|
9584
|
+
}
|
|
9585
|
+
return current;
|
|
9586
|
+
}
|
|
9587
|
+
|
|
8636
9588
|
/**
|
|
8637
9589
|
* Topologically sort accounts so that dependencies (accountRef) are resolved first.
|
|
8638
9590
|
* Non-PDA accounts come first, then PDAs in dependency order.
|
|
@@ -8719,13 +9671,32 @@ function resolveAccounts(accountMetas, args, options) {
|
|
|
8719
9671
|
missing.push(meta.name);
|
|
8720
9672
|
}
|
|
8721
9673
|
}
|
|
8722
|
-
// Return accounts in original order (as defined in accountMetas)
|
|
9674
|
+
// Return accounts in original order (as defined in accountMetas).
|
|
9675
|
+
//
|
|
9676
|
+
// Omitted optional accounts that precede a resolved account cannot simply
|
|
9677
|
+
// be dropped — that would shift every later account into the wrong slot.
|
|
9678
|
+
// Anchor's convention is to pass the program ID as a placeholder; trailing
|
|
9679
|
+
// omitted optionals are dropped as usual.
|
|
9680
|
+
const lastResolvedIndex = accountMetas.reduce((last, meta, index) => (resolvedMap[meta.name] ? index : last), -1);
|
|
8723
9681
|
const orderedAccounts = [];
|
|
8724
|
-
for (
|
|
9682
|
+
for (let i = 0; i < accountMetas.length; i++) {
|
|
9683
|
+
const meta = accountMetas[i];
|
|
8725
9684
|
const resolved = resolvedMap[meta.name];
|
|
8726
9685
|
if (resolved) {
|
|
8727
9686
|
orderedAccounts.push(resolved);
|
|
8728
9687
|
}
|
|
9688
|
+
else if (meta.isOptional && i < lastResolvedIndex) {
|
|
9689
|
+
if (!options.programId) {
|
|
9690
|
+
throw new Error('Omitted optional account "' + meta.name + '" precedes other accounts and needs ' +
|
|
9691
|
+
'the program ID as a placeholder, but no programId was provided in options.');
|
|
9692
|
+
}
|
|
9693
|
+
orderedAccounts.push({
|
|
9694
|
+
name: meta.name,
|
|
9695
|
+
address: options.programId,
|
|
9696
|
+
isSigner: false,
|
|
9697
|
+
isWritable: false,
|
|
9698
|
+
});
|
|
9699
|
+
}
|
|
8729
9700
|
}
|
|
8730
9701
|
return {
|
|
8731
9702
|
accounts: orderedAccounts,
|
|
@@ -8735,24 +9706,25 @@ function resolveAccounts(accountMetas, args, options) {
|
|
|
8735
9706
|
function resolveSingleAccount(meta, args, options, resolvedMap) {
|
|
8736
9707
|
switch (meta.category) {
|
|
8737
9708
|
case 'signer':
|
|
8738
|
-
return resolveSignerAccount(meta, options.wallet);
|
|
9709
|
+
return resolveSignerAccount(meta, options.accounts, options.wallet);
|
|
8739
9710
|
case 'known':
|
|
8740
9711
|
return resolveKnownAccount(meta);
|
|
8741
9712
|
case 'pda':
|
|
8742
|
-
return resolvePdaAccount(meta, args, resolvedMap, options.programId);
|
|
9713
|
+
return resolvePdaAccount(meta, args, resolvedMap, options.programId, options.resolve);
|
|
8743
9714
|
case 'userProvided':
|
|
8744
9715
|
return resolveUserProvidedAccount(meta, options.accounts);
|
|
8745
9716
|
default:
|
|
8746
9717
|
return null;
|
|
8747
9718
|
}
|
|
8748
9719
|
}
|
|
8749
|
-
function resolveSignerAccount(meta, wallet) {
|
|
8750
|
-
|
|
9720
|
+
function resolveSignerAccount(meta, accounts, wallet) {
|
|
9721
|
+
const address = accounts?.[meta.name] ?? wallet?.publicKey;
|
|
9722
|
+
if (!address) {
|
|
8751
9723
|
return null;
|
|
8752
9724
|
}
|
|
8753
9725
|
return {
|
|
8754
9726
|
name: meta.name,
|
|
8755
|
-
address
|
|
9727
|
+
address,
|
|
8756
9728
|
isSigner: true,
|
|
8757
9729
|
isWritable: meta.isWritable,
|
|
8758
9730
|
};
|
|
@@ -8768,7 +9740,7 @@ function resolveKnownAccount(meta) {
|
|
|
8768
9740
|
isWritable: meta.isWritable,
|
|
8769
9741
|
};
|
|
8770
9742
|
}
|
|
8771
|
-
function resolvePdaAccount(meta, args, resolvedMap, programId) {
|
|
9743
|
+
function resolvePdaAccount(meta, args, resolvedMap, programId, resolve) {
|
|
8772
9744
|
if (!meta.pdaConfig) {
|
|
8773
9745
|
return null;
|
|
8774
9746
|
}
|
|
@@ -8785,13 +9757,16 @@ function resolvePdaAccount(meta, args, resolvedMap, programId) {
|
|
|
8785
9757
|
case 'literal':
|
|
8786
9758
|
seeds.push(createSeed(seed.value));
|
|
8787
9759
|
break;
|
|
9760
|
+
case 'bytes':
|
|
9761
|
+
seeds.push(Uint8Array.from(seed.value));
|
|
9762
|
+
break;
|
|
8788
9763
|
case 'argRef': {
|
|
8789
|
-
const argValue = args
|
|
9764
|
+
const argValue = getValueByPath(args, seed.argName) ?? getValueByPath(resolve, seed.argName);
|
|
8790
9765
|
if (argValue === undefined) {
|
|
8791
9766
|
throw new Error('PDA seed references missing argument: ' + seed.argName +
|
|
8792
9767
|
' (for account "' + meta.name + '")');
|
|
8793
9768
|
}
|
|
8794
|
-
seeds.push(
|
|
9769
|
+
seeds.push(serializeSeedValue(argValue, seed.argType));
|
|
8795
9770
|
break;
|
|
8796
9771
|
}
|
|
8797
9772
|
case 'accountRef': {
|
|
@@ -8859,6 +9834,11 @@ function serializeInstructionData(discriminator, args, schema) {
|
|
|
8859
9834
|
const buffers = [Buffer.from(discriminator)];
|
|
8860
9835
|
for (const field of schema) {
|
|
8861
9836
|
const value = args[field.name];
|
|
9837
|
+
// Option fields treat undefined as None; everything else must be present
|
|
9838
|
+
// (silently encoding zeros for a missing arg corrupts instruction data).
|
|
9839
|
+
if (value === undefined && !(typeof field.type === 'object' && 'option' in field.type)) {
|
|
9840
|
+
throw new Error(`Missing required argument "${field.name}" (type ${JSON.stringify(field.type)})`);
|
|
9841
|
+
}
|
|
8862
9842
|
const serialized = serializeValue(value, field.type);
|
|
8863
9843
|
buffers.push(serialized);
|
|
8864
9844
|
}
|
|
@@ -8877,8 +9857,108 @@ function serializeValue(value, type) {
|
|
|
8877
9857
|
if ('array' in type) {
|
|
8878
9858
|
return serializeArray(value, type.array[0], type.array[1]);
|
|
8879
9859
|
}
|
|
9860
|
+
if ('hashMap' in type) {
|
|
9861
|
+
return serializeHashMap(value, type.hashMap[0], type.hashMap[1]);
|
|
9862
|
+
}
|
|
9863
|
+
if ('struct' in type) {
|
|
9864
|
+
return serializeStruct(value, type.struct);
|
|
9865
|
+
}
|
|
9866
|
+
if ('enum' in type) {
|
|
9867
|
+
return serializeEnum(value, type.enum);
|
|
9868
|
+
}
|
|
8880
9869
|
throw new Error(`Unknown type: ${JSON.stringify(type)}`);
|
|
8881
9870
|
}
|
|
9871
|
+
function serializeStruct(value, fields) {
|
|
9872
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
9873
|
+
throw new Error(`Struct value must be a plain object, got ${typeof value}`);
|
|
9874
|
+
}
|
|
9875
|
+
const obj = value;
|
|
9876
|
+
const buffers = [];
|
|
9877
|
+
for (const field of fields) {
|
|
9878
|
+
const fieldValue = obj[field.name];
|
|
9879
|
+
if (fieldValue === undefined &&
|
|
9880
|
+
!(typeof field.type === 'object' && 'option' in field.type)) {
|
|
9881
|
+
throw new Error(`Missing required struct field "${field.name}"`);
|
|
9882
|
+
}
|
|
9883
|
+
buffers.push(serializeValue(fieldValue, field.type));
|
|
9884
|
+
}
|
|
9885
|
+
return Buffer.concat(buffers);
|
|
9886
|
+
}
|
|
9887
|
+
function serializeHashMap(value, keyType, valueType) {
|
|
9888
|
+
if (keyType !== 'string') {
|
|
9889
|
+
throw new Error(`Instruction hashMap keys must use the 'string' schema, got ${JSON.stringify(keyType)}`);
|
|
9890
|
+
}
|
|
9891
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
9892
|
+
throw new Error(`HashMap value must be a plain object, got ${typeof value}`);
|
|
9893
|
+
}
|
|
9894
|
+
const entries = Object.entries(value).sort(([left], [right]) =>
|
|
9895
|
+
// Rust/Borsh sorts String keys by their UTF-8 bytes before serializing.
|
|
9896
|
+
Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8')));
|
|
9897
|
+
const len = Buffer.alloc(4);
|
|
9898
|
+
len.writeUInt32LE(entries.length, 0);
|
|
9899
|
+
const entryBuffers = [];
|
|
9900
|
+
for (const [key, entryValue] of entries) {
|
|
9901
|
+
entryBuffers.push(serializePrimitive(key, 'string'));
|
|
9902
|
+
entryBuffers.push(serializeValue(entryValue, valueType));
|
|
9903
|
+
}
|
|
9904
|
+
return Buffer.concat([len, ...entryBuffers]);
|
|
9905
|
+
}
|
|
9906
|
+
function variantName(variant) {
|
|
9907
|
+
return typeof variant === 'string' ? variant : variant.name;
|
|
9908
|
+
}
|
|
9909
|
+
function serializeEnum(value, variants) {
|
|
9910
|
+
// Numeric value: a bare variant index (fieldless variants only).
|
|
9911
|
+
if (typeof value === 'number') {
|
|
9912
|
+
if (!Number.isInteger(value) || value < 0 || value >= variants.length) {
|
|
9913
|
+
throw new Error(`Enum variant index ${value} out of range (0..${variants.length - 1})`);
|
|
9914
|
+
}
|
|
9915
|
+
const variant = variants[value];
|
|
9916
|
+
if (typeof variant !== 'string') {
|
|
9917
|
+
throw new Error(`Enum variant "${variantName(variant)}" carries data; pass { ${variantName(variant)}: ... } instead of an index`);
|
|
9918
|
+
}
|
|
9919
|
+
return Buffer.from([value]);
|
|
9920
|
+
}
|
|
9921
|
+
// String value: a fieldless variant by name.
|
|
9922
|
+
if (typeof value === 'string') {
|
|
9923
|
+
const index = variants.findIndex((v) => variantName(v) === value);
|
|
9924
|
+
if (index === -1) {
|
|
9925
|
+
throw new Error(`Unknown enum variant "${value}". Expected one of: ${variants.map(variantName).join(', ')}`);
|
|
9926
|
+
}
|
|
9927
|
+
const variant = variants[index];
|
|
9928
|
+
if (typeof variant !== 'string') {
|
|
9929
|
+
throw new Error(`Enum variant "${value}" carries data; pass { ${value}: ... } instead of a bare name`);
|
|
9930
|
+
}
|
|
9931
|
+
return Buffer.from([index]);
|
|
9932
|
+
}
|
|
9933
|
+
// Object value: { variantName: payload } for data-carrying variants.
|
|
9934
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
9935
|
+
const keys = Object.keys(value);
|
|
9936
|
+
if (keys.length !== 1) {
|
|
9937
|
+
throw new Error(`Enum value must be a single-key object ({ variantName: payload }), got keys [${keys.join(', ')}]`);
|
|
9938
|
+
}
|
|
9939
|
+
const key = keys[0];
|
|
9940
|
+
const payload = value[key];
|
|
9941
|
+
const index = variants.findIndex((v) => variantName(v) === key);
|
|
9942
|
+
if (index === -1) {
|
|
9943
|
+
throw new Error(`Unknown enum variant "${key}". Expected one of: ${variants.map(variantName).join(', ')}`);
|
|
9944
|
+
}
|
|
9945
|
+
const variant = variants[index];
|
|
9946
|
+
if (typeof variant === 'string') {
|
|
9947
|
+
throw new Error(`Enum variant "${key}" is fieldless; pass '${key}' instead of an object`);
|
|
9948
|
+
}
|
|
9949
|
+
const prefix = Buffer.from([index]);
|
|
9950
|
+
if ('fields' in variant) {
|
|
9951
|
+
return Buffer.concat([prefix, serializeStruct(payload, variant.fields)]);
|
|
9952
|
+
}
|
|
9953
|
+
const tuple = payload;
|
|
9954
|
+
if (!Array.isArray(tuple) || tuple.length !== variant.tuple.length) {
|
|
9955
|
+
throw new Error(`Enum variant "${key}" expects a tuple of length ${variant.tuple.length}`);
|
|
9956
|
+
}
|
|
9957
|
+
const elements = variant.tuple.map((elementType, i) => serializeValue(tuple[i], elementType));
|
|
9958
|
+
return Buffer.concat([prefix, ...elements]);
|
|
9959
|
+
}
|
|
9960
|
+
throw new Error(`Cannot serialize enum from value of type ${typeof value}`);
|
|
9961
|
+
}
|
|
8882
9962
|
function serializePrimitive(value, type) {
|
|
8883
9963
|
switch (type) {
|
|
8884
9964
|
case 'u8':
|
|
@@ -8919,21 +9999,60 @@ function serializePrimitive(value, type) {
|
|
|
8919
9999
|
case 'i128':
|
|
8920
10000
|
const i128 = Buffer.alloc(16);
|
|
8921
10001
|
const bigI128 = BigInt(value);
|
|
8922
|
-
|
|
10002
|
+
// The masked low limb is always non-negative, so it must be written
|
|
10003
|
+
// unsigned; the arithmetic-shifted high limb carries the sign.
|
|
10004
|
+
i128.writeBigUInt64LE(bigI128 & BigInt('0xFFFFFFFFFFFFFFFF'), 0);
|
|
8923
10005
|
i128.writeBigInt64LE(bigI128 >> BigInt(64), 8);
|
|
8924
10006
|
return i128;
|
|
10007
|
+
case 'f32': {
|
|
10008
|
+
const f32 = Buffer.alloc(4);
|
|
10009
|
+
f32.writeFloatLE(value, 0);
|
|
10010
|
+
return f32;
|
|
10011
|
+
}
|
|
10012
|
+
case 'f64': {
|
|
10013
|
+
const f64 = Buffer.alloc(8);
|
|
10014
|
+
f64.writeDoubleLE(value, 0);
|
|
10015
|
+
return f64;
|
|
10016
|
+
}
|
|
8925
10017
|
case 'bool':
|
|
8926
10018
|
return Buffer.from([value ? 1 : 0]);
|
|
10019
|
+
case 'bytes': {
|
|
10020
|
+
// Borsh bytes: u32 LE length prefix + raw bytes.
|
|
10021
|
+
const raw = value instanceof Uint8Array
|
|
10022
|
+
? value
|
|
10023
|
+
: Array.isArray(value)
|
|
10024
|
+
? Uint8Array.from(value)
|
|
10025
|
+
: null;
|
|
10026
|
+
if (raw === null) {
|
|
10027
|
+
throw new Error(`Cannot serialize bytes from value of type ${typeof value}`);
|
|
10028
|
+
}
|
|
10029
|
+
const lenPrefix = Buffer.alloc(4);
|
|
10030
|
+
lenPrefix.writeUInt32LE(raw.length, 0);
|
|
10031
|
+
return Buffer.concat([lenPrefix, Buffer.from(raw)]);
|
|
10032
|
+
}
|
|
8927
10033
|
case 'string':
|
|
8928
10034
|
const str = value;
|
|
8929
10035
|
const strBytes = Buffer.from(str, 'utf-8');
|
|
8930
10036
|
const strLen = Buffer.alloc(4);
|
|
8931
10037
|
strLen.writeUInt32LE(strBytes.length, 0);
|
|
8932
10038
|
return Buffer.concat([strLen, strBytes]);
|
|
8933
|
-
case 'pubkey':
|
|
8934
|
-
// Public key is 32 bytes
|
|
8935
|
-
|
|
8936
|
-
|
|
10039
|
+
case 'pubkey': {
|
|
10040
|
+
// Public key is 32 bytes. Accept base58 strings or raw 32-byte buffers.
|
|
10041
|
+
if (value instanceof Uint8Array) {
|
|
10042
|
+
if (value.length !== 32) {
|
|
10043
|
+
throw new Error(`Invalid pubkey byte length: expected 32, got ${value.length}`);
|
|
10044
|
+
}
|
|
10045
|
+
return Buffer.from(value);
|
|
10046
|
+
}
|
|
10047
|
+
if (typeof value === 'string') {
|
|
10048
|
+
const decoded = decodeBase58(value);
|
|
10049
|
+
if (decoded.length !== 32) {
|
|
10050
|
+
throw new Error(`Invalid pubkey: '${value}' decoded to ${decoded.length} bytes, expected 32`);
|
|
10051
|
+
}
|
|
10052
|
+
return Buffer.from(decoded);
|
|
10053
|
+
}
|
|
10054
|
+
throw new Error(`Cannot serialize pubkey from value of type ${typeof value}`);
|
|
10055
|
+
}
|
|
8937
10056
|
default:
|
|
8938
10057
|
throw new Error(`Unknown primitive type: ${type}`);
|
|
8939
10058
|
}
|
|
@@ -8960,59 +10079,7 @@ function serializeArray(values, elementType, length) {
|
|
|
8960
10079
|
}
|
|
8961
10080
|
|
|
8962
10081
|
/**
|
|
8963
|
-
*
|
|
8964
|
-
*
|
|
8965
|
-
* @param signature - Transaction signature
|
|
8966
|
-
* @param level - Desired confirmation level
|
|
8967
|
-
* @param timeout - Maximum wait time in milliseconds
|
|
8968
|
-
* @returns Confirmation result
|
|
8969
|
-
*/
|
|
8970
|
-
async function waitForConfirmation(signature, level = 'confirmed', timeout = 60000) {
|
|
8971
|
-
const startTime = Date.now();
|
|
8972
|
-
while (Date.now() - startTime < timeout) {
|
|
8973
|
-
const status = await checkTransactionStatus();
|
|
8974
|
-
if (status.err) {
|
|
8975
|
-
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
|
|
8976
|
-
}
|
|
8977
|
-
if (isConfirmationLevelSufficient(status.confirmations, level)) {
|
|
8978
|
-
return {
|
|
8979
|
-
level,
|
|
8980
|
-
slot: status.slot,
|
|
8981
|
-
};
|
|
8982
|
-
}
|
|
8983
|
-
await sleep(1000);
|
|
8984
|
-
}
|
|
8985
|
-
throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
|
|
8986
|
-
}
|
|
8987
|
-
async function checkTransactionStatus(_signature) {
|
|
8988
|
-
// In production, query the Solana RPC
|
|
8989
|
-
return {
|
|
8990
|
-
err: null,
|
|
8991
|
-
confirmations: 32,
|
|
8992
|
-
slot: 123456789,
|
|
8993
|
-
};
|
|
8994
|
-
}
|
|
8995
|
-
function isConfirmationLevelSufficient(confirmations, level) {
|
|
8996
|
-
if (confirmations === null) {
|
|
8997
|
-
return false;
|
|
8998
|
-
}
|
|
8999
|
-
switch (level) {
|
|
9000
|
-
case 'processed':
|
|
9001
|
-
return confirmations >= 0;
|
|
9002
|
-
case 'confirmed':
|
|
9003
|
-
return confirmations >= 1;
|
|
9004
|
-
case 'finalized':
|
|
9005
|
-
return confirmations >= 32;
|
|
9006
|
-
default:
|
|
9007
|
-
return false;
|
|
9008
|
-
}
|
|
9009
|
-
}
|
|
9010
|
-
function sleep(ms) {
|
|
9011
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
9012
|
-
}
|
|
9013
|
-
|
|
9014
|
-
/**
|
|
9015
|
-
* Parses and handles instruction errors.
|
|
10082
|
+
* Parses and handles instruction errors.
|
|
9016
10083
|
*/
|
|
9017
10084
|
/**
|
|
9018
10085
|
* Parses an error returned from a Solana transaction.
|
|
@@ -9070,103 +10137,369 @@ function extractErrorCode(error) {
|
|
|
9070
10137
|
function formatProgramError(error) {
|
|
9071
10138
|
return `${error.name} (${error.code}): ${error.message}`;
|
|
9072
10139
|
}
|
|
9073
|
-
|
|
9074
10140
|
/**
|
|
9075
|
-
*
|
|
10141
|
+
* Error thrown when an instruction fails to send and the underlying failure
|
|
10142
|
+
* could be parsed against the handler's IDL error definitions.
|
|
9076
10143
|
*/
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
10144
|
+
class InstructionError extends Error {
|
|
10145
|
+
constructor(message, programError, cause) {
|
|
10146
|
+
super(message);
|
|
10147
|
+
this.name = 'InstructionError';
|
|
10148
|
+
this.programError = programError;
|
|
10149
|
+
this.cause = cause;
|
|
9081
10150
|
}
|
|
9082
|
-
|
|
10151
|
+
}
|
|
10152
|
+
|
|
10153
|
+
/**
|
|
10154
|
+
* Creates a data-driven instruction handler.
|
|
10155
|
+
*
|
|
10156
|
+
* The returned handler implements `build()` generically: it serializes args
|
|
10157
|
+
* via the schema-driven serializer and constructs the account key list from
|
|
10158
|
+
* the resolved, ordered accounts. No imperative per-instruction code is
|
|
10159
|
+
* required, which keeps generated SDKs tiny and puts all serialization logic
|
|
10160
|
+
* in one tested place.
|
|
10161
|
+
*/
|
|
10162
|
+
function createInstructionHandler(config) {
|
|
10163
|
+
const discriminator = config.discriminator instanceof Uint8Array
|
|
10164
|
+
? config.discriminator
|
|
10165
|
+
: Uint8Array.from(config.discriminator);
|
|
10166
|
+
const argNames = config.args.map((a) => a.name);
|
|
10167
|
+
const errors = config.errors ?? [];
|
|
10168
|
+
return {
|
|
10169
|
+
programId: config.programId,
|
|
10170
|
+
accounts: config.accounts,
|
|
10171
|
+
errors,
|
|
10172
|
+
argNames,
|
|
10173
|
+
build(args, resolved) {
|
|
10174
|
+
const data = serializeInstructionData(discriminator, args, config.args);
|
|
10175
|
+
return {
|
|
10176
|
+
programId: config.programId,
|
|
10177
|
+
keys: resolved.map((r) => ({
|
|
10178
|
+
pubkey: r.address,
|
|
10179
|
+
isSigner: r.isSigner,
|
|
10180
|
+
isWritable: r.isWritable,
|
|
10181
|
+
})),
|
|
10182
|
+
data,
|
|
10183
|
+
};
|
|
10184
|
+
},
|
|
10185
|
+
};
|
|
9083
10186
|
}
|
|
9084
10187
|
/**
|
|
9085
|
-
*
|
|
10188
|
+
* Splits a merged params object into serialized args and account overrides.
|
|
9086
10189
|
*
|
|
9087
|
-
*
|
|
9088
|
-
*
|
|
9089
|
-
*
|
|
9090
|
-
*
|
|
9091
|
-
*
|
|
10190
|
+
* Keys matching a declared argument name are args; keys matching a declared
|
|
10191
|
+
* account name (with a string value) are account address overrides. This
|
|
10192
|
+
* applies to signer slots too, allowing explicit signer addresses to override
|
|
10193
|
+
* the wallet fallback. Anything else throws — a typo'd key silently dropped
|
|
10194
|
+
* here would otherwise change the built instruction. `options.accounts` on
|
|
10195
|
+
* {@link BuildOptions} remains an unvalidated escape hatch for advanced callers.
|
|
10196
|
+
*/
|
|
10197
|
+
function splitParams(handler, params) {
|
|
10198
|
+
const argNameSet = new Set(handler.argNames);
|
|
10199
|
+
const accountNameSet = new Set(handler.accounts.map((a) => a.name));
|
|
10200
|
+
const args = {};
|
|
10201
|
+
const accountOverrides = {};
|
|
10202
|
+
let resolve;
|
|
10203
|
+
for (const [key, value] of Object.entries(params)) {
|
|
10204
|
+
if (argNameSet.has(key)) {
|
|
10205
|
+
args[key] = value;
|
|
10206
|
+
}
|
|
10207
|
+
else if (key === 'resolve' && !accountNameSet.has(key)) {
|
|
10208
|
+
if (value === undefined) {
|
|
10209
|
+
continue;
|
|
10210
|
+
}
|
|
10211
|
+
if (value === null || Array.isArray(value) || typeof value !== 'object') {
|
|
10212
|
+
throw new Error('Parameter "resolve" must be an object when provided');
|
|
10213
|
+
}
|
|
10214
|
+
resolve = value;
|
|
10215
|
+
}
|
|
10216
|
+
else if (accountNameSet.has(key)) {
|
|
10217
|
+
if (typeof value !== 'string') {
|
|
10218
|
+
// Non-string values are not valid account addresses.
|
|
10219
|
+
throw new Error(`Parameter "${key}" is not a known argument and is not a base58 account address`);
|
|
10220
|
+
}
|
|
10221
|
+
accountOverrides[key] = value;
|
|
10222
|
+
}
|
|
10223
|
+
else {
|
|
10224
|
+
throw new Error(`Unknown parameter "${key}". Expected one of args [${[...argNameSet].join(', ')}] ` +
|
|
10225
|
+
`or accounts [${[...accountNameSet].join(', ')}]`);
|
|
10226
|
+
}
|
|
10227
|
+
}
|
|
10228
|
+
return { args, accountOverrides, resolve };
|
|
10229
|
+
}
|
|
10230
|
+
/**
|
|
10231
|
+
* Builds a {@link BuiltInstruction} from a handler and a merged params object.
|
|
9092
10232
|
*
|
|
9093
|
-
*
|
|
9094
|
-
*
|
|
9095
|
-
* @param options - Execution options
|
|
9096
|
-
* @returns Execution result with signature
|
|
10233
|
+
* This is a pure function: it performs no network access. It is the unit of
|
|
10234
|
+
* composition for batching (`wallet.signAndSend([a, b, c])`).
|
|
9097
10235
|
*/
|
|
9098
|
-
|
|
9099
|
-
|
|
10236
|
+
function buildInstruction(handler, params, options = {}) {
|
|
10237
|
+
const { args, accountOverrides, resolve } = splitParams(handler, params);
|
|
9100
10238
|
const resolutionOptions = {
|
|
9101
|
-
accounts: options.accounts,
|
|
10239
|
+
accounts: { ...accountOverrides, ...options.accounts },
|
|
10240
|
+
resolve,
|
|
9102
10241
|
wallet: options.wallet,
|
|
9103
|
-
programId: handler.programId,
|
|
10242
|
+
programId: handler.programId,
|
|
9104
10243
|
};
|
|
9105
10244
|
const resolution = resolveAccounts(handler.accounts, args, resolutionOptions);
|
|
9106
10245
|
validateAccountResolution(resolution);
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
// Step 3: Build transaction from the built instruction
|
|
9111
|
-
const transaction = buildTransaction(instruction);
|
|
9112
|
-
// Step 4: Sign and send
|
|
9113
|
-
if (!options.wallet) {
|
|
9114
|
-
throw new Error('Wallet required to sign transaction');
|
|
10246
|
+
const instruction = handler.build(args, resolution.accounts);
|
|
10247
|
+
if (options.remainingAccounts?.length) {
|
|
10248
|
+
instruction.keys.push(...options.remainingAccounts);
|
|
9115
10249
|
}
|
|
9116
|
-
|
|
9117
|
-
// Step 5: Wait for confirmation
|
|
9118
|
-
const confirmationLevel = options.confirmationLevel ?? 'confirmed';
|
|
9119
|
-
const timeout = options.timeout ?? 60000;
|
|
9120
|
-
const confirmation = await waitForConfirmation(signature, confirmationLevel, timeout);
|
|
9121
|
-
return {
|
|
9122
|
-
signature,
|
|
9123
|
-
confirmationLevel: confirmation.level,
|
|
9124
|
-
slot: confirmation.slot,
|
|
9125
|
-
};
|
|
10250
|
+
return instruction;
|
|
9126
10251
|
}
|
|
9127
10252
|
/**
|
|
9128
|
-
*
|
|
10253
|
+
* Builds, signs, and sends an instruction via the wallet adapter.
|
|
9129
10254
|
*
|
|
9130
|
-
*
|
|
9131
|
-
*
|
|
10255
|
+
* The core SDK does not touch RPC: the adapter owns blockhash, compilation,
|
|
10256
|
+
* signing, sending, and confirmation. On failure, program errors are parsed
|
|
10257
|
+
* against the handler's IDL error definitions and surfaced as an
|
|
10258
|
+
* {@link InstructionError}.
|
|
9132
10259
|
*/
|
|
9133
|
-
function
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
keys: instruction.keys,
|
|
9141
|
-
data: Array.from(instruction.data),
|
|
9142
|
-
}],
|
|
10260
|
+
async function executeInstruction(handler, params, options = {}) {
|
|
10261
|
+
const instruction = buildInstruction(handler, params, options);
|
|
10262
|
+
if (!options.wallet) {
|
|
10263
|
+
throw new Error('Wallet required to sign and send transaction');
|
|
10264
|
+
}
|
|
10265
|
+
const sendOptions = {
|
|
10266
|
+
...options.send,
|
|
9143
10267
|
};
|
|
10268
|
+
if (options.confirmationLevel !== undefined) {
|
|
10269
|
+
sendOptions.confirmationLevel = options.confirmationLevel;
|
|
10270
|
+
}
|
|
10271
|
+
try {
|
|
10272
|
+
const result = await options.wallet.signAndSend([instruction], sendOptions);
|
|
10273
|
+
return { signature: result.signature, slot: result.slot };
|
|
10274
|
+
}
|
|
10275
|
+
catch (err) {
|
|
10276
|
+
const programError = parseInstructionError(err, handler.errors);
|
|
10277
|
+
if (programError) {
|
|
10278
|
+
throw new InstructionError(`${programError.name} (${programError.code}): ${programError.message}`, programError, err);
|
|
10279
|
+
}
|
|
10280
|
+
throw err;
|
|
10281
|
+
}
|
|
9144
10282
|
}
|
|
9145
10283
|
/**
|
|
9146
10284
|
* Creates an instruction executor bound to a specific wallet.
|
|
9147
|
-
*
|
|
9148
|
-
* @param wallet - Wallet adapter
|
|
9149
|
-
* @returns Bound executor function
|
|
9150
10285
|
*/
|
|
9151
10286
|
function createInstructionExecutor(wallet) {
|
|
9152
10287
|
return {
|
|
9153
|
-
execute: async (handler,
|
|
9154
|
-
return executeInstruction(handler,
|
|
9155
|
-
...options,
|
|
9156
|
-
wallet,
|
|
9157
|
-
});
|
|
10288
|
+
execute: async (handler, params, options) => {
|
|
10289
|
+
return executeInstruction(handler, params, { ...options, wallet });
|
|
9158
10290
|
},
|
|
10291
|
+
build: (handler, params, options) => {
|
|
10292
|
+
return buildInstruction(handler, params, { ...options, wallet });
|
|
10293
|
+
},
|
|
10294
|
+
};
|
|
10295
|
+
}
|
|
10296
|
+
|
|
10297
|
+
const STACK_RUNTIME_EXTENSIONS = '__areteStackRuntimeExtensions';
|
|
10298
|
+
const PROGRAM_OPERATION_EXTENSIONS = '__areteProgramOperationExtensions';
|
|
10299
|
+
function getProgramRuntimeExtensions(program) {
|
|
10300
|
+
return program[PROGRAM_OPERATION_EXTENSIONS];
|
|
10301
|
+
}
|
|
10302
|
+
function getStackRuntimeExtensions(stack) {
|
|
10303
|
+
return stack[STACK_RUNTIME_EXTENSIONS];
|
|
10304
|
+
}
|
|
10305
|
+
function defineClientField(target, key, value) {
|
|
10306
|
+
if (value === undefined || key in target) {
|
|
10307
|
+
return;
|
|
10308
|
+
}
|
|
10309
|
+
Object.defineProperty(target, key, {
|
|
10310
|
+
value,
|
|
10311
|
+
enumerable: true,
|
|
10312
|
+
configurable: true,
|
|
10313
|
+
writable: false,
|
|
10314
|
+
});
|
|
10315
|
+
}
|
|
10316
|
+
function applyConnectedStackExtensions(client, stack) {
|
|
10317
|
+
const extendedStack = stack;
|
|
10318
|
+
defineClientField(client, 'addresses', extendedStack.addresses);
|
|
10319
|
+
defineClientField(client, 'constants', extendedStack.constants);
|
|
10320
|
+
defineClientField(client, 'defaults', extendedStack.defaults);
|
|
10321
|
+
defineClientField(client, 'math', extendedStack.math);
|
|
10322
|
+
const runtime = getStackRuntimeExtensions(stack);
|
|
10323
|
+
defineClientField(client, 'flows', runtime?.createFlows?.(client));
|
|
10324
|
+
defineClientField(client, 'read', runtime?.createRead?.(client));
|
|
10325
|
+
return client;
|
|
10326
|
+
}
|
|
10327
|
+
function nonEmpty(values, label) {
|
|
10328
|
+
if (values.length === 0) {
|
|
10329
|
+
throw new Error(`${label} must contain at least one item`);
|
|
10330
|
+
}
|
|
10331
|
+
return [...values];
|
|
10332
|
+
}
|
|
10333
|
+
function inferSignerAddress(value) {
|
|
10334
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
10335
|
+
return value;
|
|
10336
|
+
}
|
|
10337
|
+
if (!value || typeof value !== 'object') {
|
|
10338
|
+
return null;
|
|
10339
|
+
}
|
|
10340
|
+
const candidate = value;
|
|
10341
|
+
if (typeof candidate.address === 'string' && candidate.address.length > 0) {
|
|
10342
|
+
return candidate.address;
|
|
10343
|
+
}
|
|
10344
|
+
if (typeof candidate.publicKey === 'string' && candidate.publicKey.length > 0) {
|
|
10345
|
+
return candidate.publicKey;
|
|
10346
|
+
}
|
|
10347
|
+
const publicKey = candidate.publicKey;
|
|
10348
|
+
return typeof publicKey?.toBase58 === 'function' ? publicKey.toBase58() : null;
|
|
10349
|
+
}
|
|
10350
|
+
function validateTransactionSigners(transaction, host, options) {
|
|
10351
|
+
const signerAddresses = (options.signers ?? []).map(inferSignerAddress);
|
|
10352
|
+
const available = new Set(options.availableSignerAddresses ?? []);
|
|
10353
|
+
for (const address of options.signerRegistry?.addresses() ?? []) {
|
|
10354
|
+
available.add(address);
|
|
10355
|
+
}
|
|
10356
|
+
const wallet = options.wallet ?? host.wallet;
|
|
10357
|
+
for (const address of wallet?.signerAddresses ?? []) {
|
|
10358
|
+
available.add(address);
|
|
10359
|
+
}
|
|
10360
|
+
const walletAddress = wallet?.publicKey ?? host.publicKey;
|
|
10361
|
+
if (walletAddress) {
|
|
10362
|
+
available.add(walletAddress);
|
|
10363
|
+
}
|
|
10364
|
+
for (const address of signerAddresses) {
|
|
10365
|
+
if (address) {
|
|
10366
|
+
available.add(address);
|
|
10367
|
+
}
|
|
10368
|
+
}
|
|
10369
|
+
const missing = transaction.requiredSignerAddresses.filter((address) => !available.has(address));
|
|
10370
|
+
if (missing.length > 0) {
|
|
10371
|
+
throw new Error(`Missing signer(s) for ${transaction.name}: ${missing.join(', ')}`);
|
|
10372
|
+
}
|
|
10373
|
+
}
|
|
10374
|
+
class OperationExecutionError extends Error {
|
|
10375
|
+
constructor(input) {
|
|
10376
|
+
super(`Operation '${input.operation.name}' failed at transaction ${input.failedTransactionIndex + 1} (${input.failedTransaction.name})`);
|
|
10377
|
+
this.name = 'OperationExecutionError';
|
|
10378
|
+
this.operation = input.operation;
|
|
10379
|
+
this.failedTransaction = input.failedTransaction;
|
|
10380
|
+
this.failedTransactionIndex = input.failedTransactionIndex;
|
|
10381
|
+
this.completedReceipts = [...input.completedReceipts];
|
|
10382
|
+
this.cause = input.cause;
|
|
10383
|
+
}
|
|
10384
|
+
}
|
|
10385
|
+
async function executePreparedOperation(host, operation, options = {}) {
|
|
10386
|
+
const receipts = [];
|
|
10387
|
+
const signers = [
|
|
10388
|
+
...new Set([
|
|
10389
|
+
...(options.signerRegistry?.values() ?? []),
|
|
10390
|
+
...(options.signers ?? []),
|
|
10391
|
+
]),
|
|
10392
|
+
];
|
|
10393
|
+
for (const [transactionIndex, transaction] of operation.plan.transactions.entries()) {
|
|
10394
|
+
try {
|
|
10395
|
+
validateTransactionSigners(transaction, host, options);
|
|
10396
|
+
await options.onTransactionStart?.({ operation, transaction, transactionIndex });
|
|
10397
|
+
const result = await host.transaction(transaction.instructions, {
|
|
10398
|
+
wallet: options.wallet,
|
|
10399
|
+
send: options.send,
|
|
10400
|
+
errors: [...transaction.errors],
|
|
10401
|
+
signers: signers.length > 0 ? signers : undefined,
|
|
10402
|
+
});
|
|
10403
|
+
const receipt = {
|
|
10404
|
+
transactionIndex,
|
|
10405
|
+
transactionName: transaction.name,
|
|
10406
|
+
signature: result.signature,
|
|
10407
|
+
slot: result.slot,
|
|
10408
|
+
};
|
|
10409
|
+
receipts.push(receipt);
|
|
10410
|
+
await options.onTransactionSuccess?.({
|
|
10411
|
+
operation,
|
|
10412
|
+
transaction,
|
|
10413
|
+
transactionIndex,
|
|
10414
|
+
receipt,
|
|
10415
|
+
});
|
|
10416
|
+
}
|
|
10417
|
+
catch (cause) {
|
|
10418
|
+
throw new OperationExecutionError({
|
|
10419
|
+
operation,
|
|
10420
|
+
failedTransaction: transaction,
|
|
10421
|
+
failedTransactionIndex: transactionIndex,
|
|
10422
|
+
completedReceipts: receipts,
|
|
10423
|
+
cause,
|
|
10424
|
+
});
|
|
10425
|
+
}
|
|
10426
|
+
}
|
|
10427
|
+
if (operation.kind === 'flow') {
|
|
10428
|
+
return {
|
|
10429
|
+
kind: 'flow',
|
|
10430
|
+
operationName: operation.name,
|
|
10431
|
+
artifacts: operation.artifacts,
|
|
10432
|
+
signatures: nonEmpty(receipts.map((receipt) => receipt.signature), `Operation '${operation.name}' signatures`),
|
|
10433
|
+
transactions: nonEmpty(receipts, `Operation '${operation.name}' receipts`),
|
|
10434
|
+
};
|
|
10435
|
+
}
|
|
10436
|
+
return {
|
|
10437
|
+
kind: operation.kind,
|
|
10438
|
+
operationName: operation.name,
|
|
10439
|
+
artifacts: operation.artifacts,
|
|
10440
|
+
signatures: [receipts[0].signature],
|
|
10441
|
+
transaction: receipts[0],
|
|
9159
10442
|
};
|
|
9160
10443
|
}
|
|
9161
10444
|
|
|
9162
|
-
|
|
9163
|
-
|
|
10445
|
+
function mergeAttachedPrograms(stack, attachedPrograms) {
|
|
10446
|
+
const merged = { ...(attachedPrograms ?? {}) };
|
|
10447
|
+
for (const [name, definition] of Object.entries(stack.programs ?? {})) {
|
|
10448
|
+
if (name in merged) {
|
|
10449
|
+
console.warn(`Ignoring attached program '${name}' for stack '${stack.name}' because the stack already defines that key`);
|
|
10450
|
+
}
|
|
10451
|
+
merged[name] = definition;
|
|
10452
|
+
}
|
|
10453
|
+
return merged;
|
|
10454
|
+
}
|
|
10455
|
+
function normalizeProgramAccountWireKeys(value) {
|
|
10456
|
+
if (Array.isArray(value)) {
|
|
10457
|
+
return value.map(normalizeProgramAccountWireKeys);
|
|
10458
|
+
}
|
|
10459
|
+
if (!value || typeof value !== 'object') {
|
|
10460
|
+
return value;
|
|
10461
|
+
}
|
|
10462
|
+
return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [
|
|
10463
|
+
key.replace(/[A-Z]/g, (letter, index) => `${index === 0 ? '' : '_'}${letter.toLowerCase()}`),
|
|
10464
|
+
normalizeProgramAccountWireKeys(nestedValue),
|
|
10465
|
+
]));
|
|
10466
|
+
}
|
|
10467
|
+
function parseProgramAccountValue(definition, value) {
|
|
10468
|
+
const schema = definition.schema;
|
|
10469
|
+
if (!schema) {
|
|
10470
|
+
return value;
|
|
10471
|
+
}
|
|
10472
|
+
const parsed = schema.safeParse(value);
|
|
10473
|
+
if (parsed.success) {
|
|
10474
|
+
return parsed.data;
|
|
10475
|
+
}
|
|
10476
|
+
const normalized = schema.safeParse(normalizeProgramAccountWireKeys(value));
|
|
10477
|
+
if (normalized.success) {
|
|
10478
|
+
return normalized.data;
|
|
10479
|
+
}
|
|
10480
|
+
throw new Error(`Program account read '${definition.account}' failed schema validation`);
|
|
10481
|
+
}
|
|
10482
|
+
function cloneStackWithPrograms(stack, programs) {
|
|
10483
|
+
const cloned = Object.create(Object.getPrototypeOf(stack), Object.getOwnPropertyDescriptors(stack));
|
|
10484
|
+
cloned.programs = programs;
|
|
10485
|
+
return cloned;
|
|
10486
|
+
}
|
|
10487
|
+
function withPrograms(stack, attachedPrograms) {
|
|
10488
|
+
return cloneStackWithPrograms(stack, mergeAttachedPrograms(stack, attachedPrograms));
|
|
10489
|
+
}
|
|
10490
|
+
let Arete$1 = class Arete {
|
|
10491
|
+
constructor(url, httpBaseUrl, options) {
|
|
9164
10492
|
this.stack = options.stack;
|
|
10493
|
+
this._wallet = options.wallet;
|
|
10494
|
+
this.executionDefaults = options.execution;
|
|
10495
|
+
this.httpBaseUrl = httpBaseUrl;
|
|
10496
|
+
this.fetchImpl = options.fetch ?? this.resolveFetchImpl();
|
|
9165
10497
|
this.storage = new SortedStorageDecorator(options.storage ?? new MemoryAdapter());
|
|
9166
10498
|
this.processor = new FrameProcessor(this.storage, {
|
|
9167
10499
|
maxEntriesPerView: options.maxEntriesPerView,
|
|
9168
10500
|
flushIntervalMs: options.flushIntervalMs,
|
|
9169
|
-
schemas:
|
|
10501
|
+
schemas: this.stack.schemas,
|
|
10502
|
+
patchSchemas: this.stack.patchSchemas,
|
|
9170
10503
|
});
|
|
9171
10504
|
this.connection = new ConnectionManager({
|
|
9172
10505
|
websocketUrl: url,
|
|
@@ -9179,26 +10512,178 @@ class Arete {
|
|
|
9179
10512
|
this.processor.handleFrame(frame);
|
|
9180
10513
|
});
|
|
9181
10514
|
this._views = createTypedViews(this.stack, this.storage, this.subscriptionRegistry);
|
|
9182
|
-
this.
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
10515
|
+
this._queries = this.buildQueries();
|
|
10516
|
+
this._chain = createChainClient(this.httpBaseUrl, this.authenticatedFetch.bind(this));
|
|
10517
|
+
this._programs = this.buildPrograms();
|
|
10518
|
+
}
|
|
10519
|
+
resolveFetchImpl() {
|
|
10520
|
+
if (typeof globalThis.fetch !== 'function') {
|
|
10521
|
+
throw new AreteError('A fetch implementation is required for HTTP point reads (provide ConnectOptions.fetch or use an environment with global fetch)', 'INVALID_CONFIG');
|
|
10522
|
+
}
|
|
10523
|
+
return globalThis.fetch.bind(globalThis);
|
|
10524
|
+
}
|
|
10525
|
+
buildQueries() {
|
|
10526
|
+
const queries = {};
|
|
10527
|
+
for (const [name, definition] of Object.entries(this.stack.queries ?? {})) {
|
|
10528
|
+
queries[name] = this.createQueryExecutor(definition);
|
|
10529
|
+
}
|
|
10530
|
+
return queries;
|
|
10531
|
+
}
|
|
10532
|
+
buildPrograms() {
|
|
10533
|
+
const bases = {};
|
|
10534
|
+
for (const [name, definition] of Object.entries(this.stack.programs ?? {})) {
|
|
10535
|
+
const instructions = {};
|
|
10536
|
+
for (const [instructionName, handler] of Object.entries(definition.rawInstructions ?? {})) {
|
|
10537
|
+
instructions[instructionName] = this.createTypedInstruction(handler);
|
|
9191
10538
|
}
|
|
10539
|
+
const accounts = {};
|
|
10540
|
+
for (const [accountName, accountDefinition] of Object.entries(definition.accounts ?? {})) {
|
|
10541
|
+
accounts[accountName] = this.createAccountReader(accountDefinition);
|
|
10542
|
+
}
|
|
10543
|
+
const queries = {};
|
|
10544
|
+
for (const [queryName, queryDefinition] of Object.entries(definition.queries ?? {})) {
|
|
10545
|
+
queries[queryName] = this.createQueryExecutor(queryDefinition);
|
|
10546
|
+
}
|
|
10547
|
+
bases[name] = {
|
|
10548
|
+
name: definition.name,
|
|
10549
|
+
programId: definition.programId,
|
|
10550
|
+
schemas: definition.schemas,
|
|
10551
|
+
pdas: definition.pdas ?? {},
|
|
10552
|
+
accounts,
|
|
10553
|
+
queries,
|
|
10554
|
+
raw: instructions,
|
|
10555
|
+
addresses: definition.addresses ?? {},
|
|
10556
|
+
constants: definition.constants ?? {},
|
|
10557
|
+
defaults: definition.defaults ?? {},
|
|
10558
|
+
math: definition.math ?? {},
|
|
10559
|
+
};
|
|
10560
|
+
}
|
|
10561
|
+
const programs = {};
|
|
10562
|
+
const client = this;
|
|
10563
|
+
for (const [name, definition] of Object.entries(this.stack.programs ?? {})) {
|
|
10564
|
+
const base = bases[name];
|
|
10565
|
+
const connectedProgram = {
|
|
10566
|
+
...base,
|
|
10567
|
+
instructions: {},
|
|
10568
|
+
transactions: {},
|
|
10569
|
+
flows: {},
|
|
10570
|
+
};
|
|
10571
|
+
const runtime = getProgramRuntimeExtensions(definition);
|
|
10572
|
+
const operations = runtime?.createOperations({
|
|
10573
|
+
chain: this._chain,
|
|
10574
|
+
get wallet() {
|
|
10575
|
+
return client._wallet;
|
|
10576
|
+
},
|
|
10577
|
+
program: connectedProgram,
|
|
10578
|
+
});
|
|
10579
|
+
connectedProgram.instructions = operations?.instructions ?? {};
|
|
10580
|
+
connectedProgram.transactions = operations?.transactions ?? {};
|
|
10581
|
+
connectedProgram.flows = operations?.flows ?? {};
|
|
10582
|
+
programs[name] = connectedProgram;
|
|
9192
10583
|
}
|
|
9193
|
-
return
|
|
10584
|
+
return programs;
|
|
9194
10585
|
}
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
10586
|
+
createTypedInstruction(handler) {
|
|
10587
|
+
return {
|
|
10588
|
+
build: (params, options) => buildInstruction(handler, params, this.withWallet(options)),
|
|
10589
|
+
};
|
|
10590
|
+
}
|
|
10591
|
+
createAccountReader(definition) {
|
|
10592
|
+
return {
|
|
10593
|
+
fetch: async (address) => {
|
|
10594
|
+
const result = await this.readJson(`${definition.path}/${encodeURIComponent(address)}`);
|
|
10595
|
+
return result === null ? null : parseProgramAccountValue(definition, result);
|
|
10596
|
+
},
|
|
10597
|
+
fetchMany: async (addresses) => {
|
|
10598
|
+
const result = await this.readJson(definition.path, {
|
|
10599
|
+
method: 'POST',
|
|
10600
|
+
body: { addresses },
|
|
10601
|
+
});
|
|
10602
|
+
return result.map((value) => value === null ? null : parseProgramAccountValue(definition, value));
|
|
10603
|
+
},
|
|
10604
|
+
exists: async (address) => {
|
|
10605
|
+
const result = await this.readJson(`${definition.path}/${encodeURIComponent(address)}/exists`);
|
|
10606
|
+
return result.exists;
|
|
10607
|
+
},
|
|
10608
|
+
};
|
|
10609
|
+
}
|
|
10610
|
+
createQueryExecutor(definition) {
|
|
10611
|
+
return async (params) => {
|
|
10612
|
+
const result = await this.readJson(definition.path, {
|
|
10613
|
+
method: definition.method ?? 'POST',
|
|
10614
|
+
body: params,
|
|
10615
|
+
});
|
|
10616
|
+
if (!definition.schema) {
|
|
10617
|
+
return result;
|
|
10618
|
+
}
|
|
10619
|
+
const parsed = definition.schema.safeParse(result);
|
|
10620
|
+
if (!parsed.success) {
|
|
10621
|
+
throw new Error(`Query '${definition.name}' failed schema validation`);
|
|
10622
|
+
}
|
|
10623
|
+
return parsed.data;
|
|
10624
|
+
};
|
|
10625
|
+
}
|
|
10626
|
+
async readJson(path, options) {
|
|
10627
|
+
const response = await this.authenticatedFetch(this.resolveReadUrl(path), {
|
|
10628
|
+
method: options?.method ?? 'GET',
|
|
10629
|
+
headers: options?.body === undefined ? undefined : { 'content-type': 'application/json' },
|
|
10630
|
+
body: options?.body === undefined ? undefined : JSON.stringify(options.body),
|
|
10631
|
+
});
|
|
10632
|
+
return parseReadResponse(response, path);
|
|
10633
|
+
}
|
|
10634
|
+
async authenticatedFetch(input, init) {
|
|
10635
|
+
const attempt = async (forceRefresh = false) => {
|
|
10636
|
+
const token = await this.connection.getHttpAuthToken(forceRefresh);
|
|
10637
|
+
const headers = new Headers(init?.headers ?? undefined);
|
|
10638
|
+
if (token) {
|
|
10639
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
10640
|
+
}
|
|
10641
|
+
return this.fetchImpl(input, {
|
|
10642
|
+
...init,
|
|
10643
|
+
headers,
|
|
10644
|
+
});
|
|
10645
|
+
};
|
|
10646
|
+
let response = await attempt(false);
|
|
10647
|
+
if (!response.ok) {
|
|
10648
|
+
const wireErrorCode = response.headers.get('X-Error-Code');
|
|
10649
|
+
const errorCode = wireErrorCode ? parseErrorCode(wireErrorCode) : undefined;
|
|
10650
|
+
if (errorCode && shouldRefreshToken(errorCode)) {
|
|
10651
|
+
this.connection.clearHttpAuthToken();
|
|
10652
|
+
response = await attempt(true);
|
|
10653
|
+
}
|
|
9199
10654
|
}
|
|
10655
|
+
return response;
|
|
10656
|
+
}
|
|
10657
|
+
resolveReadUrl(path) {
|
|
10658
|
+
return `${this.httpBaseUrl.replace(/\/$/, '')}${path.startsWith('/') ? path : `/${path}`}`;
|
|
10659
|
+
}
|
|
10660
|
+
/** Merge the client's default wallet into call options (call options win). */
|
|
10661
|
+
withWallet(options) {
|
|
10662
|
+
const merged = { ...(options ?? {}) };
|
|
10663
|
+
if (!merged.wallet && this._wallet) {
|
|
10664
|
+
merged.wallet = this._wallet;
|
|
10665
|
+
}
|
|
10666
|
+
return merged;
|
|
10667
|
+
}
|
|
10668
|
+
static async connect(stack, options) {
|
|
10669
|
+
const requestedUrl = options?.url ?? stack.endpoints.ws;
|
|
10670
|
+
const autoReconnect = options?.autoReconnect !== false;
|
|
10671
|
+
const httpOnly = options?.transport === 'http' || (!requestedUrl && !autoReconnect);
|
|
10672
|
+
const url = httpOnly ? null : requestedUrl;
|
|
10673
|
+
const httpUrl = options?.httpUrl
|
|
10674
|
+
?? stack.endpoints.http
|
|
10675
|
+
?? (requestedUrl ? deriveHttpEndpoint(requestedUrl) : undefined);
|
|
10676
|
+
if (!httpOnly && !url) {
|
|
10677
|
+
throw new AreteError('WebSocket URL is required (provide url option or define endpoints.ws in stack)', 'INVALID_CONFIG');
|
|
10678
|
+
}
|
|
10679
|
+
if (!httpUrl) {
|
|
10680
|
+
throw new AreteError('HTTP endpoint is required for transport: "http" (provide httpUrl option or define endpoints.http in stack)', 'INVALID_CONFIG');
|
|
10681
|
+
}
|
|
10682
|
+
const attachedPrograms = options?.programs;
|
|
10683
|
+
const effectiveStack = withPrograms(stack, attachedPrograms);
|
|
9200
10684
|
const internalOptions = {
|
|
9201
|
-
stack,
|
|
10685
|
+
stack: effectiveStack,
|
|
10686
|
+
httpUrl,
|
|
9202
10687
|
storage: options?.storage,
|
|
9203
10688
|
maxEntriesPerView: options?.maxEntriesPerView,
|
|
9204
10689
|
flushIntervalMs: options?.flushIntervalMs,
|
|
@@ -9207,18 +10692,113 @@ class Arete {
|
|
|
9207
10692
|
maxReconnectAttempts: options?.maxReconnectAttempts,
|
|
9208
10693
|
validateFrames: options?.validateFrames,
|
|
9209
10694
|
auth: options?.auth,
|
|
10695
|
+
wallet: options?.wallet,
|
|
10696
|
+
fetch: options?.fetch,
|
|
10697
|
+
execution: options?.execution,
|
|
9210
10698
|
};
|
|
9211
|
-
const client = new Arete(url, internalOptions);
|
|
9212
|
-
if (
|
|
10699
|
+
const client = new Arete(url, httpUrl, internalOptions);
|
|
10700
|
+
if (!httpOnly && autoReconnect) {
|
|
9213
10701
|
await client.connection.connect();
|
|
9214
10702
|
}
|
|
9215
|
-
return client;
|
|
10703
|
+
return applyConnectedStackExtensions(client, effectiveStack);
|
|
9216
10704
|
}
|
|
9217
10705
|
get views() {
|
|
9218
10706
|
return this._views;
|
|
9219
10707
|
}
|
|
9220
|
-
get
|
|
9221
|
-
return this.
|
|
10708
|
+
get queries() {
|
|
10709
|
+
return this._queries;
|
|
10710
|
+
}
|
|
10711
|
+
get programs() {
|
|
10712
|
+
return this._programs;
|
|
10713
|
+
}
|
|
10714
|
+
get chain() {
|
|
10715
|
+
return this._chain;
|
|
10716
|
+
}
|
|
10717
|
+
/** The default wallet adapter, if one was configured. */
|
|
10718
|
+
get wallet() {
|
|
10719
|
+
return this._wallet;
|
|
10720
|
+
}
|
|
10721
|
+
/** The connected wallet address, if a default wallet was configured. */
|
|
10722
|
+
get publicKey() {
|
|
10723
|
+
return this._wallet?.publicKey;
|
|
10724
|
+
}
|
|
10725
|
+
/**
|
|
10726
|
+
* Set (or clear) the default wallet adapter used for instruction execution.
|
|
10727
|
+
* Useful for connecting/disconnecting a wallet after the client is created.
|
|
10728
|
+
*/
|
|
10729
|
+
setWallet(wallet) {
|
|
10730
|
+
this._wallet = wallet;
|
|
10731
|
+
}
|
|
10732
|
+
/**
|
|
10733
|
+
* Sign and send a batch of pre-built instructions as a single transaction.
|
|
10734
|
+
*
|
|
10735
|
+
* Build instructions with `client.programs.<program>.raw.<name>.build(params)`
|
|
10736
|
+
* and compose them here. RPC/compilation/confirmation are owned by the adapter.
|
|
10737
|
+
*
|
|
10738
|
+
* On failure, the error is parsed against `options.errors` when given,
|
|
10739
|
+
* otherwise against error metadata aggregated from all the stack's handlers
|
|
10740
|
+
* (deduped by code, first-wins — if the stack bundles programs with
|
|
10741
|
+
* overlapping error codes, pass `options.errors` or use the per-instruction
|
|
10742
|
+
* call path for precise attribution).
|
|
10743
|
+
*/
|
|
10744
|
+
async transaction(instructions, options) {
|
|
10745
|
+
const wallet = options?.wallet ?? this._wallet;
|
|
10746
|
+
if (!wallet) {
|
|
10747
|
+
throw new Error('Wallet required to sign and send transaction');
|
|
10748
|
+
}
|
|
10749
|
+
try {
|
|
10750
|
+
const sendOptions = {
|
|
10751
|
+
...(options?.send ?? {}),
|
|
10752
|
+
};
|
|
10753
|
+
if (options?.signers !== undefined) {
|
|
10754
|
+
sendOptions.signers = options.signers;
|
|
10755
|
+
}
|
|
10756
|
+
const result = await wallet.signAndSend(instructions, sendOptions);
|
|
10757
|
+
return { signature: result.signature, slot: result.slot };
|
|
10758
|
+
}
|
|
10759
|
+
catch (err) {
|
|
10760
|
+
const programError = parseInstructionError(err, options?.errors ?? this.aggregateErrors());
|
|
10761
|
+
if (programError) {
|
|
10762
|
+
throw new InstructionError(`${programError.name} (${programError.code}): ${programError.message}`, programError, err);
|
|
10763
|
+
}
|
|
10764
|
+
throw err;
|
|
10765
|
+
}
|
|
10766
|
+
}
|
|
10767
|
+
execute(prepared, options) {
|
|
10768
|
+
const defaults = this.executionDefaults;
|
|
10769
|
+
return executePreparedOperation(this, prepared, {
|
|
10770
|
+
wallet: options?.wallet ?? defaults?.wallet,
|
|
10771
|
+
send: defaults?.send || options?.send
|
|
10772
|
+
? { ...(defaults?.send ?? {}), ...(options?.send ?? {}) }
|
|
10773
|
+
: undefined,
|
|
10774
|
+
signers: options?.signers ?? defaults?.signers,
|
|
10775
|
+
signerRegistry: options?.signerRegistry ?? defaults?.signerRegistry,
|
|
10776
|
+
availableSignerAddresses: options?.availableSignerAddresses ?? defaults?.availableSignerAddresses,
|
|
10777
|
+
onTransactionStart: options?.onTransactionStart ?? defaults?.onTransactionStart,
|
|
10778
|
+
onTransactionSuccess: options?.onTransactionSuccess ?? defaults?.onTransactionSuccess,
|
|
10779
|
+
});
|
|
10780
|
+
}
|
|
10781
|
+
/** Error metadata from every handler in the stack, deduped by code. */
|
|
10782
|
+
aggregateErrors() {
|
|
10783
|
+
if (!this._aggregatedErrors) {
|
|
10784
|
+
const all = [];
|
|
10785
|
+
const seen = new Set();
|
|
10786
|
+
const collect = (handler) => {
|
|
10787
|
+
for (const error of handler.errors ?? []) {
|
|
10788
|
+
if (!seen.has(error.code)) {
|
|
10789
|
+
seen.add(error.code);
|
|
10790
|
+
all.push(error);
|
|
10791
|
+
}
|
|
10792
|
+
}
|
|
10793
|
+
};
|
|
10794
|
+
for (const program of Object.values(this.stack.programs ?? {})) {
|
|
10795
|
+
for (const handler of Object.values(program.rawInstructions ?? {})) {
|
|
10796
|
+
collect(handler);
|
|
10797
|
+
}
|
|
10798
|
+
}
|
|
10799
|
+
this._aggregatedErrors = all;
|
|
10800
|
+
}
|
|
10801
|
+
return this._aggregatedErrors;
|
|
9222
10802
|
}
|
|
9223
10803
|
get connectionState() {
|
|
9224
10804
|
return this.connection.getState();
|
|
@@ -9260,8 +10840,180 @@ class Arete {
|
|
|
9260
10840
|
getSubscriptionRegistry() {
|
|
9261
10841
|
return this.subscriptionRegistry;
|
|
9262
10842
|
}
|
|
10843
|
+
};
|
|
10844
|
+
|
|
10845
|
+
function createSignerRegistry(entries = []) {
|
|
10846
|
+
const signers = new Map();
|
|
10847
|
+
for (const [address, signer] of entries) {
|
|
10848
|
+
if (!address) {
|
|
10849
|
+
throw new Error('Signer registry addresses must not be empty');
|
|
10850
|
+
}
|
|
10851
|
+
signers.set(address, signer);
|
|
10852
|
+
}
|
|
10853
|
+
return {
|
|
10854
|
+
register(address, signer) {
|
|
10855
|
+
if (!address) {
|
|
10856
|
+
throw new Error('Signer registry addresses must not be empty');
|
|
10857
|
+
}
|
|
10858
|
+
signers.set(address, signer);
|
|
10859
|
+
},
|
|
10860
|
+
unregister(address) {
|
|
10861
|
+
return signers.delete(address);
|
|
10862
|
+
},
|
|
10863
|
+
get(address) {
|
|
10864
|
+
return signers.get(address);
|
|
10865
|
+
},
|
|
10866
|
+
has(address) {
|
|
10867
|
+
return signers.has(address);
|
|
10868
|
+
},
|
|
10869
|
+
addresses() {
|
|
10870
|
+
return [...signers.keys()];
|
|
10871
|
+
},
|
|
10872
|
+
values() {
|
|
10873
|
+
return [...signers.values()];
|
|
10874
|
+
},
|
|
10875
|
+
entries() {
|
|
10876
|
+
return [...signers.entries()];
|
|
10877
|
+
},
|
|
10878
|
+
clear() {
|
|
10879
|
+
signers.clear();
|
|
10880
|
+
},
|
|
10881
|
+
};
|
|
9263
10882
|
}
|
|
9264
10883
|
|
|
10884
|
+
function resolveMemberConnectOptions(stack, member, options, forceHttpOnly) {
|
|
10885
|
+
const transport = member?.transport ?? options?.transport ?? (forceHttpOnly ? 'http' : 'ws');
|
|
10886
|
+
// Resolution order: per-member override → the stack's own endpoints →
|
|
10887
|
+
// session-wide fallback endpoints.
|
|
10888
|
+
const url = member?.url ?? (stack.endpoints.ws || options?.endpoints?.ws);
|
|
10889
|
+
const httpUrl = member?.httpUrl ?? stack.endpoints.http ?? options?.endpoints?.http;
|
|
10890
|
+
return {
|
|
10891
|
+
url,
|
|
10892
|
+
httpUrl,
|
|
10893
|
+
transport,
|
|
10894
|
+
auth: member?.auth ?? options?.auth,
|
|
10895
|
+
storage: member?.storage,
|
|
10896
|
+
autoReconnect: member?.autoReconnect,
|
|
10897
|
+
wallet: options?.wallet,
|
|
10898
|
+
fetch: options?.fetch,
|
|
10899
|
+
};
|
|
10900
|
+
}
|
|
10901
|
+
function programAsStack(name, program) {
|
|
10902
|
+
return {
|
|
10903
|
+
name,
|
|
10904
|
+
endpoints: { ws: '' },
|
|
10905
|
+
views: {},
|
|
10906
|
+
programs: { [name]: program },
|
|
10907
|
+
};
|
|
10908
|
+
}
|
|
10909
|
+
async function createSession(definition, options) {
|
|
10910
|
+
const stackEntries = Object.entries(definition.stacks ?? {});
|
|
10911
|
+
const programEntries = Object.entries(definition.programs ?? {});
|
|
10912
|
+
if (stackEntries.length === 0 && programEntries.length === 0) {
|
|
10913
|
+
throw new AreteError('createSession requires at least one stack or program member', 'INVALID_CONFIG');
|
|
10914
|
+
}
|
|
10915
|
+
let wallet = options?.wallet;
|
|
10916
|
+
const signerRegistry = options?.signerRegistry ?? createSignerRegistry();
|
|
10917
|
+
const connectedStacks = await Promise.all(stackEntries.map(async ([key, stack]) => {
|
|
10918
|
+
const memberOptions = options?.stacks?.[key];
|
|
10919
|
+
const effectiveStack = withPrograms(stack, memberOptions?.programs);
|
|
10920
|
+
const connectOptions = resolveMemberConnectOptions(effectiveStack, memberOptions, options, false);
|
|
10921
|
+
const client = await Arete$1.connect(effectiveStack, connectOptions);
|
|
10922
|
+
return [key, client];
|
|
10923
|
+
}));
|
|
10924
|
+
const connectedPrograms = await Promise.all(programEntries.map(async ([key, program]) => {
|
|
10925
|
+
const syntheticStack = programAsStack(key, program);
|
|
10926
|
+
const connectOptions = resolveMemberConnectOptions(syntheticStack, options?.programs?.[key], options, true);
|
|
10927
|
+
const client = await Arete$1.connect(syntheticStack, connectOptions);
|
|
10928
|
+
return [key, client];
|
|
10929
|
+
}));
|
|
10930
|
+
const memberClients = [
|
|
10931
|
+
...connectedStacks.map(([, client]) => client),
|
|
10932
|
+
...connectedPrograms.map(([, client]) => client),
|
|
10933
|
+
];
|
|
10934
|
+
const executionHost = memberClients[0];
|
|
10935
|
+
const stacks = Object.fromEntries(connectedStacks);
|
|
10936
|
+
const explicitProgramKeys = new Set(connectedPrograms.map(([key]) => key));
|
|
10937
|
+
const programOwners = new Map();
|
|
10938
|
+
const connectedProgramEntries = connectedPrograms.map(([key, client]) => [
|
|
10939
|
+
key,
|
|
10940
|
+
client.programs[key],
|
|
10941
|
+
]);
|
|
10942
|
+
const promotedPrograms = Object.fromEntries(connectedProgramEntries);
|
|
10943
|
+
for (const [stackKey, client] of connectedStacks) {
|
|
10944
|
+
for (const [programKey, program] of Object.entries(client.programs)) {
|
|
10945
|
+
if (explicitProgramKeys.has(programKey)) {
|
|
10946
|
+
continue;
|
|
10947
|
+
}
|
|
10948
|
+
const existingOwner = programOwners.get(programKey);
|
|
10949
|
+
if (existingOwner) {
|
|
10950
|
+
console.warn(`Program '${programKey}' is bundled by stacks '${existingOwner.stack}'` +
|
|
10951
|
+
` (${existingOwner.programId ?? 'unknown program ID'}) and '${stackKey}'` +
|
|
10952
|
+
` (${program.programId ?? 'unknown program ID'}); session.programs.${programKey}` +
|
|
10953
|
+
` uses '${existingOwner.stack}' because it was connected first`);
|
|
10954
|
+
continue;
|
|
10955
|
+
}
|
|
10956
|
+
promotedPrograms[programKey] = program;
|
|
10957
|
+
programOwners.set(programKey, { stack: stackKey, programId: program.programId });
|
|
10958
|
+
}
|
|
10959
|
+
}
|
|
10960
|
+
const programs = promotedPrograms;
|
|
10961
|
+
const chain = options?.chain ?? (options?.endpoints?.http !== undefined
|
|
10962
|
+
? createChainClient(options.endpoints.http, (options?.fetch ?? globalThis.fetch?.bind(globalThis)))
|
|
10963
|
+
: executionHost.chain);
|
|
10964
|
+
const session = {
|
|
10965
|
+
stacks,
|
|
10966
|
+
programs,
|
|
10967
|
+
get wallet() {
|
|
10968
|
+
return wallet;
|
|
10969
|
+
},
|
|
10970
|
+
signerRegistry,
|
|
10971
|
+
chain,
|
|
10972
|
+
transaction(instructions, transactionOptions) {
|
|
10973
|
+
const defaults = options?.execution;
|
|
10974
|
+
const configuredSigners = transactionOptions?.signers ?? defaults?.signers;
|
|
10975
|
+
const signers = [...new Set([...signerRegistry.values(), ...(configuredSigners ?? [])])];
|
|
10976
|
+
return executionHost.transaction(instructions, {
|
|
10977
|
+
wallet: transactionOptions?.wallet ?? defaults?.wallet,
|
|
10978
|
+
send: defaults?.send || transactionOptions?.send
|
|
10979
|
+
? { ...(defaults?.send ?? {}), ...(transactionOptions?.send ?? {}) }
|
|
10980
|
+
: undefined,
|
|
10981
|
+
errors: transactionOptions?.errors,
|
|
10982
|
+
signers: signers.length > 0 ? signers : undefined,
|
|
10983
|
+
});
|
|
10984
|
+
},
|
|
10985
|
+
execute(prepared, executionOptions) {
|
|
10986
|
+
const defaults = options?.execution;
|
|
10987
|
+
const configuredSigners = executionOptions?.signers ?? defaults?.signers;
|
|
10988
|
+
return executionHost.execute(prepared, {
|
|
10989
|
+
wallet: executionOptions?.wallet ?? defaults?.wallet,
|
|
10990
|
+
send: defaults?.send || executionOptions?.send
|
|
10991
|
+
? { ...(defaults?.send ?? {}), ...(executionOptions?.send ?? {}) }
|
|
10992
|
+
: undefined,
|
|
10993
|
+
signers: configuredSigners,
|
|
10994
|
+
signerRegistry,
|
|
10995
|
+
availableSignerAddresses: executionOptions?.availableSignerAddresses ?? defaults?.availableSignerAddresses,
|
|
10996
|
+
onTransactionStart: executionOptions?.onTransactionStart ?? defaults?.onTransactionStart,
|
|
10997
|
+
onTransactionSuccess: executionOptions?.onTransactionSuccess ?? defaults?.onTransactionSuccess,
|
|
10998
|
+
});
|
|
10999
|
+
},
|
|
11000
|
+
setWallet(nextWallet) {
|
|
11001
|
+
wallet = nextWallet;
|
|
11002
|
+
for (const client of memberClients) {
|
|
11003
|
+
client.setWallet(nextWallet);
|
|
11004
|
+
}
|
|
11005
|
+
},
|
|
11006
|
+
close() {
|
|
11007
|
+
for (const client of memberClients) {
|
|
11008
|
+
client.disconnect();
|
|
11009
|
+
}
|
|
11010
|
+
},
|
|
11011
|
+
};
|
|
11012
|
+
return session;
|
|
11013
|
+
}
|
|
11014
|
+
|
|
11015
|
+
const Arete = Object.assign(Arete$1, { session: createSession });
|
|
11016
|
+
|
|
9265
11017
|
const DEFAULT_FLUSH_INTERVAL_MS = 16;
|
|
9266
11018
|
|
|
9267
11019
|
class ZustandAdapter {
|
|
@@ -9430,11 +11182,52 @@ class ZustandAdapter {
|
|
|
9430
11182
|
}
|
|
9431
11183
|
}
|
|
9432
11184
|
|
|
11185
|
+
function syncClientWallets(entries, wallet) {
|
|
11186
|
+
for (const entry of entries) {
|
|
11187
|
+
entry.client.setWallet(wallet);
|
|
11188
|
+
}
|
|
11189
|
+
}
|
|
11190
|
+
function initializeConnectedClient(client, adapter, wallet) {
|
|
11191
|
+
client.setWallet(wallet);
|
|
11192
|
+
client.onConnectionStateChange((state) => {
|
|
11193
|
+
adapter.setConnectionState(state);
|
|
11194
|
+
});
|
|
11195
|
+
adapter.setConnectionState(client.connectionState);
|
|
11196
|
+
}
|
|
11197
|
+
|
|
11198
|
+
const objectKeys = new WeakMap();
|
|
11199
|
+
let nextObjectKey = 0;
|
|
11200
|
+
function getObjectKey(value, prefix) {
|
|
11201
|
+
const existing = objectKeys.get(value);
|
|
11202
|
+
if (existing) {
|
|
11203
|
+
return existing;
|
|
11204
|
+
}
|
|
11205
|
+
const key = `${prefix}-${++nextObjectKey}`;
|
|
11206
|
+
objectKeys.set(value, key);
|
|
11207
|
+
return key;
|
|
11208
|
+
}
|
|
11209
|
+
function createClientCacheKey(stack, options) {
|
|
11210
|
+
if (!stack) {
|
|
11211
|
+
return null;
|
|
11212
|
+
}
|
|
11213
|
+
return [
|
|
11214
|
+
getObjectKey(stack, 'stack'),
|
|
11215
|
+
options?.transport ?? 'ws',
|
|
11216
|
+
options?.url ?? stack.endpoints.ws,
|
|
11217
|
+
options?.httpUrl ?? stack.endpoints.http ?? '',
|
|
11218
|
+
options?.programs
|
|
11219
|
+
? getObjectKey(options.programs, 'programs')
|
|
11220
|
+
: 'programs-none',
|
|
11221
|
+
].join(':');
|
|
11222
|
+
}
|
|
11223
|
+
|
|
9433
11224
|
const AreteContext = createContext(null);
|
|
9434
11225
|
function AreteProvider({ children, fallback = null, ...config }) {
|
|
9435
11226
|
const clientsRef = useRef(new Map());
|
|
9436
11227
|
const connectingRef = useRef(new Map());
|
|
9437
11228
|
const clientChangeListenersRef = useRef(new Set());
|
|
11229
|
+
const latestWalletRef = useRef(config.wallet);
|
|
11230
|
+
latestWalletRef.current = config.wallet;
|
|
9438
11231
|
const notifyClientChange = useCallback(() => {
|
|
9439
11232
|
clientChangeListenersRef.current.forEach(cb => { cb(); });
|
|
9440
11233
|
}, []);
|
|
@@ -9444,8 +11237,11 @@ function AreteProvider({ children, fallback = null, ...config }) {
|
|
|
9444
11237
|
clientChangeListenersRef.current.delete(callback);
|
|
9445
11238
|
};
|
|
9446
11239
|
}, []);
|
|
9447
|
-
const getOrCreateClient = useCallback(async (stack,
|
|
9448
|
-
const cacheKey =
|
|
11240
|
+
const getOrCreateClient = useCallback(async (stack, options) => {
|
|
11241
|
+
const cacheKey = createClientCacheKey(stack, options);
|
|
11242
|
+
if (!cacheKey) {
|
|
11243
|
+
throw new Error('Stack is required to create an Arete client');
|
|
11244
|
+
}
|
|
9449
11245
|
const existing = clientsRef.current.get(cacheKey);
|
|
9450
11246
|
if (existing) {
|
|
9451
11247
|
return existing.client;
|
|
@@ -9456,21 +11252,24 @@ function AreteProvider({ children, fallback = null, ...config }) {
|
|
|
9456
11252
|
}
|
|
9457
11253
|
const adapter = new ZustandAdapter();
|
|
9458
11254
|
const connectionPromise = Arete.connect(stack, {
|
|
9459
|
-
url:
|
|
11255
|
+
url: options?.url,
|
|
11256
|
+
httpUrl: options?.httpUrl,
|
|
11257
|
+
transport: options?.transport,
|
|
11258
|
+
programs: options?.programs,
|
|
9460
11259
|
storage: adapter,
|
|
9461
11260
|
autoReconnect: config.autoConnect,
|
|
9462
11261
|
reconnectIntervals: config.reconnectIntervals,
|
|
9463
11262
|
maxReconnectAttempts: config.maxReconnectAttempts,
|
|
9464
11263
|
maxEntriesPerView: config.maxEntriesPerView,
|
|
9465
11264
|
flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
11265
|
+
fetch: config.fetch,
|
|
11266
|
+
validateFrames: config.validateFrames,
|
|
9466
11267
|
auth: config.auth,
|
|
11268
|
+
wallet: latestWalletRef.current,
|
|
9467
11269
|
}).then((client) => {
|
|
9468
|
-
client
|
|
9469
|
-
adapter.setConnectionState(state, error);
|
|
9470
|
-
});
|
|
9471
|
-
adapter.setConnectionState(client.connectionState);
|
|
11270
|
+
initializeConnectedClient(client, adapter, latestWalletRef.current);
|
|
9472
11271
|
clientsRef.current.set(cacheKey, {
|
|
9473
|
-
client,
|
|
11272
|
+
client: client,
|
|
9474
11273
|
disconnect: () => client.disconnect()
|
|
9475
11274
|
});
|
|
9476
11275
|
connectingRef.current.delete(cacheKey);
|
|
@@ -9479,8 +11278,8 @@ function AreteProvider({ children, fallback = null, ...config }) {
|
|
|
9479
11278
|
});
|
|
9480
11279
|
connectingRef.current.set(cacheKey, connectionPromise);
|
|
9481
11280
|
return connectionPromise;
|
|
9482
|
-
}, [config.autoConnect, config.reconnectIntervals, config.maxReconnectAttempts, config.maxEntriesPerView, config.flushIntervalMs, config.auth, notifyClientChange]);
|
|
9483
|
-
const getClient = useCallback((stack) => {
|
|
11281
|
+
}, [config.autoConnect, config.reconnectIntervals, config.maxReconnectAttempts, config.maxEntriesPerView, config.flushIntervalMs, config.fetch, config.validateFrames, config.auth, notifyClientChange]);
|
|
11282
|
+
const getClient = useCallback((stack, options) => {
|
|
9484
11283
|
if (!stack) {
|
|
9485
11284
|
if (clientsRef.current.size === 1) {
|
|
9486
11285
|
const firstEntry = clientsRef.current.values().next().value;
|
|
@@ -9488,9 +11287,17 @@ function AreteProvider({ children, fallback = null, ...config }) {
|
|
|
9488
11287
|
}
|
|
9489
11288
|
return null;
|
|
9490
11289
|
}
|
|
9491
|
-
const
|
|
11290
|
+
const cacheKey = createClientCacheKey(stack, options);
|
|
11291
|
+
if (!cacheKey) {
|
|
11292
|
+
return null;
|
|
11293
|
+
}
|
|
11294
|
+
const entry = clientsRef.current.get(cacheKey);
|
|
9492
11295
|
return entry ? entry.client : null;
|
|
9493
11296
|
}, []);
|
|
11297
|
+
useEffect(() => {
|
|
11298
|
+
syncClientWallets(clientsRef.current.values(), config.wallet);
|
|
11299
|
+
notifyClientChange();
|
|
11300
|
+
}, [config.wallet, notifyClientChange]);
|
|
9494
11301
|
useEffect(() => {
|
|
9495
11302
|
return () => {
|
|
9496
11303
|
clientsRef.current.forEach((entry) => {
|
|
@@ -9515,10 +11322,10 @@ function useAreteContext() {
|
|
|
9515
11322
|
}
|
|
9516
11323
|
return context;
|
|
9517
11324
|
}
|
|
9518
|
-
function useConnectionState(stack) {
|
|
11325
|
+
function useConnectionState(stack, options) {
|
|
9519
11326
|
const { getClient, subscribeToClientChanges } = useAreteContext();
|
|
9520
11327
|
const [state, setState] = React.useState(() => {
|
|
9521
|
-
const client = getClient(stack);
|
|
11328
|
+
const client = getClient(stack, options);
|
|
9522
11329
|
return client?.connectionState ?? 'disconnected';
|
|
9523
11330
|
});
|
|
9524
11331
|
const unsubscribeRef = React.useRef(undefined);
|
|
@@ -9527,7 +11334,7 @@ function useConnectionState(stack) {
|
|
|
9527
11334
|
const setupClientSubscription = () => {
|
|
9528
11335
|
unsubscribeRef.current?.();
|
|
9529
11336
|
unsubscribeRef.current = undefined;
|
|
9530
|
-
const client = getClient(stack);
|
|
11337
|
+
const client = getClient(stack, options);
|
|
9531
11338
|
if (client && mounted) {
|
|
9532
11339
|
setState(client.connectionState);
|
|
9533
11340
|
unsubscribeRef.current = client.onConnectionStateChange((newState) => {
|
|
@@ -9546,12 +11353,12 @@ function useConnectionState(stack) {
|
|
|
9546
11353
|
unsubscribeFromClientChanges();
|
|
9547
11354
|
unsubscribeRef.current?.();
|
|
9548
11355
|
};
|
|
9549
|
-
}, [getClient, subscribeToClientChanges, stack]);
|
|
11356
|
+
}, [getClient, subscribeToClientChanges, stack, options]);
|
|
9550
11357
|
return state;
|
|
9551
11358
|
}
|
|
9552
|
-
function useView(stack, viewPath) {
|
|
11359
|
+
function useView(stack, viewPath, options) {
|
|
9553
11360
|
const { getClient } = useAreteContext();
|
|
9554
|
-
const client = getClient(stack);
|
|
11361
|
+
const client = getClient(stack, options);
|
|
9555
11362
|
return useSyncExternalStore((callback) => {
|
|
9556
11363
|
if (!client)
|
|
9557
11364
|
return () => { };
|
|
@@ -9563,9 +11370,9 @@ function useView(stack, viewPath) {
|
|
|
9563
11370
|
return data;
|
|
9564
11371
|
});
|
|
9565
11372
|
}
|
|
9566
|
-
function useEntity(stack, viewPath, key) {
|
|
11373
|
+
function useEntity(stack, viewPath, key, options) {
|
|
9567
11374
|
const { getClient } = useAreteContext();
|
|
9568
|
-
const client = getClient(stack);
|
|
11375
|
+
const client = getClient(stack, options);
|
|
9569
11376
|
return useSyncExternalStore((callback) => {
|
|
9570
11377
|
if (!client)
|
|
9571
11378
|
return () => { };
|
|
@@ -9578,6 +11385,84 @@ function useEntity(stack, viewPath, key) {
|
|
|
9578
11385
|
});
|
|
9579
11386
|
}
|
|
9580
11387
|
|
|
11388
|
+
function wrapRawInstruction(instruction, client, createMutationHook) {
|
|
11389
|
+
const execute = async (input, options) => {
|
|
11390
|
+
if (!client) {
|
|
11391
|
+
throw new Error('Arete client is not connected');
|
|
11392
|
+
}
|
|
11393
|
+
return client.transaction([instruction.build(input)], options);
|
|
11394
|
+
};
|
|
11395
|
+
return {
|
|
11396
|
+
execute,
|
|
11397
|
+
build: instruction.build,
|
|
11398
|
+
useMutation: () => createMutationHook(execute),
|
|
11399
|
+
};
|
|
11400
|
+
}
|
|
11401
|
+
function isConnectedOperation(value) {
|
|
11402
|
+
return Boolean(value && typeof value === 'object' && typeof value.prepare === 'function');
|
|
11403
|
+
}
|
|
11404
|
+
function wrapOperation(operation, client, createMutationHook) {
|
|
11405
|
+
const execute = async (input, options) => {
|
|
11406
|
+
if (!client) {
|
|
11407
|
+
throw new Error('Arete client is not connected');
|
|
11408
|
+
}
|
|
11409
|
+
const prepared = await operation.prepare(input);
|
|
11410
|
+
return client.execute(prepared, options);
|
|
11411
|
+
};
|
|
11412
|
+
return {
|
|
11413
|
+
prepare: operation.prepare.bind(operation),
|
|
11414
|
+
execute,
|
|
11415
|
+
useMutation: () => createMutationHook(execute),
|
|
11416
|
+
};
|
|
11417
|
+
}
|
|
11418
|
+
function wrapOperationNamespace(namespace, client, createMutationHook) {
|
|
11419
|
+
const wrapped = {};
|
|
11420
|
+
for (const [name, value] of Object.entries(namespace)) {
|
|
11421
|
+
if (isConnectedOperation(value)) {
|
|
11422
|
+
wrapped[name] = wrapOperation(value, client, createMutationHook);
|
|
11423
|
+
continue;
|
|
11424
|
+
}
|
|
11425
|
+
if (value && typeof value === 'object') {
|
|
11426
|
+
wrapped[name] = wrapOperationNamespace(value, client, createMutationHook);
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
return wrapped;
|
|
11430
|
+
}
|
|
11431
|
+
function wrapMaybeOperationNamespace(namespace, client, createMutationHook) {
|
|
11432
|
+
return namespace
|
|
11433
|
+
? wrapOperationNamespace(namespace, client, createMutationHook)
|
|
11434
|
+
: {};
|
|
11435
|
+
}
|
|
11436
|
+
function buildProgramHookInterfaces(programs, client, createMutationHook) {
|
|
11437
|
+
const wrappedPrograms = {};
|
|
11438
|
+
if (!programs) {
|
|
11439
|
+
return wrappedPrograms;
|
|
11440
|
+
}
|
|
11441
|
+
for (const [name, program] of Object.entries(programs)) {
|
|
11442
|
+
const raw = {};
|
|
11443
|
+
for (const [instructionName, instruction] of Object.entries(program.raw)) {
|
|
11444
|
+
raw[instructionName] = wrapRawInstruction(instruction, client, createMutationHook);
|
|
11445
|
+
}
|
|
11446
|
+
wrappedPrograms[name] = {
|
|
11447
|
+
name: program.name,
|
|
11448
|
+
programId: program.programId,
|
|
11449
|
+
schemas: program.schemas,
|
|
11450
|
+
pdas: program.pdas,
|
|
11451
|
+
accounts: program.accounts,
|
|
11452
|
+
queries: program.queries,
|
|
11453
|
+
addresses: program.addresses,
|
|
11454
|
+
constants: program.constants,
|
|
11455
|
+
defaults: program.defaults,
|
|
11456
|
+
math: program.math,
|
|
11457
|
+
raw: raw,
|
|
11458
|
+
instructions: wrapMaybeOperationNamespace(program.instructions, client, createMutationHook),
|
|
11459
|
+
transactions: wrapMaybeOperationNamespace(program.transactions, client, createMutationHook),
|
|
11460
|
+
flows: wrapMaybeOperationNamespace(program.flows, client, createMutationHook),
|
|
11461
|
+
};
|
|
11462
|
+
}
|
|
11463
|
+
return wrappedPrograms;
|
|
11464
|
+
}
|
|
11465
|
+
|
|
9581
11466
|
function shallowArrayEqual(a, b) {
|
|
9582
11467
|
if (a === b)
|
|
9583
11468
|
return true;
|
|
@@ -9591,6 +11476,34 @@ function shallowArrayEqual(a, b) {
|
|
|
9591
11476
|
}
|
|
9592
11477
|
return true;
|
|
9593
11478
|
}
|
|
11479
|
+
function compareNumericValues(value, condition, op) {
|
|
11480
|
+
if (typeof value === 'bigint' || typeof condition === 'bigint') {
|
|
11481
|
+
const left = typeof value === 'bigint' ? value : BigInt(value);
|
|
11482
|
+
const right = typeof condition === 'bigint' ? condition : BigInt(condition);
|
|
11483
|
+
switch (op) {
|
|
11484
|
+
case 'gte':
|
|
11485
|
+
return left >= right;
|
|
11486
|
+
case 'lte':
|
|
11487
|
+
return left <= right;
|
|
11488
|
+
case 'gt':
|
|
11489
|
+
return left > right;
|
|
11490
|
+
case 'lt':
|
|
11491
|
+
return left < right;
|
|
11492
|
+
}
|
|
11493
|
+
}
|
|
11494
|
+
const left = value;
|
|
11495
|
+
const right = condition;
|
|
11496
|
+
switch (op) {
|
|
11497
|
+
case 'gte':
|
|
11498
|
+
return left >= right;
|
|
11499
|
+
case 'lte':
|
|
11500
|
+
return left <= right;
|
|
11501
|
+
case 'gt':
|
|
11502
|
+
return left > right;
|
|
11503
|
+
case 'lt':
|
|
11504
|
+
return left < right;
|
|
11505
|
+
}
|
|
11506
|
+
}
|
|
9594
11507
|
function useStateView(viewDef, client, key, options) {
|
|
9595
11508
|
const [isLoading, setIsLoading] = useState(!options?.initialData && options?.withSnapshot !== false);
|
|
9596
11509
|
const [error, setError] = useState();
|
|
@@ -9675,7 +11588,10 @@ function useStateView(viewDef, client, key, options) {
|
|
|
9675
11588
|
? clientRef.current.store.get(viewDef.view, keyString)
|
|
9676
11589
|
: clientRef.current.store.getAll(viewDef.view)[0];
|
|
9677
11590
|
const validated = entity && schema
|
|
9678
|
-
? (
|
|
11591
|
+
? (() => {
|
|
11592
|
+
const parsed = schema.safeParse(entity);
|
|
11593
|
+
return parsed.success ? parsed.data : undefined;
|
|
11594
|
+
})()
|
|
9679
11595
|
: entity;
|
|
9680
11596
|
if (validated !== cachedSnapshotRef.current) {
|
|
9681
11597
|
cachedSnapshotRef.current = validated;
|
|
@@ -9801,20 +11717,23 @@ function useListView(viewDef, client, params, options) {
|
|
|
9801
11717
|
if (typeof condition === 'object' && condition !== null) {
|
|
9802
11718
|
const cond = condition;
|
|
9803
11719
|
if ('gte' in cond)
|
|
9804
|
-
return value
|
|
11720
|
+
return compareNumericValues(value, cond.gte, 'gte');
|
|
9805
11721
|
if ('lte' in cond)
|
|
9806
|
-
return value
|
|
11722
|
+
return compareNumericValues(value, cond.lte, 'lte');
|
|
9807
11723
|
if ('gt' in cond)
|
|
9808
|
-
return value
|
|
11724
|
+
return compareNumericValues(value, cond.gt, 'gt');
|
|
9809
11725
|
if ('lt' in cond)
|
|
9810
|
-
return value
|
|
11726
|
+
return compareNumericValues(value, cond.lt, 'lt');
|
|
9811
11727
|
}
|
|
9812
11728
|
return value === condition;
|
|
9813
11729
|
});
|
|
9814
11730
|
});
|
|
9815
11731
|
}
|
|
9816
11732
|
if (schema) {
|
|
9817
|
-
items = items.
|
|
11733
|
+
items = items.flatMap((item) => {
|
|
11734
|
+
const parsed = schema.safeParse(item);
|
|
11735
|
+
return parsed.success ? [parsed.data] : [];
|
|
11736
|
+
});
|
|
9818
11737
|
}
|
|
9819
11738
|
if (limit) {
|
|
9820
11739
|
items = items.slice(0, limit);
|
|
@@ -9874,30 +11793,42 @@ function createListViewHook(viewDef, client) {
|
|
|
9874
11793
|
function useInstructionMutation(execute) {
|
|
9875
11794
|
const [status, setStatus] = useState('idle');
|
|
9876
11795
|
const [error, setError] = useState(null);
|
|
11796
|
+
const [signatures, setSignatures] = useState([]);
|
|
9877
11797
|
const [signature, setSignature] = useState(null);
|
|
9878
11798
|
const submit = useCallback(async (args, options) => {
|
|
9879
11799
|
setStatus('pending');
|
|
9880
11800
|
setError(null);
|
|
11801
|
+
setSignatures([]);
|
|
9881
11802
|
setSignature(null);
|
|
11803
|
+
const { onSuccess, onError, ...executionOptions } = options ?? {};
|
|
9882
11804
|
try {
|
|
9883
|
-
const result = await execute(args,
|
|
11805
|
+
const result = await execute(args, executionOptions);
|
|
11806
|
+
const resultSignatures = typeof result === 'object' && result !== null && 'signatures' in result
|
|
11807
|
+
&& Array.isArray(result.signatures)
|
|
11808
|
+
? result.signatures.map(String)
|
|
11809
|
+
: typeof result === 'object' && result !== null && 'signature' in result
|
|
11810
|
+
? [String(result.signature)]
|
|
11811
|
+
: [];
|
|
9884
11812
|
setStatus('success');
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
11813
|
+
setSignatures(resultSignatures);
|
|
11814
|
+
setSignature(resultSignatures.length === 1 ? resultSignatures[0] : null);
|
|
11815
|
+
if (onSuccess) {
|
|
11816
|
+
onSuccess(result);
|
|
9888
11817
|
}
|
|
9889
11818
|
return result;
|
|
9890
11819
|
}
|
|
9891
11820
|
catch (err) {
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
const displayError = programError
|
|
9895
|
-
? `${programError.name}: ${programError.message}`
|
|
9896
|
-
:
|
|
11821
|
+
// The core executor already parses program errors against the handler's
|
|
11822
|
+
// IDL error definitions and throws an InstructionError.
|
|
11823
|
+
const displayError = err instanceof InstructionError && err.programError
|
|
11824
|
+
? `${err.programError.name}: ${err.programError.message}`
|
|
11825
|
+
: err instanceof Error
|
|
11826
|
+
? err.message
|
|
11827
|
+
: String(err);
|
|
9897
11828
|
setStatus('error');
|
|
9898
11829
|
setError(displayError);
|
|
9899
|
-
if (
|
|
9900
|
-
|
|
11830
|
+
if (onError && err instanceof Error) {
|
|
11831
|
+
onError(err);
|
|
9901
11832
|
}
|
|
9902
11833
|
throw err;
|
|
9903
11834
|
}
|
|
@@ -9905,12 +11836,14 @@ function useInstructionMutation(execute) {
|
|
|
9905
11836
|
const reset = useCallback(() => {
|
|
9906
11837
|
setStatus('idle');
|
|
9907
11838
|
setError(null);
|
|
11839
|
+
setSignatures([]);
|
|
9908
11840
|
setSignature(null);
|
|
9909
11841
|
}, []);
|
|
9910
11842
|
return {
|
|
9911
11843
|
submit,
|
|
9912
11844
|
status,
|
|
9913
11845
|
error,
|
|
11846
|
+
signatures,
|
|
9914
11847
|
signature,
|
|
9915
11848
|
isLoading: status === 'pending',
|
|
9916
11849
|
reset,
|
|
@@ -9919,21 +11852,25 @@ function useInstructionMutation(execute) {
|
|
|
9919
11852
|
|
|
9920
11853
|
function useArete(stack, options) {
|
|
9921
11854
|
const { getOrCreateClient, getClient } = useAreteContext();
|
|
9922
|
-
const
|
|
9923
|
-
const
|
|
11855
|
+
const url = options?.url;
|
|
11856
|
+
const httpUrl = options?.httpUrl;
|
|
11857
|
+
const transport = options?.transport;
|
|
11858
|
+
const attachedPrograms = options?.programs;
|
|
11859
|
+
const lookupOptions = useMemo(() => ({ url, httpUrl, transport, programs: attachedPrograms }), [url, httpUrl, transport, attachedPrograms]);
|
|
11860
|
+
const [client, setClient] = useState(getClient(stack, lookupOptions));
|
|
9924
11861
|
const [isLoading, setIsLoading] = useState(!client);
|
|
9925
11862
|
const [error, setError] = useState(null);
|
|
9926
11863
|
const [connectionState, setConnectionState] = useState(() => client?.connectionState ?? 'disconnected');
|
|
9927
11864
|
useEffect(() => {
|
|
9928
|
-
const existingClient = getClient(stack);
|
|
9929
|
-
if (existingClient
|
|
11865
|
+
const existingClient = getClient(stack, lookupOptions);
|
|
11866
|
+
if (existingClient) {
|
|
9930
11867
|
setClient(existingClient);
|
|
9931
11868
|
setIsLoading(false);
|
|
9932
11869
|
return;
|
|
9933
11870
|
}
|
|
9934
11871
|
setIsLoading(true);
|
|
9935
11872
|
setError(null);
|
|
9936
|
-
getOrCreateClient(stack,
|
|
11873
|
+
getOrCreateClient(stack, lookupOptions)
|
|
9937
11874
|
.then((newClient) => {
|
|
9938
11875
|
setClient(newClient);
|
|
9939
11876
|
setIsLoading(false);
|
|
@@ -9942,7 +11879,7 @@ function useArete(stack, options) {
|
|
|
9942
11879
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
9943
11880
|
setIsLoading(false);
|
|
9944
11881
|
});
|
|
9945
|
-
}, [stack, getOrCreateClient, getClient,
|
|
11882
|
+
}, [stack, getOrCreateClient, getClient, lookupOptions]);
|
|
9946
11883
|
useEffect(() => {
|
|
9947
11884
|
if (!client) {
|
|
9948
11885
|
setConnectionState('disconnected');
|
|
@@ -9973,21 +11910,14 @@ function useArete(stack, options) {
|
|
|
9973
11910
|
}
|
|
9974
11911
|
return result;
|
|
9975
11912
|
}, [stack, client]);
|
|
9976
|
-
const
|
|
9977
|
-
|
|
9978
|
-
if (client?.instructions) {
|
|
9979
|
-
for (const [instructionName, executeFn] of Object.entries(client.instructions)) {
|
|
9980
|
-
result[instructionName] = {
|
|
9981
|
-
execute: executeFn,
|
|
9982
|
-
useMutation: () => useInstructionMutation(executeFn)
|
|
9983
|
-
};
|
|
9984
|
-
}
|
|
9985
|
-
}
|
|
9986
|
-
return result;
|
|
11913
|
+
const programs = useMemo(() => {
|
|
11914
|
+
return buildProgramHookInterfaces(client?.programs, client, useInstructionMutation);
|
|
9987
11915
|
}, [client]);
|
|
9988
11916
|
return {
|
|
9989
11917
|
views: views,
|
|
9990
|
-
|
|
11918
|
+
queries: (client?.queries ?? {}),
|
|
11919
|
+
programs: programs,
|
|
11920
|
+
chain: client?.chain,
|
|
9991
11921
|
zustandStore: client?.store?.store,
|
|
9992
11922
|
client: client,
|
|
9993
11923
|
connectionState,
|
|
@@ -9997,5 +11927,5 @@ function useArete(stack, options) {
|
|
|
9997
11927
|
};
|
|
9998
11928
|
}
|
|
9999
11929
|
|
|
10000
|
-
export { Arete, AreteError, AreteProvider, ConnectionManager, DEFAULT_CONFIG, DEFAULT_MAX_ENTRIES_PER_VIEW, FrameProcessor, MemoryAdapter, SubscriptionRegistry, ZustandAdapter, createInstructionExecutor, createPublicKeySeed, createSeed, findProgramAddress as derivePda, executeInstruction, formatProgramError, isSnapshotFrame, isValidFrame, parseFrame, parseFrameFromBlob, parseInstructionError, resolveAccounts, serializeInstructionData, useArete, useAreteContext, useConnectionState, useEntity, useInstructionMutation, useView, validateAccountResolution,
|
|
11930
|
+
export { Arete, AreteError, AreteProvider, ConnectionManager, DEFAULT_CONFIG, DEFAULT_MAX_ENTRIES_PER_VIEW, FrameProcessor, InstructionError, MemoryAdapter, ReadRequestError, SubscriptionRegistry, ZustandAdapter, buildInstruction, createInstructionExecutor, createInstructionHandler, createPublicKeySeed, createSeed, findProgramAddress as derivePda, executeInstruction, formatProgramError, isSnapshotFrame, isValidFrame, parseFrame, parseFrameFromBlob, parseInstructionError, resolveAccounts, serializeInstructionData, useArete, useAreteContext, useConnectionState, useEntity, useInstructionMutation, useView, validateAccountResolution, withPrograms };
|
|
10001
11931
|
//# sourceMappingURL=index.esm.js.map
|