@usearete/sdk 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 +273 -58
- package/dist/index.d.ts +957 -175
- package/dist/index.esm.js +2004 -680
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2055 -681
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { inflate } from 'pako';
|
|
2
|
+
import { Point } from '@noble/ed25519';
|
|
2
3
|
|
|
3
4
|
const DEFAULT_MAX_ENTRIES_PER_VIEW = 10000;
|
|
4
5
|
const DEFAULT_CONFIG = {
|
|
@@ -185,6 +186,11 @@ function isHostedAreteWebsocketUrl(websocketUrl) {
|
|
|
185
186
|
return false;
|
|
186
187
|
}
|
|
187
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Historical sentinel accepted for back-compat: connecting with this URL is
|
|
191
|
+
* treated the same as passing no WebSocket URL at all (HTTP-only mode).
|
|
192
|
+
*/
|
|
193
|
+
const DISABLED_WEBSOCKET_URL = 'ws://127.0.0.1/__arete_disabled__';
|
|
188
194
|
class ConnectionManager {
|
|
189
195
|
constructor(config) {
|
|
190
196
|
this.ws = null;
|
|
@@ -200,11 +206,11 @@ class ConnectionManager {
|
|
|
200
206
|
this.stateHandlers = new Set();
|
|
201
207
|
this.socketIssueHandlers = new Set();
|
|
202
208
|
this.reconnectForTokenRefresh = false;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
this.websocketUrl =
|
|
207
|
-
this.hostedAreteUrl = isHostedAreteWebsocketUrl(
|
|
209
|
+
const websocketUrl = config.websocketUrl && config.websocketUrl !== DISABLED_WEBSOCKET_URL
|
|
210
|
+
? config.websocketUrl
|
|
211
|
+
: null;
|
|
212
|
+
this.websocketUrl = websocketUrl;
|
|
213
|
+
this.hostedAreteUrl = websocketUrl !== null && isHostedAreteWebsocketUrl(websocketUrl);
|
|
208
214
|
this.reconnectIntervals = config.reconnectIntervals ?? DEFAULT_CONFIG.reconnectIntervals;
|
|
209
215
|
this.maxReconnectAttempts =
|
|
210
216
|
config.maxReconnectAttempts ?? DEFAULT_CONFIG.maxReconnectAttempts;
|
|
@@ -296,7 +302,7 @@ class ConnectionManager {
|
|
|
296
302
|
}
|
|
297
303
|
createTokenEndpointRequestBody() {
|
|
298
304
|
return {
|
|
299
|
-
websocket_url: this.websocketUrl,
|
|
305
|
+
websocket_url: this.websocketUrl ?? '',
|
|
300
306
|
};
|
|
301
307
|
}
|
|
302
308
|
async fetchTokenFromEndpoint(tokenEndpoint) {
|
|
@@ -450,14 +456,21 @@ class ConnectionManager {
|
|
|
450
456
|
this.ws.close(1000, 'token refresh');
|
|
451
457
|
}
|
|
452
458
|
buildAuthUrl(token) {
|
|
459
|
+
const websocketUrl = this.requireWebsocketUrl();
|
|
453
460
|
if (this.authConfig?.tokenTransport === 'bearer') {
|
|
454
|
-
return
|
|
461
|
+
return websocketUrl;
|
|
455
462
|
}
|
|
456
463
|
if (!token) {
|
|
457
|
-
return
|
|
464
|
+
return websocketUrl;
|
|
465
|
+
}
|
|
466
|
+
const separator = websocketUrl.includes('?') ? '&' : '?';
|
|
467
|
+
return `${websocketUrl}${separator}${DEFAULT_QUERY_PARAMETER}=${encodeURIComponent(token)}`;
|
|
468
|
+
}
|
|
469
|
+
requireWebsocketUrl() {
|
|
470
|
+
if (this.websocketUrl === null) {
|
|
471
|
+
throw new AreteError('WebSocket transport is disabled (client was connected with transport: "http"); views and subscriptions are unavailable', 'WEBSOCKET_DISABLED');
|
|
458
472
|
}
|
|
459
|
-
|
|
460
|
-
return `${this.websocketUrl}${separator}${DEFAULT_QUERY_PARAMETER}=${encodeURIComponent(token)}`;
|
|
473
|
+
return this.websocketUrl;
|
|
461
474
|
}
|
|
462
475
|
createWebSocket(url, token) {
|
|
463
476
|
if (this.authConfig?.tokenTransport === 'bearer') {
|
|
@@ -477,6 +490,12 @@ class ConnectionManager {
|
|
|
477
490
|
getState() {
|
|
478
491
|
return this.currentState;
|
|
479
492
|
}
|
|
493
|
+
async getHttpAuthToken(forceRefresh = false) {
|
|
494
|
+
return this.getOrRefreshToken(forceRefresh);
|
|
495
|
+
}
|
|
496
|
+
clearHttpAuthToken() {
|
|
497
|
+
this.clearTokenState();
|
|
498
|
+
}
|
|
480
499
|
onFrame(handler) {
|
|
481
500
|
this.frameHandlers.add(handler);
|
|
482
501
|
return () => {
|
|
@@ -512,6 +531,7 @@ class ConnectionManager {
|
|
|
512
531
|
return issue;
|
|
513
532
|
}
|
|
514
533
|
async connect() {
|
|
534
|
+
this.requireWebsocketUrl();
|
|
515
535
|
if (this.ws?.readyState === WebSocket.OPEN ||
|
|
516
536
|
this.ws?.readyState === WebSocket.CONNECTING ||
|
|
517
537
|
this.currentState === 'connecting') {
|
|
@@ -656,6 +676,7 @@ class ConnectionManager {
|
|
|
656
676
|
}
|
|
657
677
|
}
|
|
658
678
|
subscribe(subscription) {
|
|
679
|
+
this.requireWebsocketUrl();
|
|
659
680
|
const subKey = this.makeSubKey(subscription);
|
|
660
681
|
if (this.currentState === 'connected' && this.ws?.readyState === WebSocket.OPEN) {
|
|
661
682
|
if (this.activeSubscriptions.has(subKey)) {
|
|
@@ -759,16 +780,20 @@ class ConnectionManager {
|
|
|
759
780
|
}
|
|
760
781
|
}
|
|
761
782
|
|
|
762
|
-
|
|
783
|
+
const INTERNAL_SEQ_FIELD = '__seq';
|
|
784
|
+
function isObject(item) {
|
|
763
785
|
return item !== null && typeof item === 'object' && !Array.isArray(item);
|
|
764
786
|
}
|
|
765
|
-
function deepMergeWithAppend
|
|
766
|
-
if (!isObject
|
|
787
|
+
function deepMergeWithAppend(target, source, appendPaths, currentPath = '') {
|
|
788
|
+
if (!isObject(target) || !isObject(source)) {
|
|
767
789
|
return source;
|
|
768
790
|
}
|
|
769
791
|
const result = { ...target };
|
|
770
792
|
for (const key in source) {
|
|
771
793
|
const sourceValue = source[key];
|
|
794
|
+
if (sourceValue === undefined) {
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
772
797
|
const targetValue = result[key];
|
|
773
798
|
const fieldPath = currentPath ? `${currentPath}.${key}` : key;
|
|
774
799
|
if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
|
|
@@ -779,8 +804,8 @@ function deepMergeWithAppend$1(target, source, appendPaths, currentPath = '') {
|
|
|
779
804
|
result[key] = sourceValue;
|
|
780
805
|
}
|
|
781
806
|
}
|
|
782
|
-
else if (isObject
|
|
783
|
-
result[key] = deepMergeWithAppend
|
|
807
|
+
else if (isObject(sourceValue) && isObject(targetValue)) {
|
|
808
|
+
result[key] = deepMergeWithAppend(targetValue, sourceValue, appendPaths, fieldPath);
|
|
784
809
|
}
|
|
785
810
|
else {
|
|
786
811
|
result[key] = sourceValue;
|
|
@@ -788,6 +813,36 @@ function deepMergeWithAppend$1(target, source, appendPaths, currentPath = '') {
|
|
|
788
813
|
}
|
|
789
814
|
return result;
|
|
790
815
|
}
|
|
816
|
+
function stripUndefinedProperties(value) {
|
|
817
|
+
if (Array.isArray(value)) {
|
|
818
|
+
return value.map(item => stripUndefinedProperties(item));
|
|
819
|
+
}
|
|
820
|
+
if (!isObject(value)) {
|
|
821
|
+
return value;
|
|
822
|
+
}
|
|
823
|
+
const result = {};
|
|
824
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
825
|
+
if (nestedValue === undefined) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
result[key] = stripUndefinedProperties(nestedValue);
|
|
829
|
+
}
|
|
830
|
+
return result;
|
|
831
|
+
}
|
|
832
|
+
function toCamelCaseSegment(value) {
|
|
833
|
+
if (value === '_seq') {
|
|
834
|
+
return INTERNAL_SEQ_FIELD;
|
|
835
|
+
}
|
|
836
|
+
const pascal = value
|
|
837
|
+
.split(/[_.-]/)
|
|
838
|
+
.filter(segment => segment.length > 0)
|
|
839
|
+
.map(segment => segment[0].toUpperCase() + segment.slice(1))
|
|
840
|
+
.join('');
|
|
841
|
+
if (pascal.length === 0) {
|
|
842
|
+
return value;
|
|
843
|
+
}
|
|
844
|
+
return pascal[0].toLowerCase() + pascal.slice(1);
|
|
845
|
+
}
|
|
791
846
|
class FrameProcessor {
|
|
792
847
|
constructor(storage, config = {}) {
|
|
793
848
|
this.pendingUpdates = [];
|
|
@@ -799,9 +854,10 @@ class FrameProcessor {
|
|
|
799
854
|
: config.maxEntriesPerView;
|
|
800
855
|
this.flushIntervalMs = config.flushIntervalMs ?? 0;
|
|
801
856
|
this.schemas = config.schemas;
|
|
857
|
+
this.patchSchemas = config.patchSchemas;
|
|
802
858
|
}
|
|
803
|
-
getSchema(viewPath) {
|
|
804
|
-
const schemas = this.schemas;
|
|
859
|
+
getSchema(viewPath, patch = false) {
|
|
860
|
+
const schemas = patch ? this.patchSchemas : this.schemas;
|
|
805
861
|
if (!schemas)
|
|
806
862
|
return null;
|
|
807
863
|
const entityName = viewPath.split('/')[0];
|
|
@@ -810,19 +866,86 @@ class FrameProcessor {
|
|
|
810
866
|
const entityKey = entityName;
|
|
811
867
|
return schemas[entityKey] ?? null;
|
|
812
868
|
}
|
|
813
|
-
|
|
814
|
-
const schema = this.getSchema(viewPath)
|
|
869
|
+
normalizeEntity(viewPath, data, patch = false) {
|
|
870
|
+
const schema = this.getSchema(viewPath, patch)
|
|
871
|
+
?? (!patch ? null : this.getSchema(viewPath));
|
|
815
872
|
if (!schema)
|
|
816
|
-
return
|
|
873
|
+
return data;
|
|
817
874
|
const result = schema.safeParse(data);
|
|
818
875
|
if (!result.success) {
|
|
819
876
|
console.warn('[Arete] Frame validation failed:', {
|
|
820
877
|
view: viewPath,
|
|
821
878
|
error: result.error,
|
|
822
879
|
});
|
|
823
|
-
return
|
|
880
|
+
return null;
|
|
824
881
|
}
|
|
825
|
-
return
|
|
882
|
+
return stripUndefinedProperties(result.data);
|
|
883
|
+
}
|
|
884
|
+
hasSchema(viewPath) {
|
|
885
|
+
return this.getSchema(viewPath) !== null;
|
|
886
|
+
}
|
|
887
|
+
normalizePathSegment(viewPath, segment) {
|
|
888
|
+
if (!this.hasSchema(viewPath)) {
|
|
889
|
+
return segment;
|
|
890
|
+
}
|
|
891
|
+
return toCamelCaseSegment(segment);
|
|
892
|
+
}
|
|
893
|
+
normalizePath(viewPath, path) {
|
|
894
|
+
if (!this.hasSchema(viewPath)) {
|
|
895
|
+
return path;
|
|
896
|
+
}
|
|
897
|
+
return path
|
|
898
|
+
.split('.')
|
|
899
|
+
.filter(segment => segment.length > 0)
|
|
900
|
+
.map(segment => this.normalizePathSegment(viewPath, segment))
|
|
901
|
+
.join('.');
|
|
902
|
+
}
|
|
903
|
+
normalizeSortConfig(viewPath, sort) {
|
|
904
|
+
if (!this.hasSchema(viewPath)) {
|
|
905
|
+
return sort;
|
|
906
|
+
}
|
|
907
|
+
return {
|
|
908
|
+
...sort,
|
|
909
|
+
field: sort.field.map(segment => this.normalizePathSegment(viewPath, segment)),
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
normalizeAppendPaths(viewPath, append) {
|
|
913
|
+
if (!append || append.length === 0 || !this.hasSchema(viewPath)) {
|
|
914
|
+
return append ?? [];
|
|
915
|
+
}
|
|
916
|
+
return append.map(path => this.normalizePath(viewPath, path));
|
|
917
|
+
}
|
|
918
|
+
extractSeq(data) {
|
|
919
|
+
if (!isObject(data)) {
|
|
920
|
+
return undefined;
|
|
921
|
+
}
|
|
922
|
+
const seq = data._seq;
|
|
923
|
+
if (typeof seq === 'string') {
|
|
924
|
+
return seq;
|
|
925
|
+
}
|
|
926
|
+
if (typeof seq === 'number' && Number.isFinite(seq)) {
|
|
927
|
+
return String(seq);
|
|
928
|
+
}
|
|
929
|
+
return undefined;
|
|
930
|
+
}
|
|
931
|
+
getInternalSeq(data) {
|
|
932
|
+
if (!isObject(data)) {
|
|
933
|
+
return undefined;
|
|
934
|
+
}
|
|
935
|
+
const seq = data[INTERNAL_SEQ_FIELD];
|
|
936
|
+
return typeof seq === 'string' ? seq : undefined;
|
|
937
|
+
}
|
|
938
|
+
attachInternalSeq(viewPath, data, seq) {
|
|
939
|
+
if (!seq || !this.hasSchema(viewPath) || !isObject(data)) {
|
|
940
|
+
return data;
|
|
941
|
+
}
|
|
942
|
+
Object.defineProperty(data, INTERNAL_SEQ_FIELD, {
|
|
943
|
+
value: seq,
|
|
944
|
+
enumerable: false,
|
|
945
|
+
configurable: true,
|
|
946
|
+
writable: true,
|
|
947
|
+
});
|
|
948
|
+
return data;
|
|
826
949
|
}
|
|
827
950
|
handleFrame(frame) {
|
|
828
951
|
if (this.flushIntervalMs === 0) {
|
|
@@ -908,7 +1031,9 @@ class FrameProcessor {
|
|
|
908
1031
|
}
|
|
909
1032
|
handleSubscribedFrame(frame) {
|
|
910
1033
|
if (this.storage.setViewConfig && frame.sort) {
|
|
911
|
-
this.storage.setViewConfig(frame.view, {
|
|
1034
|
+
this.storage.setViewConfig(frame.view, {
|
|
1035
|
+
sort: this.normalizeSortConfig(frame.view, frame.sort),
|
|
1036
|
+
});
|
|
912
1037
|
}
|
|
913
1038
|
}
|
|
914
1039
|
handleSnapshotFrame(frame) {
|
|
@@ -918,17 +1043,19 @@ class FrameProcessor {
|
|
|
918
1043
|
handleSnapshotFrameWithoutEnforce(frame) {
|
|
919
1044
|
const viewPath = frame.entity;
|
|
920
1045
|
for (const entity of frame.data) {
|
|
921
|
-
|
|
1046
|
+
const normalized = this.normalizeEntity(viewPath, entity.data);
|
|
1047
|
+
if (normalized === null) {
|
|
922
1048
|
continue;
|
|
923
1049
|
}
|
|
1050
|
+
const nextValue = this.attachInternalSeq(viewPath, normalized, this.extractSeq(entity.data));
|
|
924
1051
|
const previousValue = this.storage.get(viewPath, entity.key);
|
|
925
|
-
this.storage.set(viewPath, entity.key,
|
|
1052
|
+
this.storage.set(viewPath, entity.key, nextValue);
|
|
926
1053
|
this.storage.notifyUpdate(viewPath, entity.key, {
|
|
927
1054
|
type: 'upsert',
|
|
928
1055
|
key: entity.key,
|
|
929
|
-
data:
|
|
1056
|
+
data: nextValue,
|
|
930
1057
|
});
|
|
931
|
-
this.emitRichUpdate(viewPath, entity.key, previousValue,
|
|
1058
|
+
this.emitRichUpdate(viewPath, entity.key, previousValue, nextValue, 'upsert');
|
|
932
1059
|
}
|
|
933
1060
|
}
|
|
934
1061
|
handleEntityFrame(frame) {
|
|
@@ -941,33 +1068,39 @@ class FrameProcessor {
|
|
|
941
1068
|
switch (frame.op) {
|
|
942
1069
|
case 'create':
|
|
943
1070
|
case 'upsert':
|
|
944
|
-
|
|
1071
|
+
{
|
|
1072
|
+
const normalized = this.normalizeEntity(viewPath, frame.data);
|
|
1073
|
+
if (normalized === null) {
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
const nextValue = this.attachInternalSeq(viewPath, normalized, frame.seq ?? this.extractSeq(frame.data));
|
|
1077
|
+
this.storage.set(viewPath, frame.key, nextValue);
|
|
1078
|
+
this.storage.notifyUpdate(viewPath, frame.key, {
|
|
1079
|
+
type: 'upsert',
|
|
1080
|
+
key: frame.key,
|
|
1081
|
+
data: nextValue,
|
|
1082
|
+
});
|
|
1083
|
+
this.emitRichUpdate(viewPath, frame.key, previousValue, nextValue, frame.op);
|
|
945
1084
|
break;
|
|
946
1085
|
}
|
|
947
|
-
this.storage.set(viewPath, frame.key, frame.data);
|
|
948
|
-
this.storage.notifyUpdate(viewPath, frame.key, {
|
|
949
|
-
type: 'upsert',
|
|
950
|
-
key: frame.key,
|
|
951
|
-
data: frame.data,
|
|
952
|
-
});
|
|
953
|
-
this.emitRichUpdate(viewPath, frame.key, previousValue, frame.data, frame.op);
|
|
954
|
-
break;
|
|
955
1086
|
case 'patch': {
|
|
956
1087
|
const existing = this.storage.get(viewPath, frame.key);
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
? deepMergeWithAppend$1(existing, frame.data, appendPaths)
|
|
960
|
-
: frame.data;
|
|
961
|
-
if (!this.validateEntity(viewPath, merged)) {
|
|
1088
|
+
const normalizedPatch = this.normalizeEntity(viewPath, frame.data, true);
|
|
1089
|
+
if (normalizedPatch === null) {
|
|
962
1090
|
break;
|
|
963
1091
|
}
|
|
964
|
-
this.
|
|
1092
|
+
const appendPaths = this.normalizeAppendPaths(viewPath, frame.append);
|
|
1093
|
+
const merged = existing
|
|
1094
|
+
? deepMergeWithAppend(existing, normalizedPatch, appendPaths)
|
|
1095
|
+
: normalizedPatch;
|
|
1096
|
+
const nextValue = this.attachInternalSeq(viewPath, merged, frame.seq ?? this.extractSeq(frame.data) ?? this.getInternalSeq(existing));
|
|
1097
|
+
this.storage.set(viewPath, frame.key, nextValue);
|
|
965
1098
|
this.storage.notifyUpdate(viewPath, frame.key, {
|
|
966
1099
|
type: 'patch',
|
|
967
1100
|
key: frame.key,
|
|
968
|
-
data:
|
|
1101
|
+
data: normalizedPatch,
|
|
969
1102
|
});
|
|
970
|
-
this.emitRichUpdate(viewPath, frame.key, previousValue,
|
|
1103
|
+
this.emitRichUpdate(viewPath, frame.key, previousValue, nextValue, 'patch', normalizedPatch);
|
|
971
1104
|
break;
|
|
972
1105
|
}
|
|
973
1106
|
case 'delete':
|
|
@@ -1000,7 +1133,7 @@ class FrameProcessor {
|
|
|
1000
1133
|
}
|
|
1001
1134
|
}
|
|
1002
1135
|
|
|
1003
|
-
|
|
1136
|
+
class ViewData {
|
|
1004
1137
|
constructor() {
|
|
1005
1138
|
this.entities = new Map();
|
|
1006
1139
|
this.accessOrder = [];
|
|
@@ -1054,7 +1187,7 @@ let ViewData$1 = class ViewData {
|
|
|
1054
1187
|
this.entities.clear();
|
|
1055
1188
|
this.accessOrder = [];
|
|
1056
1189
|
}
|
|
1057
|
-
}
|
|
1190
|
+
}
|
|
1058
1191
|
class MemoryAdapter {
|
|
1059
1192
|
constructor(_config = {}) {
|
|
1060
1193
|
this.views = new Map();
|
|
@@ -1102,7 +1235,7 @@ class MemoryAdapter {
|
|
|
1102
1235
|
set(viewPath, key, data) {
|
|
1103
1236
|
let view = this.views.get(viewPath);
|
|
1104
1237
|
if (!view) {
|
|
1105
|
-
view = new ViewData
|
|
1238
|
+
view = new ViewData();
|
|
1106
1239
|
this.views.set(viewPath, view);
|
|
1107
1240
|
}
|
|
1108
1241
|
view.set(key, data);
|
|
@@ -1142,7 +1275,7 @@ class MemoryAdapter {
|
|
|
1142
1275
|
}
|
|
1143
1276
|
}
|
|
1144
1277
|
|
|
1145
|
-
function getNestedValue
|
|
1278
|
+
function getNestedValue(obj, path) {
|
|
1146
1279
|
let current = obj;
|
|
1147
1280
|
for (const segment of path) {
|
|
1148
1281
|
if (current === null || current === undefined)
|
|
@@ -1153,16 +1286,25 @@ function getNestedValue$1(obj, path) {
|
|
|
1153
1286
|
}
|
|
1154
1287
|
return current;
|
|
1155
1288
|
}
|
|
1156
|
-
function compareSortValues
|
|
1289
|
+
function compareSortValues(a, b) {
|
|
1157
1290
|
if (a === b)
|
|
1158
1291
|
return 0;
|
|
1159
1292
|
if (a === undefined || a === null)
|
|
1160
1293
|
return -1;
|
|
1161
1294
|
if (b === undefined || b === null)
|
|
1162
1295
|
return 1;
|
|
1296
|
+
if (typeof a === 'bigint' && typeof b === 'bigint') {
|
|
1297
|
+
return a < b ? -1 : 1;
|
|
1298
|
+
}
|
|
1163
1299
|
if (typeof a === 'number' && typeof b === 'number') {
|
|
1164
1300
|
return a - b;
|
|
1165
1301
|
}
|
|
1302
|
+
if (typeof a === 'bigint' && typeof b === 'number' && Number.isInteger(b)) {
|
|
1303
|
+
return a < BigInt(b) ? -1 : 1;
|
|
1304
|
+
}
|
|
1305
|
+
if (typeof a === 'number' && typeof b === 'bigint' && Number.isInteger(a)) {
|
|
1306
|
+
return BigInt(a) < b ? -1 : 1;
|
|
1307
|
+
}
|
|
1166
1308
|
if (typeof a === 'string' && typeof b === 'string') {
|
|
1167
1309
|
return a.localeCompare(b);
|
|
1168
1310
|
}
|
|
@@ -1289,7 +1431,7 @@ class SortedStorageDecorator {
|
|
|
1289
1431
|
sortedKeys.splice(insertIdx, 0, key);
|
|
1290
1432
|
}
|
|
1291
1433
|
binarySearchInsertPosition(viewPath, sortedKeys, sortConfig, newKey, newValue) {
|
|
1292
|
-
const newSortValue = getNestedValue
|
|
1434
|
+
const newSortValue = getNestedValue(newValue, sortConfig.field);
|
|
1293
1435
|
const isDesc = sortConfig.order === 'desc';
|
|
1294
1436
|
let low = 0;
|
|
1295
1437
|
let high = sortedKeys.length;
|
|
@@ -1297,8 +1439,8 @@ class SortedStorageDecorator {
|
|
|
1297
1439
|
const mid = Math.floor((low + high) / 2);
|
|
1298
1440
|
const midKey = sortedKeys[mid];
|
|
1299
1441
|
const midEntity = this.inner.get(viewPath, midKey);
|
|
1300
|
-
const midValue = getNestedValue
|
|
1301
|
-
let cmp = compareSortValues
|
|
1442
|
+
const midValue = getNestedValue(midEntity, sortConfig.field);
|
|
1443
|
+
let cmp = compareSortValues(newSortValue, midValue);
|
|
1302
1444
|
if (isDesc)
|
|
1303
1445
|
cmp = -cmp;
|
|
1304
1446
|
if (cmp === 0) {
|
|
@@ -1320,9 +1462,9 @@ class SortedStorageDecorator {
|
|
|
1320
1462
|
const isDesc = sortConfig.order === 'desc';
|
|
1321
1463
|
const entries = allKeys.map(k => [k, this.inner.get(viewPath, k)]);
|
|
1322
1464
|
entries.sort((a, b) => {
|
|
1323
|
-
const aValue = getNestedValue
|
|
1324
|
-
const bValue = getNestedValue
|
|
1325
|
-
let cmp = compareSortValues
|
|
1465
|
+
const aValue = getNestedValue(a[1], sortConfig.field);
|
|
1466
|
+
const bValue = getNestedValue(b[1], sortConfig.field);
|
|
1467
|
+
let cmp = compareSortValues(aValue, bValue);
|
|
1326
1468
|
if (isDesc)
|
|
1327
1469
|
cmp = -cmp;
|
|
1328
1470
|
if (cmp === 0) {
|
|
@@ -1652,6 +1794,160 @@ function createTypedViews(stack, storage, subscriptionRegistry) {
|
|
|
1652
1794
|
return views;
|
|
1653
1795
|
}
|
|
1654
1796
|
|
|
1797
|
+
class ReadRequestError extends Error {
|
|
1798
|
+
constructor(input) {
|
|
1799
|
+
super(`Read request to '${input.path}' failed (${input.status}): ${input.body}`);
|
|
1800
|
+
this.name = 'ReadRequestError';
|
|
1801
|
+
this.status = input.status;
|
|
1802
|
+
this.path = input.path;
|
|
1803
|
+
this.body = input.body;
|
|
1804
|
+
this.serverErrorCode = input.serverErrorCode;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
function getServerErrorCode(response, body) {
|
|
1808
|
+
const headerCode = response.headers.get('X-Error-Code');
|
|
1809
|
+
if (headerCode) {
|
|
1810
|
+
return headerCode;
|
|
1811
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
const parsed = JSON.parse(body);
|
|
1814
|
+
return typeof parsed.code === 'string' ? parsed.code : undefined;
|
|
1815
|
+
}
|
|
1816
|
+
catch {
|
|
1817
|
+
return undefined;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
async function parseReadResponse(response, path) {
|
|
1821
|
+
if (!response.ok) {
|
|
1822
|
+
const body = await response.text();
|
|
1823
|
+
throw new ReadRequestError({
|
|
1824
|
+
status: response.status,
|
|
1825
|
+
path,
|
|
1826
|
+
body,
|
|
1827
|
+
serverErrorCode: getServerErrorCode(response, body),
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
return response.json();
|
|
1831
|
+
}
|
|
1832
|
+
function programAccountRead(input) {
|
|
1833
|
+
return {
|
|
1834
|
+
account: input.account,
|
|
1835
|
+
path: input.path,
|
|
1836
|
+
schema: input.schema,
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
function programQuery(input) {
|
|
1840
|
+
return {
|
|
1841
|
+
name: input.name,
|
|
1842
|
+
path: input.path,
|
|
1843
|
+
method: input.method,
|
|
1844
|
+
schema: input.schema,
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
function stackQuery(input) {
|
|
1848
|
+
return {
|
|
1849
|
+
name: input.name,
|
|
1850
|
+
path: input.path,
|
|
1851
|
+
method: input.method,
|
|
1852
|
+
schema: input.schema,
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
function joinUrl(baseUrl, path) {
|
|
1857
|
+
return `${baseUrl.replace(/\/$/, '')}${path.startsWith('/') ? path : `/${path}`}`;
|
|
1858
|
+
}
|
|
1859
|
+
function decodeBase64(encoded) {
|
|
1860
|
+
if (typeof atob === 'function') {
|
|
1861
|
+
const binary = atob(encoded);
|
|
1862
|
+
const bytes = new Uint8Array(binary.length);
|
|
1863
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1864
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1865
|
+
}
|
|
1866
|
+
return bytes;
|
|
1867
|
+
}
|
|
1868
|
+
const bufferCtor = globalThis.Buffer;
|
|
1869
|
+
if (bufferCtor) {
|
|
1870
|
+
return new Uint8Array(bufferCtor.from(encoded, 'base64'));
|
|
1871
|
+
}
|
|
1872
|
+
throw new Error('No base64 decoder available in this environment');
|
|
1873
|
+
}
|
|
1874
|
+
function deriveHttpEndpoint(wsUrl) {
|
|
1875
|
+
try {
|
|
1876
|
+
const parsed = new URL(wsUrl);
|
|
1877
|
+
if (parsed.protocol === 'ws:') {
|
|
1878
|
+
parsed.protocol = 'http:';
|
|
1879
|
+
}
|
|
1880
|
+
else if (parsed.protocol === 'wss:') {
|
|
1881
|
+
parsed.protocol = 'https:';
|
|
1882
|
+
}
|
|
1883
|
+
return parsed.toString().replace(/\/$/, '');
|
|
1884
|
+
}
|
|
1885
|
+
catch {
|
|
1886
|
+
return wsUrl.replace(/^wss?:/i, (protocol) => protocol.toLowerCase() === 'wss:' ? 'https:' : 'http:');
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
function createChainClient(httpBaseUrl, fetchImpl) {
|
|
1890
|
+
return {
|
|
1891
|
+
async exists(address) {
|
|
1892
|
+
const path = `/chain/exists/${encodeURIComponent(address)}`;
|
|
1893
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
1894
|
+
const body = await parseReadResponse(response, path);
|
|
1895
|
+
return body.exists;
|
|
1896
|
+
},
|
|
1897
|
+
async lamports(address) {
|
|
1898
|
+
const path = `/chain/lamports/${encodeURIComponent(address)}`;
|
|
1899
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
1900
|
+
const body = await parseReadResponse(response, path);
|
|
1901
|
+
return body.lamports;
|
|
1902
|
+
},
|
|
1903
|
+
async minimumBalanceForRentExemption(space) {
|
|
1904
|
+
const path = `/chain/rent-exemption/${encodeURIComponent(String(space))}`;
|
|
1905
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
1906
|
+
const body = await parseReadResponse(response, path);
|
|
1907
|
+
return body.lamports;
|
|
1908
|
+
},
|
|
1909
|
+
async clock() {
|
|
1910
|
+
const path = '/chain/clock';
|
|
1911
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
1912
|
+
return parseReadResponse(response, path);
|
|
1913
|
+
},
|
|
1914
|
+
async account(address) {
|
|
1915
|
+
const path = `/chain/accounts/${encodeURIComponent(address)}`;
|
|
1916
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
1917
|
+
const body = await parseReadResponse(response, path);
|
|
1918
|
+
if (!body) {
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
return {
|
|
1922
|
+
address: body.address,
|
|
1923
|
+
ownerProgram: body.ownerProgram,
|
|
1924
|
+
lamports: body.lamports,
|
|
1925
|
+
executable: body.executable,
|
|
1926
|
+
data: decodeBase64(body.data),
|
|
1927
|
+
};
|
|
1928
|
+
},
|
|
1929
|
+
async mint(address) {
|
|
1930
|
+
const path = `/chain/mints/${encodeURIComponent(address)}`;
|
|
1931
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
1932
|
+
return parseReadResponse(response, path);
|
|
1933
|
+
},
|
|
1934
|
+
async tokenAccount(address) {
|
|
1935
|
+
const path = `/chain/token-accounts/${encodeURIComponent(address)}`;
|
|
1936
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path));
|
|
1937
|
+
return parseReadResponse(response, path);
|
|
1938
|
+
},
|
|
1939
|
+
async balance(input) {
|
|
1940
|
+
const path = '/chain/balances';
|
|
1941
|
+
const response = await fetchImpl(joinUrl(httpBaseUrl, path), {
|
|
1942
|
+
method: 'POST',
|
|
1943
|
+
headers: { 'content-type': 'application/json' },
|
|
1944
|
+
body: JSON.stringify(input),
|
|
1945
|
+
});
|
|
1946
|
+
return parseReadResponse(response, path);
|
|
1947
|
+
},
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1655
1951
|
/**
|
|
1656
1952
|
* PDA (Program Derived Address) derivation utilities.
|
|
1657
1953
|
*
|
|
@@ -1666,7 +1962,10 @@ function decodeBase58(str) {
|
|
|
1666
1962
|
if (str.length === 0) {
|
|
1667
1963
|
return new Uint8Array(0);
|
|
1668
1964
|
}
|
|
1669
|
-
|
|
1965
|
+
// Big-endian byte accumulator (stored little-endian here, reversed at the
|
|
1966
|
+
// end). Must start empty: a leading `[0]` produces a spurious extra byte for
|
|
1967
|
+
// all-zero values such as the System Program ("111...1").
|
|
1968
|
+
const bytes = [];
|
|
1670
1969
|
for (const char of str) {
|
|
1671
1970
|
const value = BASE58_ALPHABET.indexOf(char);
|
|
1672
1971
|
if (value === -1) {
|
|
@@ -1698,7 +1997,9 @@ function encodeBase58(bytes) {
|
|
|
1698
1997
|
if (bytes.length === 0) {
|
|
1699
1998
|
return '';
|
|
1700
1999
|
}
|
|
1701
|
-
|
|
2000
|
+
// Must start empty for the same reason as `decodeBase58`: a leading `[0]`
|
|
2001
|
+
// yields an extra '1' character when encoding all-zero inputs.
|
|
2002
|
+
const digits = [];
|
|
1702
2003
|
for (const byte of bytes) {
|
|
1703
2004
|
let carry = byte;
|
|
1704
2005
|
for (let i = 0; i < digits.length; i++) {
|
|
@@ -1719,25 +2020,125 @@ function encodeBase58(bytes) {
|
|
|
1719
2020
|
}
|
|
1720
2021
|
return digits.reverse().map(d => BASE58_ALPHABET[d]).join('');
|
|
1721
2022
|
}
|
|
2023
|
+
// SHA-256 round constants (first 32 bits of the fractional parts of the cube
|
|
2024
|
+
// roots of the first 64 primes).
|
|
2025
|
+
const SHA256_K = new Uint32Array([
|
|
2026
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
2027
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
2028
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
2029
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
2030
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
2031
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
2032
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
2033
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
2034
|
+
]);
|
|
2035
|
+
/**
|
|
2036
|
+
* Dependency-free, synchronous SHA-256.
|
|
2037
|
+
*
|
|
2038
|
+
* Used so PDA derivation works identically in browsers, Node (both CJS and
|
|
2039
|
+
* ESM), and bundlers without relying on `require('crypto')` or async WebCrypto.
|
|
2040
|
+
*/
|
|
2041
|
+
function sha256Pure(data) {
|
|
2042
|
+
const h = new Uint32Array([
|
|
2043
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
2044
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
2045
|
+
]);
|
|
2046
|
+
const bitLen = data.length * 8;
|
|
2047
|
+
// Pad: append 0x80, then zeros, until length ≡ 56 (mod 64), then 8-byte length.
|
|
2048
|
+
const paddedLen = ((data.length + 8) >> 6) * 64 + 64;
|
|
2049
|
+
const msg = new Uint8Array(paddedLen);
|
|
2050
|
+
msg.set(data);
|
|
2051
|
+
msg[data.length] = 0x80;
|
|
2052
|
+
// 64-bit big-endian bit length (high 32 bits assumed 0 for our seed sizes).
|
|
2053
|
+
const dv = new DataView(msg.buffer);
|
|
2054
|
+
dv.setUint32(paddedLen - 4, bitLen >>> 0, false);
|
|
2055
|
+
dv.setUint32(paddedLen - 8, Math.floor(bitLen / 0x100000000), false);
|
|
2056
|
+
const w = new Uint32Array(64);
|
|
2057
|
+
for (let offset = 0; offset < paddedLen; offset += 64) {
|
|
2058
|
+
for (let i = 0; i < 16; i++) {
|
|
2059
|
+
w[i] = dv.getUint32(offset + i * 4, false);
|
|
2060
|
+
}
|
|
2061
|
+
for (let i = 16; i < 64; i++) {
|
|
2062
|
+
const w15 = w[i - 15];
|
|
2063
|
+
const w2 = w[i - 2];
|
|
2064
|
+
const s0 = ((w15 >>> 7) | (w15 << 25)) ^ ((w15 >>> 18) | (w15 << 14)) ^ (w15 >>> 3);
|
|
2065
|
+
const s1 = ((w2 >>> 17) | (w2 << 15)) ^ ((w2 >>> 19) | (w2 << 13)) ^ (w2 >>> 10);
|
|
2066
|
+
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
|
|
2067
|
+
}
|
|
2068
|
+
let a = h[0], b = h[1], c = h[2], d = h[3];
|
|
2069
|
+
let e = h[4], f = h[5], g = h[6], hh = h[7];
|
|
2070
|
+
for (let i = 0; i < 64; i++) {
|
|
2071
|
+
const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
|
|
2072
|
+
const ch = (e & f) ^ (~e & g);
|
|
2073
|
+
const t1 = (hh + S1 + ch + SHA256_K[i] + w[i]) >>> 0;
|
|
2074
|
+
const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
|
|
2075
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
2076
|
+
const t2 = (S0 + maj) >>> 0;
|
|
2077
|
+
hh = g;
|
|
2078
|
+
g = f;
|
|
2079
|
+
f = e;
|
|
2080
|
+
e = (d + t1) >>> 0;
|
|
2081
|
+
d = c;
|
|
2082
|
+
c = b;
|
|
2083
|
+
b = a;
|
|
2084
|
+
a = (t1 + t2) >>> 0;
|
|
2085
|
+
}
|
|
2086
|
+
h[0] = (h[0] + a) >>> 0;
|
|
2087
|
+
h[1] = (h[1] + b) >>> 0;
|
|
2088
|
+
h[2] = (h[2] + c) >>> 0;
|
|
2089
|
+
h[3] = (h[3] + d) >>> 0;
|
|
2090
|
+
h[4] = (h[4] + e) >>> 0;
|
|
2091
|
+
h[5] = (h[5] + f) >>> 0;
|
|
2092
|
+
h[6] = (h[6] + g) >>> 0;
|
|
2093
|
+
h[7] = (h[7] + hh) >>> 0;
|
|
2094
|
+
}
|
|
2095
|
+
const out = new Uint8Array(32);
|
|
2096
|
+
const outView = new DataView(out.buffer);
|
|
2097
|
+
for (let i = 0; i < 8; i++) {
|
|
2098
|
+
outView.setUint32(i * 4, h[i], false);
|
|
2099
|
+
}
|
|
2100
|
+
return out;
|
|
2101
|
+
}
|
|
1722
2102
|
/**
|
|
1723
|
-
* SHA-256 hash function (synchronous
|
|
2103
|
+
* SHA-256 hash function (synchronous).
|
|
1724
2104
|
*/
|
|
1725
2105
|
function sha256Sync(data) {
|
|
1726
|
-
|
|
1727
|
-
const { createHash } = require('crypto');
|
|
1728
|
-
return new Uint8Array(createHash('sha256').update(Buffer.from(data)).digest());
|
|
2106
|
+
return sha256Pure(data);
|
|
1729
2107
|
}
|
|
1730
2108
|
/**
|
|
1731
|
-
* SHA-256 hash function (async
|
|
2109
|
+
* SHA-256 hash function (async). Uses WebCrypto when available for speed,
|
|
2110
|
+
* otherwise falls back to the pure implementation.
|
|
1732
2111
|
*/
|
|
1733
2112
|
async function sha256Async(data) {
|
|
1734
2113
|
if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.subtle) {
|
|
1735
|
-
// Create a copy of the data to ensure we have an ArrayBuffer
|
|
1736
2114
|
const copy = new Uint8Array(data);
|
|
1737
2115
|
const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', copy);
|
|
1738
2116
|
return new Uint8Array(hashBuffer);
|
|
1739
2117
|
}
|
|
1740
|
-
return
|
|
2118
|
+
return sha256Pure(data);
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Check if a 32-byte value is a valid point on the ed25519 curve.
|
|
2122
|
+
*
|
|
2123
|
+
* A valid PDA must be OFF the curve (it must NOT correspond to a real
|
|
2124
|
+
* ed25519 public key, so that no private key can ever sign for it).
|
|
2125
|
+
*
|
|
2126
|
+
* We determine on-curve status by attempting to decompress the candidate
|
|
2127
|
+
* as a compressed Edwards point. If decompression succeeds the value lies
|
|
2128
|
+
* on the curve; if it throws, the value is off-curve and is a valid PDA.
|
|
2129
|
+
* This matches the behaviour of `PublicKey.isOnCurve` in @solana/web3.js.
|
|
2130
|
+
*/
|
|
2131
|
+
function isOnCurve(publicKey) {
|
|
2132
|
+
if (publicKey.length !== 32) {
|
|
2133
|
+
return false;
|
|
2134
|
+
}
|
|
2135
|
+
try {
|
|
2136
|
+
Point.fromHex(publicKey);
|
|
2137
|
+
return true;
|
|
2138
|
+
}
|
|
2139
|
+
catch {
|
|
2140
|
+
return false;
|
|
2141
|
+
}
|
|
1741
2142
|
}
|
|
1742
2143
|
/**
|
|
1743
2144
|
* PDA marker bytes appended to seeds before hashing.
|
|
@@ -1805,7 +2206,7 @@ async function findProgramAddress(seeds, programId) {
|
|
|
1805
2206
|
for (let bump = 255; bump >= 0; bump--) {
|
|
1806
2207
|
const buffer = buildPdaBuffer(seeds, programIdBytes, bump);
|
|
1807
2208
|
const hash = await sha256Async(buffer);
|
|
1808
|
-
{
|
|
2209
|
+
if (!isOnCurve(hash)) {
|
|
1809
2210
|
return [encodeBase58(hash), bump];
|
|
1810
2211
|
}
|
|
1811
2212
|
}
|
|
@@ -1825,7 +2226,7 @@ function findProgramAddressSync(seeds, programId) {
|
|
|
1825
2226
|
for (let bump = 255; bump >= 0; bump--) {
|
|
1826
2227
|
const buffer = buildPdaBuffer(seeds, programIdBytes, bump);
|
|
1827
2228
|
const hash = sha256Sync(buffer);
|
|
1828
|
-
{
|
|
2229
|
+
if (!isOnCurve(hash)) {
|
|
1829
2230
|
return [encodeBase58(hash), bump];
|
|
1830
2231
|
}
|
|
1831
2232
|
}
|
|
@@ -1874,6 +2275,134 @@ function createPublicKeySeed(address) {
|
|
|
1874
2275
|
return decoded;
|
|
1875
2276
|
}
|
|
1876
2277
|
|
|
2278
|
+
/**
|
|
2279
|
+
* Typed serialization of PDA seed values.
|
|
2280
|
+
*
|
|
2281
|
+
* Shared by the standalone PDA DSL (`pda-dsl.ts`) and instruction account
|
|
2282
|
+
* resolution (`account-resolver.ts`). When a seed carries a declared type
|
|
2283
|
+
* (from the IDL or the `pdas!` registry), encoding is exact: pubkeys are
|
|
2284
|
+
* base58-decoded to 32 bytes, integers are little-endian at the declared
|
|
2285
|
+
* width. Without a type, legacy heuristics apply for backward compatibility.
|
|
2286
|
+
*/
|
|
2287
|
+
/**
|
|
2288
|
+
* Normalizes the type-name variants that IDLs and codegen produce
|
|
2289
|
+
* ("Pubkey", "publicKey", "solana_pubkey::Pubkey", "String", ...) to a
|
|
2290
|
+
* canonical seed type. Returns undefined for types that cannot be a seed.
|
|
2291
|
+
*/
|
|
2292
|
+
function normalizeSeedType(argType) {
|
|
2293
|
+
if (!argType)
|
|
2294
|
+
return undefined;
|
|
2295
|
+
// Strip any path qualifier (e.g. solana_pubkey::Pubkey).
|
|
2296
|
+
const parts = argType.split('::');
|
|
2297
|
+
const t = (parts[parts.length - 1] ?? argType).trim();
|
|
2298
|
+
if (/^[ui](8|16|32|64|128)$/.test(t)) {
|
|
2299
|
+
return t;
|
|
2300
|
+
}
|
|
2301
|
+
switch (t) {
|
|
2302
|
+
case 'pubkey':
|
|
2303
|
+
case 'Pubkey':
|
|
2304
|
+
case 'publicKey':
|
|
2305
|
+
case 'PublicKey':
|
|
2306
|
+
return 'pubkey';
|
|
2307
|
+
case 'string':
|
|
2308
|
+
case 'String':
|
|
2309
|
+
case 'str':
|
|
2310
|
+
return 'string';
|
|
2311
|
+
default:
|
|
2312
|
+
return undefined;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Serializes a PDA seed value.
|
|
2317
|
+
*
|
|
2318
|
+
* With a recognized `argType`, encoding is strict and width-exact; an
|
|
2319
|
+
* incompatible value throws rather than deriving a wrong address. Without
|
|
2320
|
+
* one, the legacy heuristics apply: raw bytes pass through, 43/44-character
|
|
2321
|
+
* strings are tried as base58, other strings are utf-8, numbers are 8-byte
|
|
2322
|
+
* little-endian u64.
|
|
2323
|
+
*/
|
|
2324
|
+
function serializeSeedValue(value, argType) {
|
|
2325
|
+
if (value instanceof Uint8Array) {
|
|
2326
|
+
return value;
|
|
2327
|
+
}
|
|
2328
|
+
const t = normalizeSeedType(argType);
|
|
2329
|
+
if (t === 'pubkey') {
|
|
2330
|
+
if (typeof value !== 'string') {
|
|
2331
|
+
throw new Error(`Pubkey seed requires a base58 string, got ${typeof value}`);
|
|
2332
|
+
}
|
|
2333
|
+
const decoded = decodeBase58(value);
|
|
2334
|
+
if (decoded.length !== 32) {
|
|
2335
|
+
throw new Error(`Pubkey seed '${value}' decoded to ${decoded.length} bytes, expected 32`);
|
|
2336
|
+
}
|
|
2337
|
+
return decoded;
|
|
2338
|
+
}
|
|
2339
|
+
if (t === 'string') {
|
|
2340
|
+
if (typeof value !== 'string') {
|
|
2341
|
+
throw new Error(`String seed requires a string value, got ${typeof value}`);
|
|
2342
|
+
}
|
|
2343
|
+
return new TextEncoder().encode(value);
|
|
2344
|
+
}
|
|
2345
|
+
if (t !== undefined) {
|
|
2346
|
+
// Numeric type at a declared width.
|
|
2347
|
+
if (typeof value !== 'bigint' && typeof value !== 'number') {
|
|
2348
|
+
throw new Error(`Numeric seed of type ${t} requires a number/bigint, got ${typeof value}`);
|
|
2349
|
+
}
|
|
2350
|
+
const bits = parseInt(t.slice(1), 10);
|
|
2351
|
+
return serializeNumber(value, bits / 8, t.startsWith('i'));
|
|
2352
|
+
}
|
|
2353
|
+
// --- Untyped: legacy heuristics. ---
|
|
2354
|
+
if (typeof value === 'string') {
|
|
2355
|
+
if (value.length === 43 || value.length === 44) {
|
|
2356
|
+
try {
|
|
2357
|
+
return decodeBase58(value);
|
|
2358
|
+
}
|
|
2359
|
+
catch {
|
|
2360
|
+
return new TextEncoder().encode(value);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
return new TextEncoder().encode(value);
|
|
2364
|
+
}
|
|
2365
|
+
if (typeof value === 'bigint' || typeof value === 'number') {
|
|
2366
|
+
return serializeNumber(value, 8, true);
|
|
2367
|
+
}
|
|
2368
|
+
throw new Error(`Cannot serialize value for PDA seed: ${typeof value}`);
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Little-endian two's-complement encoding at a fixed byte width, with an
|
|
2372
|
+
* overflow check so out-of-range values fail instead of silently truncating.
|
|
2373
|
+
*/
|
|
2374
|
+
function serializeNumber(value, size, signed) {
|
|
2375
|
+
const buffer = new Uint8Array(size);
|
|
2376
|
+
let n = typeof value === 'bigint' ? value : BigInt(value);
|
|
2377
|
+
const original = n;
|
|
2378
|
+
for (let i = 0; i < size; i++) {
|
|
2379
|
+
buffer[i] = Number(n & BigInt(0xff));
|
|
2380
|
+
n >>= BigInt(8);
|
|
2381
|
+
}
|
|
2382
|
+
const fits = signed ? n === BigInt(0) || n === BigInt(-1) : n === BigInt(0);
|
|
2383
|
+
if (!fits) {
|
|
2384
|
+
throw new Error(`Seed value ${original} does not fit in ${size * 8} bits`);
|
|
2385
|
+
}
|
|
2386
|
+
return buffer;
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
function getValueByPath(source, path) {
|
|
2390
|
+
if (!source) {
|
|
2391
|
+
return undefined;
|
|
2392
|
+
}
|
|
2393
|
+
if (Object.prototype.hasOwnProperty.call(source, path)) {
|
|
2394
|
+
return source[path];
|
|
2395
|
+
}
|
|
2396
|
+
let current = source;
|
|
2397
|
+
for (const segment of path.split('.')) {
|
|
2398
|
+
if (current === null || typeof current !== 'object') {
|
|
2399
|
+
return undefined;
|
|
2400
|
+
}
|
|
2401
|
+
current = current[segment];
|
|
2402
|
+
}
|
|
2403
|
+
return current;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
1877
2406
|
/**
|
|
1878
2407
|
* Topologically sort accounts so that dependencies (accountRef) are resolved first.
|
|
1879
2408
|
* Non-PDA accounts come first, then PDAs in dependency order.
|
|
@@ -1960,13 +2489,32 @@ function resolveAccounts(accountMetas, args, options) {
|
|
|
1960
2489
|
missing.push(meta.name);
|
|
1961
2490
|
}
|
|
1962
2491
|
}
|
|
1963
|
-
// Return accounts in original order (as defined in accountMetas)
|
|
2492
|
+
// Return accounts in original order (as defined in accountMetas).
|
|
2493
|
+
//
|
|
2494
|
+
// Omitted optional accounts that precede a resolved account cannot simply
|
|
2495
|
+
// be dropped — that would shift every later account into the wrong slot.
|
|
2496
|
+
// Anchor's convention is to pass the program ID as a placeholder; trailing
|
|
2497
|
+
// omitted optionals are dropped as usual.
|
|
2498
|
+
const lastResolvedIndex = accountMetas.reduce((last, meta, index) => (resolvedMap[meta.name] ? index : last), -1);
|
|
1964
2499
|
const orderedAccounts = [];
|
|
1965
|
-
for (
|
|
2500
|
+
for (let i = 0; i < accountMetas.length; i++) {
|
|
2501
|
+
const meta = accountMetas[i];
|
|
1966
2502
|
const resolved = resolvedMap[meta.name];
|
|
1967
2503
|
if (resolved) {
|
|
1968
2504
|
orderedAccounts.push(resolved);
|
|
1969
2505
|
}
|
|
2506
|
+
else if (meta.isOptional && i < lastResolvedIndex) {
|
|
2507
|
+
if (!options.programId) {
|
|
2508
|
+
throw new Error('Omitted optional account "' + meta.name + '" precedes other accounts and needs ' +
|
|
2509
|
+
'the program ID as a placeholder, but no programId was provided in options.');
|
|
2510
|
+
}
|
|
2511
|
+
orderedAccounts.push({
|
|
2512
|
+
name: meta.name,
|
|
2513
|
+
address: options.programId,
|
|
2514
|
+
isSigner: false,
|
|
2515
|
+
isWritable: false,
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
1970
2518
|
}
|
|
1971
2519
|
return {
|
|
1972
2520
|
accounts: orderedAccounts,
|
|
@@ -1976,24 +2524,25 @@ function resolveAccounts(accountMetas, args, options) {
|
|
|
1976
2524
|
function resolveSingleAccount(meta, args, options, resolvedMap) {
|
|
1977
2525
|
switch (meta.category) {
|
|
1978
2526
|
case 'signer':
|
|
1979
|
-
return resolveSignerAccount(meta, options.wallet);
|
|
2527
|
+
return resolveSignerAccount(meta, options.accounts, options.wallet);
|
|
1980
2528
|
case 'known':
|
|
1981
2529
|
return resolveKnownAccount(meta);
|
|
1982
2530
|
case 'pda':
|
|
1983
|
-
return resolvePdaAccount(meta, args, resolvedMap, options.programId);
|
|
2531
|
+
return resolvePdaAccount(meta, args, resolvedMap, options.programId, options.resolve);
|
|
1984
2532
|
case 'userProvided':
|
|
1985
2533
|
return resolveUserProvidedAccount(meta, options.accounts);
|
|
1986
2534
|
default:
|
|
1987
2535
|
return null;
|
|
1988
2536
|
}
|
|
1989
2537
|
}
|
|
1990
|
-
function resolveSignerAccount(meta, wallet) {
|
|
1991
|
-
|
|
2538
|
+
function resolveSignerAccount(meta, accounts, wallet) {
|
|
2539
|
+
const address = accounts?.[meta.name] ?? wallet?.publicKey;
|
|
2540
|
+
if (!address) {
|
|
1992
2541
|
return null;
|
|
1993
2542
|
}
|
|
1994
2543
|
return {
|
|
1995
2544
|
name: meta.name,
|
|
1996
|
-
address
|
|
2545
|
+
address,
|
|
1997
2546
|
isSigner: true,
|
|
1998
2547
|
isWritable: meta.isWritable,
|
|
1999
2548
|
};
|
|
@@ -2009,7 +2558,7 @@ function resolveKnownAccount(meta) {
|
|
|
2009
2558
|
isWritable: meta.isWritable,
|
|
2010
2559
|
};
|
|
2011
2560
|
}
|
|
2012
|
-
function resolvePdaAccount(meta, args, resolvedMap, programId) {
|
|
2561
|
+
function resolvePdaAccount(meta, args, resolvedMap, programId, resolve) {
|
|
2013
2562
|
if (!meta.pdaConfig) {
|
|
2014
2563
|
return null;
|
|
2015
2564
|
}
|
|
@@ -2026,13 +2575,16 @@ function resolvePdaAccount(meta, args, resolvedMap, programId) {
|
|
|
2026
2575
|
case 'literal':
|
|
2027
2576
|
seeds.push(createSeed(seed.value));
|
|
2028
2577
|
break;
|
|
2578
|
+
case 'bytes':
|
|
2579
|
+
seeds.push(Uint8Array.from(seed.value));
|
|
2580
|
+
break;
|
|
2029
2581
|
case 'argRef': {
|
|
2030
|
-
const argValue = args
|
|
2582
|
+
const argValue = getValueByPath(args, seed.argName) ?? getValueByPath(resolve, seed.argName);
|
|
2031
2583
|
if (argValue === undefined) {
|
|
2032
2584
|
throw new Error('PDA seed references missing argument: ' + seed.argName +
|
|
2033
2585
|
' (for account "' + meta.name + '")');
|
|
2034
2586
|
}
|
|
2035
|
-
seeds.push(
|
|
2587
|
+
seeds.push(serializeSeedValue(argValue, seed.argType));
|
|
2036
2588
|
break;
|
|
2037
2589
|
}
|
|
2038
2590
|
case 'accountRef': {
|
|
@@ -2100,6 +2652,11 @@ function serializeInstructionData(discriminator, args, schema) {
|
|
|
2100
2652
|
const buffers = [Buffer.from(discriminator)];
|
|
2101
2653
|
for (const field of schema) {
|
|
2102
2654
|
const value = args[field.name];
|
|
2655
|
+
// Option fields treat undefined as None; everything else must be present
|
|
2656
|
+
// (silently encoding zeros for a missing arg corrupts instruction data).
|
|
2657
|
+
if (value === undefined && !(typeof field.type === 'object' && 'option' in field.type)) {
|
|
2658
|
+
throw new Error(`Missing required argument "${field.name}" (type ${JSON.stringify(field.type)})`);
|
|
2659
|
+
}
|
|
2103
2660
|
const serialized = serializeValue(value, field.type);
|
|
2104
2661
|
buffers.push(serialized);
|
|
2105
2662
|
}
|
|
@@ -2118,8 +2675,108 @@ function serializeValue(value, type) {
|
|
|
2118
2675
|
if ('array' in type) {
|
|
2119
2676
|
return serializeArray(value, type.array[0], type.array[1]);
|
|
2120
2677
|
}
|
|
2678
|
+
if ('hashMap' in type) {
|
|
2679
|
+
return serializeHashMap(value, type.hashMap[0], type.hashMap[1]);
|
|
2680
|
+
}
|
|
2681
|
+
if ('struct' in type) {
|
|
2682
|
+
return serializeStruct(value, type.struct);
|
|
2683
|
+
}
|
|
2684
|
+
if ('enum' in type) {
|
|
2685
|
+
return serializeEnum(value, type.enum);
|
|
2686
|
+
}
|
|
2121
2687
|
throw new Error(`Unknown type: ${JSON.stringify(type)}`);
|
|
2122
2688
|
}
|
|
2689
|
+
function serializeStruct(value, fields) {
|
|
2690
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
2691
|
+
throw new Error(`Struct value must be a plain object, got ${typeof value}`);
|
|
2692
|
+
}
|
|
2693
|
+
const obj = value;
|
|
2694
|
+
const buffers = [];
|
|
2695
|
+
for (const field of fields) {
|
|
2696
|
+
const fieldValue = obj[field.name];
|
|
2697
|
+
if (fieldValue === undefined &&
|
|
2698
|
+
!(typeof field.type === 'object' && 'option' in field.type)) {
|
|
2699
|
+
throw new Error(`Missing required struct field "${field.name}"`);
|
|
2700
|
+
}
|
|
2701
|
+
buffers.push(serializeValue(fieldValue, field.type));
|
|
2702
|
+
}
|
|
2703
|
+
return Buffer.concat(buffers);
|
|
2704
|
+
}
|
|
2705
|
+
function serializeHashMap(value, keyType, valueType) {
|
|
2706
|
+
if (keyType !== 'string') {
|
|
2707
|
+
throw new Error(`Instruction hashMap keys must use the 'string' schema, got ${JSON.stringify(keyType)}`);
|
|
2708
|
+
}
|
|
2709
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
2710
|
+
throw new Error(`HashMap value must be a plain object, got ${typeof value}`);
|
|
2711
|
+
}
|
|
2712
|
+
const entries = Object.entries(value).sort(([left], [right]) =>
|
|
2713
|
+
// Rust/Borsh sorts String keys by their UTF-8 bytes before serializing.
|
|
2714
|
+
Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8')));
|
|
2715
|
+
const len = Buffer.alloc(4);
|
|
2716
|
+
len.writeUInt32LE(entries.length, 0);
|
|
2717
|
+
const entryBuffers = [];
|
|
2718
|
+
for (const [key, entryValue] of entries) {
|
|
2719
|
+
entryBuffers.push(serializePrimitive(key, 'string'));
|
|
2720
|
+
entryBuffers.push(serializeValue(entryValue, valueType));
|
|
2721
|
+
}
|
|
2722
|
+
return Buffer.concat([len, ...entryBuffers]);
|
|
2723
|
+
}
|
|
2724
|
+
function variantName(variant) {
|
|
2725
|
+
return typeof variant === 'string' ? variant : variant.name;
|
|
2726
|
+
}
|
|
2727
|
+
function serializeEnum(value, variants) {
|
|
2728
|
+
// Numeric value: a bare variant index (fieldless variants only).
|
|
2729
|
+
if (typeof value === 'number') {
|
|
2730
|
+
if (!Number.isInteger(value) || value < 0 || value >= variants.length) {
|
|
2731
|
+
throw new Error(`Enum variant index ${value} out of range (0..${variants.length - 1})`);
|
|
2732
|
+
}
|
|
2733
|
+
const variant = variants[value];
|
|
2734
|
+
if (typeof variant !== 'string') {
|
|
2735
|
+
throw new Error(`Enum variant "${variantName(variant)}" carries data; pass { ${variantName(variant)}: ... } instead of an index`);
|
|
2736
|
+
}
|
|
2737
|
+
return Buffer.from([value]);
|
|
2738
|
+
}
|
|
2739
|
+
// String value: a fieldless variant by name.
|
|
2740
|
+
if (typeof value === 'string') {
|
|
2741
|
+
const index = variants.findIndex((v) => variantName(v) === value);
|
|
2742
|
+
if (index === -1) {
|
|
2743
|
+
throw new Error(`Unknown enum variant "${value}". Expected one of: ${variants.map(variantName).join(', ')}`);
|
|
2744
|
+
}
|
|
2745
|
+
const variant = variants[index];
|
|
2746
|
+
if (typeof variant !== 'string') {
|
|
2747
|
+
throw new Error(`Enum variant "${value}" carries data; pass { ${value}: ... } instead of a bare name`);
|
|
2748
|
+
}
|
|
2749
|
+
return Buffer.from([index]);
|
|
2750
|
+
}
|
|
2751
|
+
// Object value: { variantName: payload } for data-carrying variants.
|
|
2752
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
2753
|
+
const keys = Object.keys(value);
|
|
2754
|
+
if (keys.length !== 1) {
|
|
2755
|
+
throw new Error(`Enum value must be a single-key object ({ variantName: payload }), got keys [${keys.join(', ')}]`);
|
|
2756
|
+
}
|
|
2757
|
+
const key = keys[0];
|
|
2758
|
+
const payload = value[key];
|
|
2759
|
+
const index = variants.findIndex((v) => variantName(v) === key);
|
|
2760
|
+
if (index === -1) {
|
|
2761
|
+
throw new Error(`Unknown enum variant "${key}". Expected one of: ${variants.map(variantName).join(', ')}`);
|
|
2762
|
+
}
|
|
2763
|
+
const variant = variants[index];
|
|
2764
|
+
if (typeof variant === 'string') {
|
|
2765
|
+
throw new Error(`Enum variant "${key}" is fieldless; pass '${key}' instead of an object`);
|
|
2766
|
+
}
|
|
2767
|
+
const prefix = Buffer.from([index]);
|
|
2768
|
+
if ('fields' in variant) {
|
|
2769
|
+
return Buffer.concat([prefix, serializeStruct(payload, variant.fields)]);
|
|
2770
|
+
}
|
|
2771
|
+
const tuple = payload;
|
|
2772
|
+
if (!Array.isArray(tuple) || tuple.length !== variant.tuple.length) {
|
|
2773
|
+
throw new Error(`Enum variant "${key}" expects a tuple of length ${variant.tuple.length}`);
|
|
2774
|
+
}
|
|
2775
|
+
const elements = variant.tuple.map((elementType, i) => serializeValue(tuple[i], elementType));
|
|
2776
|
+
return Buffer.concat([prefix, ...elements]);
|
|
2777
|
+
}
|
|
2778
|
+
throw new Error(`Cannot serialize enum from value of type ${typeof value}`);
|
|
2779
|
+
}
|
|
2123
2780
|
function serializePrimitive(value, type) {
|
|
2124
2781
|
switch (type) {
|
|
2125
2782
|
case 'u8':
|
|
@@ -2160,21 +2817,60 @@ function serializePrimitive(value, type) {
|
|
|
2160
2817
|
case 'i128':
|
|
2161
2818
|
const i128 = Buffer.alloc(16);
|
|
2162
2819
|
const bigI128 = BigInt(value);
|
|
2163
|
-
|
|
2820
|
+
// The masked low limb is always non-negative, so it must be written
|
|
2821
|
+
// unsigned; the arithmetic-shifted high limb carries the sign.
|
|
2822
|
+
i128.writeBigUInt64LE(bigI128 & BigInt('0xFFFFFFFFFFFFFFFF'), 0);
|
|
2164
2823
|
i128.writeBigInt64LE(bigI128 >> BigInt(64), 8);
|
|
2165
2824
|
return i128;
|
|
2825
|
+
case 'f32': {
|
|
2826
|
+
const f32 = Buffer.alloc(4);
|
|
2827
|
+
f32.writeFloatLE(value, 0);
|
|
2828
|
+
return f32;
|
|
2829
|
+
}
|
|
2830
|
+
case 'f64': {
|
|
2831
|
+
const f64 = Buffer.alloc(8);
|
|
2832
|
+
f64.writeDoubleLE(value, 0);
|
|
2833
|
+
return f64;
|
|
2834
|
+
}
|
|
2166
2835
|
case 'bool':
|
|
2167
2836
|
return Buffer.from([value ? 1 : 0]);
|
|
2837
|
+
case 'bytes': {
|
|
2838
|
+
// Borsh bytes: u32 LE length prefix + raw bytes.
|
|
2839
|
+
const raw = value instanceof Uint8Array
|
|
2840
|
+
? value
|
|
2841
|
+
: Array.isArray(value)
|
|
2842
|
+
? Uint8Array.from(value)
|
|
2843
|
+
: null;
|
|
2844
|
+
if (raw === null) {
|
|
2845
|
+
throw new Error(`Cannot serialize bytes from value of type ${typeof value}`);
|
|
2846
|
+
}
|
|
2847
|
+
const lenPrefix = Buffer.alloc(4);
|
|
2848
|
+
lenPrefix.writeUInt32LE(raw.length, 0);
|
|
2849
|
+
return Buffer.concat([lenPrefix, Buffer.from(raw)]);
|
|
2850
|
+
}
|
|
2168
2851
|
case 'string':
|
|
2169
2852
|
const str = value;
|
|
2170
2853
|
const strBytes = Buffer.from(str, 'utf-8');
|
|
2171
2854
|
const strLen = Buffer.alloc(4);
|
|
2172
2855
|
strLen.writeUInt32LE(strBytes.length, 0);
|
|
2173
2856
|
return Buffer.concat([strLen, strBytes]);
|
|
2174
|
-
case 'pubkey':
|
|
2175
|
-
// Public key is 32 bytes
|
|
2176
|
-
|
|
2177
|
-
|
|
2857
|
+
case 'pubkey': {
|
|
2858
|
+
// Public key is 32 bytes. Accept base58 strings or raw 32-byte buffers.
|
|
2859
|
+
if (value instanceof Uint8Array) {
|
|
2860
|
+
if (value.length !== 32) {
|
|
2861
|
+
throw new Error(`Invalid pubkey byte length: expected 32, got ${value.length}`);
|
|
2862
|
+
}
|
|
2863
|
+
return Buffer.from(value);
|
|
2864
|
+
}
|
|
2865
|
+
if (typeof value === 'string') {
|
|
2866
|
+
const decoded = decodeBase58(value);
|
|
2867
|
+
if (decoded.length !== 32) {
|
|
2868
|
+
throw new Error(`Invalid pubkey: '${value}' decoded to ${decoded.length} bytes, expected 32`);
|
|
2869
|
+
}
|
|
2870
|
+
return Buffer.from(decoded);
|
|
2871
|
+
}
|
|
2872
|
+
throw new Error(`Cannot serialize pubkey from value of type ${typeof value}`);
|
|
2873
|
+
}
|
|
2178
2874
|
default:
|
|
2179
2875
|
throw new Error(`Unknown primitive type: ${type}`);
|
|
2180
2876
|
}
|
|
@@ -2201,59 +2897,7 @@ function serializeArray(values, elementType, length) {
|
|
|
2201
2897
|
}
|
|
2202
2898
|
|
|
2203
2899
|
/**
|
|
2204
|
-
*
|
|
2205
|
-
*
|
|
2206
|
-
* @param signature - Transaction signature
|
|
2207
|
-
* @param level - Desired confirmation level
|
|
2208
|
-
* @param timeout - Maximum wait time in milliseconds
|
|
2209
|
-
* @returns Confirmation result
|
|
2210
|
-
*/
|
|
2211
|
-
async function waitForConfirmation(signature, level = 'confirmed', timeout = 60000) {
|
|
2212
|
-
const startTime = Date.now();
|
|
2213
|
-
while (Date.now() - startTime < timeout) {
|
|
2214
|
-
const status = await checkTransactionStatus();
|
|
2215
|
-
if (status.err) {
|
|
2216
|
-
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
|
|
2217
|
-
}
|
|
2218
|
-
if (isConfirmationLevelSufficient(status.confirmations, level)) {
|
|
2219
|
-
return {
|
|
2220
|
-
level,
|
|
2221
|
-
slot: status.slot,
|
|
2222
|
-
};
|
|
2223
|
-
}
|
|
2224
|
-
await sleep(1000);
|
|
2225
|
-
}
|
|
2226
|
-
throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
|
|
2227
|
-
}
|
|
2228
|
-
async function checkTransactionStatus(_signature) {
|
|
2229
|
-
// In production, query the Solana RPC
|
|
2230
|
-
return {
|
|
2231
|
-
err: null,
|
|
2232
|
-
confirmations: 32,
|
|
2233
|
-
slot: 123456789,
|
|
2234
|
-
};
|
|
2235
|
-
}
|
|
2236
|
-
function isConfirmationLevelSufficient(confirmations, level) {
|
|
2237
|
-
if (confirmations === null) {
|
|
2238
|
-
return false;
|
|
2239
|
-
}
|
|
2240
|
-
switch (level) {
|
|
2241
|
-
case 'processed':
|
|
2242
|
-
return confirmations >= 0;
|
|
2243
|
-
case 'confirmed':
|
|
2244
|
-
return confirmations >= 1;
|
|
2245
|
-
case 'finalized':
|
|
2246
|
-
return confirmations >= 32;
|
|
2247
|
-
default:
|
|
2248
|
-
return false;
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
function sleep(ms) {
|
|
2252
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
/**
|
|
2256
|
-
* Parses and handles instruction errors.
|
|
2900
|
+
* Parses and handles instruction errors.
|
|
2257
2901
|
*/
|
|
2258
2902
|
/**
|
|
2259
2903
|
* Parses an error returned from a Solana transaction.
|
|
@@ -2311,91 +2955,159 @@ function extractErrorCode(error) {
|
|
|
2311
2955
|
function formatProgramError(error) {
|
|
2312
2956
|
return `${error.name} (${error.code}): ${error.message}`;
|
|
2313
2957
|
}
|
|
2314
|
-
|
|
2315
2958
|
/**
|
|
2316
|
-
*
|
|
2959
|
+
* Error thrown when an instruction fails to send and the underlying failure
|
|
2960
|
+
* could be parsed against the handler's IDL error definitions.
|
|
2317
2961
|
*/
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2962
|
+
class InstructionError extends Error {
|
|
2963
|
+
constructor(message, programError, cause) {
|
|
2964
|
+
super(message);
|
|
2965
|
+
this.name = 'InstructionError';
|
|
2966
|
+
this.programError = programError;
|
|
2967
|
+
this.cause = cause;
|
|
2322
2968
|
}
|
|
2323
|
-
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
/**
|
|
2972
|
+
* Creates a data-driven instruction handler.
|
|
2973
|
+
*
|
|
2974
|
+
* The returned handler implements `build()` generically: it serializes args
|
|
2975
|
+
* via the schema-driven serializer and constructs the account key list from
|
|
2976
|
+
* the resolved, ordered accounts. No imperative per-instruction code is
|
|
2977
|
+
* required, which keeps generated SDKs tiny and puts all serialization logic
|
|
2978
|
+
* in one tested place.
|
|
2979
|
+
*/
|
|
2980
|
+
function createInstructionHandler(config) {
|
|
2981
|
+
const discriminator = config.discriminator instanceof Uint8Array
|
|
2982
|
+
? config.discriminator
|
|
2983
|
+
: Uint8Array.from(config.discriminator);
|
|
2984
|
+
const argNames = config.args.map((a) => a.name);
|
|
2985
|
+
const errors = config.errors ?? [];
|
|
2986
|
+
return {
|
|
2987
|
+
programId: config.programId,
|
|
2988
|
+
accounts: config.accounts,
|
|
2989
|
+
errors,
|
|
2990
|
+
argNames,
|
|
2991
|
+
build(args, resolved) {
|
|
2992
|
+
const data = serializeInstructionData(discriminator, args, config.args);
|
|
2993
|
+
return {
|
|
2994
|
+
programId: config.programId,
|
|
2995
|
+
keys: resolved.map((r) => ({
|
|
2996
|
+
pubkey: r.address,
|
|
2997
|
+
isSigner: r.isSigner,
|
|
2998
|
+
isWritable: r.isWritable,
|
|
2999
|
+
})),
|
|
3000
|
+
data,
|
|
3001
|
+
};
|
|
3002
|
+
},
|
|
3003
|
+
};
|
|
2324
3004
|
}
|
|
2325
3005
|
/**
|
|
2326
|
-
*
|
|
3006
|
+
* Splits a merged params object into serialized args and account overrides.
|
|
2327
3007
|
*
|
|
2328
|
-
*
|
|
2329
|
-
*
|
|
2330
|
-
*
|
|
2331
|
-
*
|
|
2332
|
-
*
|
|
3008
|
+
* Keys matching a declared argument name are args; keys matching a declared
|
|
3009
|
+
* account name (with a string value) are account address overrides. This
|
|
3010
|
+
* applies to signer slots too, allowing explicit signer addresses to override
|
|
3011
|
+
* the wallet fallback. Anything else throws — a typo'd key silently dropped
|
|
3012
|
+
* here would otherwise change the built instruction. `options.accounts` on
|
|
3013
|
+
* {@link BuildOptions} remains an unvalidated escape hatch for advanced callers.
|
|
3014
|
+
*/
|
|
3015
|
+
function splitParams(handler, params) {
|
|
3016
|
+
const argNameSet = new Set(handler.argNames);
|
|
3017
|
+
const accountNameSet = new Set(handler.accounts.map((a) => a.name));
|
|
3018
|
+
const args = {};
|
|
3019
|
+
const accountOverrides = {};
|
|
3020
|
+
let resolve;
|
|
3021
|
+
for (const [key, value] of Object.entries(params)) {
|
|
3022
|
+
if (argNameSet.has(key)) {
|
|
3023
|
+
args[key] = value;
|
|
3024
|
+
}
|
|
3025
|
+
else if (key === 'resolve' && !accountNameSet.has(key)) {
|
|
3026
|
+
if (value === undefined) {
|
|
3027
|
+
continue;
|
|
3028
|
+
}
|
|
3029
|
+
if (value === null || Array.isArray(value) || typeof value !== 'object') {
|
|
3030
|
+
throw new Error('Parameter "resolve" must be an object when provided');
|
|
3031
|
+
}
|
|
3032
|
+
resolve = value;
|
|
3033
|
+
}
|
|
3034
|
+
else if (accountNameSet.has(key)) {
|
|
3035
|
+
if (typeof value !== 'string') {
|
|
3036
|
+
// Non-string values are not valid account addresses.
|
|
3037
|
+
throw new Error(`Parameter "${key}" is not a known argument and is not a base58 account address`);
|
|
3038
|
+
}
|
|
3039
|
+
accountOverrides[key] = value;
|
|
3040
|
+
}
|
|
3041
|
+
else {
|
|
3042
|
+
throw new Error(`Unknown parameter "${key}". Expected one of args [${[...argNameSet].join(', ')}] ` +
|
|
3043
|
+
`or accounts [${[...accountNameSet].join(', ')}]`);
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
return { args, accountOverrides, resolve };
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* Builds a {@link BuiltInstruction} from a handler and a merged params object.
|
|
2333
3050
|
*
|
|
2334
|
-
*
|
|
2335
|
-
*
|
|
2336
|
-
* @param options - Execution options
|
|
2337
|
-
* @returns Execution result with signature
|
|
3051
|
+
* This is a pure function: it performs no network access. It is the unit of
|
|
3052
|
+
* composition for batching (`wallet.signAndSend([a, b, c])`).
|
|
2338
3053
|
*/
|
|
2339
|
-
|
|
2340
|
-
|
|
3054
|
+
function buildInstruction(handler, params, options = {}) {
|
|
3055
|
+
const { args, accountOverrides, resolve } = splitParams(handler, params);
|
|
2341
3056
|
const resolutionOptions = {
|
|
2342
|
-
accounts: options.accounts,
|
|
3057
|
+
accounts: { ...accountOverrides, ...options.accounts },
|
|
3058
|
+
resolve,
|
|
2343
3059
|
wallet: options.wallet,
|
|
2344
|
-
programId: handler.programId,
|
|
3060
|
+
programId: handler.programId,
|
|
2345
3061
|
};
|
|
2346
3062
|
const resolution = resolveAccounts(handler.accounts, args, resolutionOptions);
|
|
2347
3063
|
validateAccountResolution(resolution);
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
// Step 3: Build transaction from the built instruction
|
|
2352
|
-
const transaction = buildTransaction(instruction);
|
|
2353
|
-
// Step 4: Sign and send
|
|
2354
|
-
if (!options.wallet) {
|
|
2355
|
-
throw new Error('Wallet required to sign transaction');
|
|
3064
|
+
const instruction = handler.build(args, resolution.accounts);
|
|
3065
|
+
if (options.remainingAccounts?.length) {
|
|
3066
|
+
instruction.keys.push(...options.remainingAccounts);
|
|
2356
3067
|
}
|
|
2357
|
-
|
|
2358
|
-
// Step 5: Wait for confirmation
|
|
2359
|
-
const confirmationLevel = options.confirmationLevel ?? 'confirmed';
|
|
2360
|
-
const timeout = options.timeout ?? 60000;
|
|
2361
|
-
const confirmation = await waitForConfirmation(signature, confirmationLevel, timeout);
|
|
2362
|
-
return {
|
|
2363
|
-
signature,
|
|
2364
|
-
confirmationLevel: confirmation.level,
|
|
2365
|
-
slot: confirmation.slot,
|
|
2366
|
-
};
|
|
3068
|
+
return instruction;
|
|
2367
3069
|
}
|
|
2368
3070
|
/**
|
|
2369
|
-
*
|
|
3071
|
+
* Builds, signs, and sends an instruction via the wallet adapter.
|
|
2370
3072
|
*
|
|
2371
|
-
*
|
|
2372
|
-
*
|
|
3073
|
+
* The core SDK does not touch RPC: the adapter owns blockhash, compilation,
|
|
3074
|
+
* signing, sending, and confirmation. On failure, program errors are parsed
|
|
3075
|
+
* against the handler's IDL error definitions and surfaced as an
|
|
3076
|
+
* {@link InstructionError}.
|
|
2373
3077
|
*/
|
|
2374
|
-
function
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
keys: instruction.keys,
|
|
2382
|
-
data: Array.from(instruction.data),
|
|
2383
|
-
}],
|
|
3078
|
+
async function executeInstruction(handler, params, options = {}) {
|
|
3079
|
+
const instruction = buildInstruction(handler, params, options);
|
|
3080
|
+
if (!options.wallet) {
|
|
3081
|
+
throw new Error('Wallet required to sign and send transaction');
|
|
3082
|
+
}
|
|
3083
|
+
const sendOptions = {
|
|
3084
|
+
...options.send,
|
|
2384
3085
|
};
|
|
3086
|
+
if (options.confirmationLevel !== undefined) {
|
|
3087
|
+
sendOptions.confirmationLevel = options.confirmationLevel;
|
|
3088
|
+
}
|
|
3089
|
+
try {
|
|
3090
|
+
const result = await options.wallet.signAndSend([instruction], sendOptions);
|
|
3091
|
+
return { signature: result.signature, slot: result.slot };
|
|
3092
|
+
}
|
|
3093
|
+
catch (err) {
|
|
3094
|
+
const programError = parseInstructionError(err, handler.errors);
|
|
3095
|
+
if (programError) {
|
|
3096
|
+
throw new InstructionError(`${programError.name} (${programError.code}): ${programError.message}`, programError, err);
|
|
3097
|
+
}
|
|
3098
|
+
throw err;
|
|
3099
|
+
}
|
|
2385
3100
|
}
|
|
2386
3101
|
/**
|
|
2387
3102
|
* Creates an instruction executor bound to a specific wallet.
|
|
2388
|
-
*
|
|
2389
|
-
* @param wallet - Wallet adapter
|
|
2390
|
-
* @returns Bound executor function
|
|
2391
3103
|
*/
|
|
2392
3104
|
function createInstructionExecutor(wallet) {
|
|
2393
3105
|
return {
|
|
2394
|
-
execute: async (handler,
|
|
2395
|
-
return executeInstruction(handler,
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
});
|
|
3106
|
+
execute: async (handler, params, options) => {
|
|
3107
|
+
return executeInstruction(handler, params, { ...options, wallet });
|
|
3108
|
+
},
|
|
3109
|
+
build: (handler, params, options) => {
|
|
3110
|
+
return buildInstruction(handler, params, { ...options, wallet });
|
|
2399
3111
|
},
|
|
2400
3112
|
};
|
|
2401
3113
|
}
|
|
@@ -2420,11 +3132,11 @@ function resolveSeeds(seeds, context) {
|
|
|
2420
3132
|
case 'bytes':
|
|
2421
3133
|
return seed.value;
|
|
2422
3134
|
case 'argRef': {
|
|
2423
|
-
const value = context.args
|
|
3135
|
+
const value = getValueByPath(context.args, seed.argName) ?? getValueByPath(context.resolve, seed.argName);
|
|
2424
3136
|
if (value === undefined) {
|
|
2425
3137
|
throw new Error(`Missing arg for PDA seed: ${seed.argName}`);
|
|
2426
3138
|
}
|
|
2427
|
-
return
|
|
3139
|
+
return serializeSeedValue(value, seed.argType);
|
|
2428
3140
|
}
|
|
2429
3141
|
case 'accountRef': {
|
|
2430
3142
|
const address = context.accounts?.[seed.accountName];
|
|
@@ -2436,47 +3148,6 @@ function resolveSeeds(seeds, context) {
|
|
|
2436
3148
|
}
|
|
2437
3149
|
});
|
|
2438
3150
|
}
|
|
2439
|
-
function serializeArgForSeed(value, argType) {
|
|
2440
|
-
if (value instanceof Uint8Array) {
|
|
2441
|
-
return value;
|
|
2442
|
-
}
|
|
2443
|
-
if (typeof value === 'string') {
|
|
2444
|
-
if (value.length === 43 || value.length === 44) {
|
|
2445
|
-
try {
|
|
2446
|
-
return decodeBase58(value);
|
|
2447
|
-
}
|
|
2448
|
-
catch {
|
|
2449
|
-
return new TextEncoder().encode(value);
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
return new TextEncoder().encode(value);
|
|
2453
|
-
}
|
|
2454
|
-
if (typeof value === 'bigint' || typeof value === 'number') {
|
|
2455
|
-
const size = getArgSize(argType);
|
|
2456
|
-
return serializeNumber(value, size);
|
|
2457
|
-
}
|
|
2458
|
-
throw new Error(`Cannot serialize value for PDA seed: ${typeof value}`);
|
|
2459
|
-
}
|
|
2460
|
-
function getArgSize(argType) {
|
|
2461
|
-
if (!argType)
|
|
2462
|
-
return 8;
|
|
2463
|
-
const match = argType.match(/^[ui](\d+)$/);
|
|
2464
|
-
if (match && match[1]) {
|
|
2465
|
-
return parseInt(match[1], 10) / 8;
|
|
2466
|
-
}
|
|
2467
|
-
if (argType === 'pubkey')
|
|
2468
|
-
return 32;
|
|
2469
|
-
return 8;
|
|
2470
|
-
}
|
|
2471
|
-
function serializeNumber(value, size) {
|
|
2472
|
-
const buffer = new Uint8Array(size);
|
|
2473
|
-
let n = typeof value === 'bigint' ? value : BigInt(value);
|
|
2474
|
-
for (let i = 0; i < size; i++) {
|
|
2475
|
-
buffer[i] = Number(n & BigInt(0xff));
|
|
2476
|
-
n >>= BigInt(8);
|
|
2477
|
-
}
|
|
2478
|
-
return buffer;
|
|
2479
|
-
}
|
|
2480
3151
|
function pda(programId, ...seeds) {
|
|
2481
3152
|
return {
|
|
2482
3153
|
seeds,
|
|
@@ -2502,530 +3173,1183 @@ function createProgramPdas(pdas) {
|
|
|
2502
3173
|
return pdas;
|
|
2503
3174
|
}
|
|
2504
3175
|
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
}
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
reconnectIntervals: options.reconnectIntervals,
|
|
2517
|
-
maxReconnectAttempts: options.maxReconnectAttempts,
|
|
2518
|
-
auth: options.auth,
|
|
2519
|
-
});
|
|
2520
|
-
this.subscriptionRegistry = new SubscriptionRegistry(this.connection);
|
|
2521
|
-
this.connection.onFrame((frame) => {
|
|
2522
|
-
this.processor.handleFrame(frame);
|
|
2523
|
-
});
|
|
2524
|
-
this._views = createTypedViews(this.stack, this.storage, this.subscriptionRegistry);
|
|
2525
|
-
this._instructions = this.buildInstructions();
|
|
2526
|
-
}
|
|
2527
|
-
buildInstructions() {
|
|
2528
|
-
const instructions = {};
|
|
2529
|
-
if (this.stack.instructions) {
|
|
2530
|
-
for (const [name, handler] of Object.entries(this.stack.instructions)) {
|
|
2531
|
-
instructions[name] = (args, options) => {
|
|
2532
|
-
return executeInstruction(handler, args, options);
|
|
2533
|
-
};
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
return instructions;
|
|
2537
|
-
}
|
|
2538
|
-
static async connect(stack, options) {
|
|
2539
|
-
const url = options?.url ?? stack.url;
|
|
2540
|
-
if (!url) {
|
|
2541
|
-
throw new AreteError('URL is required (provide url option or define url in stack)', 'INVALID_CONFIG');
|
|
2542
|
-
}
|
|
2543
|
-
const internalOptions = {
|
|
2544
|
-
stack,
|
|
2545
|
-
storage: options?.storage,
|
|
2546
|
-
maxEntriesPerView: options?.maxEntriesPerView,
|
|
2547
|
-
flushIntervalMs: options?.flushIntervalMs,
|
|
2548
|
-
autoReconnect: options?.autoReconnect,
|
|
2549
|
-
reconnectIntervals: options?.reconnectIntervals,
|
|
2550
|
-
maxReconnectAttempts: options?.maxReconnectAttempts,
|
|
2551
|
-
validateFrames: options?.validateFrames,
|
|
2552
|
-
auth: options?.auth,
|
|
2553
|
-
};
|
|
2554
|
-
const client = new Arete(url, internalOptions);
|
|
2555
|
-
if (options?.autoReconnect !== false) {
|
|
2556
|
-
await client.connection.connect();
|
|
3176
|
+
const STACK_RUNTIME_EXTENSIONS = '__areteStackRuntimeExtensions';
|
|
3177
|
+
const PROGRAM_OPERATION_EXTENSIONS = '__areteProgramOperationExtensions';
|
|
3178
|
+
function defineProgramExtensions() {
|
|
3179
|
+
return (extensions) => extensions;
|
|
3180
|
+
}
|
|
3181
|
+
function mergeNamespace(base, extension) {
|
|
3182
|
+
if (base && typeof base === 'object' && !Array.isArray(base)
|
|
3183
|
+
&& extension && typeof extension === 'object' && !Array.isArray(extension)) {
|
|
3184
|
+
const merged = { ...base };
|
|
3185
|
+
for (const [key, value] of Object.entries(extension)) {
|
|
3186
|
+
merged[key] = key in merged ? mergeNamespace(merged[key], value) : value;
|
|
2557
3187
|
}
|
|
2558
|
-
return
|
|
3188
|
+
return merged;
|
|
2559
3189
|
}
|
|
2560
|
-
|
|
2561
|
-
|
|
3190
|
+
return extension;
|
|
3191
|
+
}
|
|
3192
|
+
function mergeOperations(base, extension) {
|
|
3193
|
+
return {
|
|
3194
|
+
instructions: mergeNamespace(base?.instructions, extension?.instructions),
|
|
3195
|
+
transactions: mergeNamespace(base?.transactions, extension?.transactions),
|
|
3196
|
+
flows: mergeNamespace(base?.flows, extension?.flows),
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
function extendProgram(program, extensions) {
|
|
3200
|
+
const base = program;
|
|
3201
|
+
const extended = { ...program };
|
|
3202
|
+
for (const key of ['pdas', 'accounts', 'queries', 'addresses', 'constants', 'defaults', 'math']) {
|
|
3203
|
+
const extensionValue = extensions[key];
|
|
3204
|
+
if (extensionValue !== undefined) {
|
|
3205
|
+
extended[key] = mergeNamespace(base[key], extensionValue);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
if (extensions.raw !== undefined) {
|
|
3209
|
+
extended.rawInstructions = mergeNamespace(base.rawInstructions, extensions.raw);
|
|
3210
|
+
}
|
|
3211
|
+
const baseFactory = base[PROGRAM_OPERATION_EXTENSIONS]?.createOperations;
|
|
3212
|
+
const extensionFactory = extensions.createOperations;
|
|
3213
|
+
if (baseFactory || extensionFactory) {
|
|
3214
|
+
Object.defineProperty(extended, PROGRAM_OPERATION_EXTENSIONS, {
|
|
3215
|
+
value: {
|
|
3216
|
+
createOperations(context) {
|
|
3217
|
+
const baseOperations = baseFactory?.(context);
|
|
3218
|
+
if (baseOperations) {
|
|
3219
|
+
const connectedProgram = context.program;
|
|
3220
|
+
connectedProgram.instructions = mergeNamespace(connectedProgram.instructions, baseOperations.instructions);
|
|
3221
|
+
connectedProgram.transactions = mergeNamespace(connectedProgram.transactions, baseOperations.transactions);
|
|
3222
|
+
connectedProgram.flows = mergeNamespace(connectedProgram.flows, baseOperations.flows);
|
|
3223
|
+
}
|
|
3224
|
+
return mergeOperations(baseOperations, extensionFactory?.(context));
|
|
3225
|
+
},
|
|
3226
|
+
},
|
|
3227
|
+
enumerable: false,
|
|
3228
|
+
configurable: false,
|
|
3229
|
+
writable: false,
|
|
3230
|
+
});
|
|
2562
3231
|
}
|
|
2563
|
-
|
|
2564
|
-
|
|
3232
|
+
return extended;
|
|
3233
|
+
}
|
|
3234
|
+
function extendPrograms(programs, extensions) {
|
|
3235
|
+
const merged = { ...programs };
|
|
3236
|
+
for (const [name, program] of Object.entries(programs)) {
|
|
3237
|
+
const extension = extensions[name];
|
|
3238
|
+
merged[name] = extension
|
|
3239
|
+
? extendProgram(program, extension)
|
|
3240
|
+
: program;
|
|
3241
|
+
}
|
|
3242
|
+
return merged;
|
|
3243
|
+
}
|
|
3244
|
+
function getProgramRuntimeExtensions(program) {
|
|
3245
|
+
return program[PROGRAM_OPERATION_EXTENSIONS];
|
|
3246
|
+
}
|
|
3247
|
+
function defineStackExtensions() {
|
|
3248
|
+
return (extensions) => extensions;
|
|
3249
|
+
}
|
|
3250
|
+
function extendStack(stack, extensions) {
|
|
3251
|
+
const base = stack;
|
|
3252
|
+
const extended = { ...stack };
|
|
3253
|
+
for (const key of ['addresses', 'constants', 'defaults', 'math']) {
|
|
3254
|
+
if (extensions[key] !== undefined) {
|
|
3255
|
+
extended[key] = mergeNamespace(base[key], extensions[key]);
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
const baseRuntime = base[STACK_RUNTIME_EXTENSIONS];
|
|
3259
|
+
if (baseRuntime || extensions.createRead || extensions.createFlows) {
|
|
3260
|
+
Object.defineProperty(extended, STACK_RUNTIME_EXTENSIONS, {
|
|
3261
|
+
value: {
|
|
3262
|
+
createRead: baseRuntime?.createRead && extensions.createRead
|
|
3263
|
+
? (client) => mergeNamespace(baseRuntime.createRead(client), extensions.createRead(client))
|
|
3264
|
+
: extensions.createRead ?? baseRuntime?.createRead,
|
|
3265
|
+
createFlows: baseRuntime?.createFlows && extensions.createFlows
|
|
3266
|
+
? (client) => mergeNamespace(baseRuntime.createFlows(client), extensions.createFlows(client))
|
|
3267
|
+
: extensions.createFlows ?? baseRuntime?.createFlows,
|
|
3268
|
+
},
|
|
3269
|
+
enumerable: false,
|
|
3270
|
+
configurable: false,
|
|
3271
|
+
writable: false,
|
|
3272
|
+
});
|
|
2565
3273
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
3274
|
+
return extended;
|
|
3275
|
+
}
|
|
3276
|
+
function getStackRuntimeExtensions(stack) {
|
|
3277
|
+
return stack[STACK_RUNTIME_EXTENSIONS];
|
|
3278
|
+
}
|
|
3279
|
+
function defineClientField(target, key, value) {
|
|
3280
|
+
if (value === undefined || key in target) {
|
|
3281
|
+
return;
|
|
3282
|
+
}
|
|
3283
|
+
Object.defineProperty(target, key, {
|
|
3284
|
+
value,
|
|
3285
|
+
enumerable: true,
|
|
3286
|
+
configurable: true,
|
|
3287
|
+
writable: false,
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
3290
|
+
function applyConnectedStackExtensions(client, stack) {
|
|
3291
|
+
const extendedStack = stack;
|
|
3292
|
+
defineClientField(client, 'addresses', extendedStack.addresses);
|
|
3293
|
+
defineClientField(client, 'constants', extendedStack.constants);
|
|
3294
|
+
defineClientField(client, 'defaults', extendedStack.defaults);
|
|
3295
|
+
defineClientField(client, 'math', extendedStack.math);
|
|
3296
|
+
const runtime = getStackRuntimeExtensions(stack);
|
|
3297
|
+
defineClientField(client, 'flows', runtime?.createFlows?.(client));
|
|
3298
|
+
defineClientField(client, 'read', runtime?.createRead?.(client));
|
|
3299
|
+
return client;
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
function isPreparedInstruction(instruction) {
|
|
3303
|
+
return 'kind' in instruction
|
|
3304
|
+
&& instruction.kind === 'instruction'
|
|
3305
|
+
&& 'instruction' in instruction;
|
|
3306
|
+
}
|
|
3307
|
+
function nonEmpty(values, label) {
|
|
3308
|
+
if (values.length === 0) {
|
|
3309
|
+
throw new Error(`${label} must contain at least one item`);
|
|
2568
3310
|
}
|
|
2569
|
-
|
|
2570
|
-
|
|
3311
|
+
return [...values];
|
|
3312
|
+
}
|
|
3313
|
+
function dedupe(values) {
|
|
3314
|
+
return [...new Set(values)];
|
|
3315
|
+
}
|
|
3316
|
+
function inferSignerAddresses(instructions) {
|
|
3317
|
+
return dedupe(instructions.flatMap((instruction) => instruction.keys.filter((key) => key.isSigner).map((key) => key.pubkey)));
|
|
3318
|
+
}
|
|
3319
|
+
function createPreparedTransactionBody(input) {
|
|
3320
|
+
const instructions = nonEmpty(input.instructions, `Transaction '${input.name}'`);
|
|
3321
|
+
return {
|
|
3322
|
+
name: input.name,
|
|
3323
|
+
instructions,
|
|
3324
|
+
requiredSignerAddresses: dedupe(input.requiredSignerAddresses ?? inferSignerAddresses(instructions)),
|
|
3325
|
+
errors: [...(input.errors ?? [])],
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
function createPlan(name, artifacts, transactions) {
|
|
3329
|
+
return {
|
|
3330
|
+
name,
|
|
3331
|
+
artifacts,
|
|
3332
|
+
transactions: nonEmpty(transactions, `Flow '${name}'`),
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
function createPreparedInstruction(input) {
|
|
3336
|
+
const transaction = createPreparedTransactionBody({
|
|
3337
|
+
name: input.name,
|
|
3338
|
+
instructions: [input.instruction],
|
|
3339
|
+
requiredSignerAddresses: input.requiredSignerAddresses,
|
|
3340
|
+
errors: input.errors,
|
|
3341
|
+
});
|
|
3342
|
+
return {
|
|
3343
|
+
kind: 'instruction',
|
|
3344
|
+
name: input.name,
|
|
3345
|
+
instruction: input.instruction,
|
|
3346
|
+
transaction,
|
|
3347
|
+
plan: createPlan(input.name, input.artifacts, [transaction]),
|
|
3348
|
+
artifacts: input.artifacts,
|
|
3349
|
+
};
|
|
3350
|
+
}
|
|
3351
|
+
function createPreparedTransaction(input) {
|
|
3352
|
+
const hasInstructions = input.instructions !== undefined;
|
|
3353
|
+
const hasOperations = input.operations !== undefined;
|
|
3354
|
+
if (hasInstructions === hasOperations) {
|
|
3355
|
+
throw new Error(`Transaction '${input.name}' must provide exactly one of instructions or operations`);
|
|
3356
|
+
}
|
|
3357
|
+
const transactionParts = hasOperations
|
|
3358
|
+
? input.operations.map((operation) => operation.transaction)
|
|
3359
|
+
: input.instructions.map((instruction) => isPreparedInstruction(instruction)
|
|
3360
|
+
? instruction.transaction
|
|
3361
|
+
: createPreparedTransactionBody({
|
|
3362
|
+
name: input.name,
|
|
3363
|
+
instructions: [instruction],
|
|
3364
|
+
}));
|
|
3365
|
+
const transaction = createPreparedTransactionBody({
|
|
3366
|
+
name: input.name,
|
|
3367
|
+
instructions: transactionParts.flatMap((part) => part.instructions),
|
|
3368
|
+
requiredSignerAddresses: input.requiredSignerAddresses
|
|
3369
|
+
?? transactionParts.flatMap((part) => part.requiredSignerAddresses),
|
|
3370
|
+
errors: input.errors ?? transactionParts.flatMap((part) => part.errors),
|
|
3371
|
+
});
|
|
3372
|
+
return {
|
|
3373
|
+
kind: 'transaction',
|
|
3374
|
+
name: input.name,
|
|
3375
|
+
transaction,
|
|
3376
|
+
plan: createPlan(input.name, input.artifacts, [transaction]),
|
|
3377
|
+
artifacts: input.artifacts,
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
function createPreparedFlow(input) {
|
|
3381
|
+
const transactions = input.transactions.map((transaction) => createPreparedTransactionBody(transaction));
|
|
3382
|
+
return {
|
|
3383
|
+
kind: 'flow',
|
|
3384
|
+
name: input.name,
|
|
3385
|
+
plan: createPlan(input.name, input.artifacts, transactions),
|
|
3386
|
+
artifacts: input.artifacts,
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
3389
|
+
function prependTransactionInstructions(transaction, instructions) {
|
|
3390
|
+
return createPreparedTransactionBody({
|
|
3391
|
+
...transaction,
|
|
3392
|
+
instructions: [...instructions, ...transaction.instructions],
|
|
3393
|
+
requiredSignerAddresses: [
|
|
3394
|
+
...inferSignerAddresses(instructions),
|
|
3395
|
+
...transaction.requiredSignerAddresses,
|
|
3396
|
+
],
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
3399
|
+
function appendTransactionInstructions(transaction, instructions) {
|
|
3400
|
+
return createPreparedTransactionBody({
|
|
3401
|
+
...transaction,
|
|
3402
|
+
instructions: [...transaction.instructions, ...instructions],
|
|
3403
|
+
requiredSignerAddresses: [
|
|
3404
|
+
...transaction.requiredSignerAddresses,
|
|
3405
|
+
...inferSignerAddresses(instructions),
|
|
3406
|
+
],
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3409
|
+
function appendFlowTransactions(flow, transactions) {
|
|
3410
|
+
return createPreparedFlow({
|
|
3411
|
+
name: flow.name,
|
|
3412
|
+
artifacts: flow.artifacts,
|
|
3413
|
+
transactions: [...flow.plan.transactions, ...transactions],
|
|
3414
|
+
});
|
|
3415
|
+
}
|
|
3416
|
+
function prependFlowTransactionInstructions(flow, transactionIndex, instructions) {
|
|
3417
|
+
const transaction = flow.plan.transactions[transactionIndex];
|
|
3418
|
+
if (!transaction) {
|
|
3419
|
+
throw new Error(`Flow '${flow.name}' has no transaction at index ${transactionIndex}`);
|
|
3420
|
+
}
|
|
3421
|
+
const transactions = [...flow.plan.transactions];
|
|
3422
|
+
transactions[transactionIndex] = prependTransactionInstructions(transaction, instructions);
|
|
3423
|
+
return createPreparedFlow({
|
|
3424
|
+
name: flow.name,
|
|
3425
|
+
artifacts: flow.artifacts,
|
|
3426
|
+
transactions,
|
|
3427
|
+
});
|
|
3428
|
+
}
|
|
3429
|
+
function inferSignerAddress(value) {
|
|
3430
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
3431
|
+
return value;
|
|
2571
3432
|
}
|
|
2572
|
-
|
|
2573
|
-
return
|
|
3433
|
+
if (!value || typeof value !== 'object') {
|
|
3434
|
+
return null;
|
|
2574
3435
|
}
|
|
2575
|
-
|
|
2576
|
-
|
|
3436
|
+
const candidate = value;
|
|
3437
|
+
if (typeof candidate.address === 'string' && candidate.address.length > 0) {
|
|
3438
|
+
return candidate.address;
|
|
2577
3439
|
}
|
|
2578
|
-
|
|
2579
|
-
return
|
|
3440
|
+
if (typeof candidate.publicKey === 'string' && candidate.publicKey.length > 0) {
|
|
3441
|
+
return candidate.publicKey;
|
|
2580
3442
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
3443
|
+
const publicKey = candidate.publicKey;
|
|
3444
|
+
return typeof publicKey?.toBase58 === 'function' ? publicKey.toBase58() : null;
|
|
3445
|
+
}
|
|
3446
|
+
function validateTransactionSigners(transaction, host, options) {
|
|
3447
|
+
const signerAddresses = (options.signers ?? []).map(inferSignerAddress);
|
|
3448
|
+
const available = new Set(options.availableSignerAddresses ?? []);
|
|
3449
|
+
for (const address of options.signerRegistry?.addresses() ?? []) {
|
|
3450
|
+
available.add(address);
|
|
2583
3451
|
}
|
|
2584
|
-
|
|
2585
|
-
|
|
3452
|
+
const wallet = options.wallet ?? host.wallet;
|
|
3453
|
+
for (const address of wallet?.signerAddresses ?? []) {
|
|
3454
|
+
available.add(address);
|
|
2586
3455
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
3456
|
+
const walletAddress = wallet?.publicKey ?? host.publicKey;
|
|
3457
|
+
if (walletAddress) {
|
|
3458
|
+
available.add(walletAddress);
|
|
2590
3459
|
}
|
|
2591
|
-
|
|
2592
|
-
|
|
3460
|
+
for (const address of signerAddresses) {
|
|
3461
|
+
if (address) {
|
|
3462
|
+
available.add(address);
|
|
3463
|
+
}
|
|
2593
3464
|
}
|
|
2594
|
-
|
|
2595
|
-
|
|
3465
|
+
const missing = transaction.requiredSignerAddresses.filter((address) => !available.has(address));
|
|
3466
|
+
if (missing.length > 0) {
|
|
3467
|
+
throw new Error(`Missing signer(s) for ${transaction.name}: ${missing.join(', ')}`);
|
|
2596
3468
|
}
|
|
2597
|
-
|
|
2598
|
-
|
|
3469
|
+
}
|
|
3470
|
+
class OperationExecutionError extends Error {
|
|
3471
|
+
constructor(input) {
|
|
3472
|
+
super(`Operation '${input.operation.name}' failed at transaction ${input.failedTransactionIndex + 1} (${input.failedTransaction.name})`);
|
|
3473
|
+
this.name = 'OperationExecutionError';
|
|
3474
|
+
this.operation = input.operation;
|
|
3475
|
+
this.failedTransaction = input.failedTransaction;
|
|
3476
|
+
this.failedTransactionIndex = input.failedTransactionIndex;
|
|
3477
|
+
this.completedReceipts = [...input.completedReceipts];
|
|
3478
|
+
this.cause = input.cause;
|
|
2599
3479
|
}
|
|
2600
|
-
|
|
2601
|
-
|
|
3480
|
+
}
|
|
3481
|
+
async function executePreparedOperation(host, operation, options = {}) {
|
|
3482
|
+
const receipts = [];
|
|
3483
|
+
const signers = [
|
|
3484
|
+
...new Set([
|
|
3485
|
+
...(options.signerRegistry?.values() ?? []),
|
|
3486
|
+
...(options.signers ?? []),
|
|
3487
|
+
]),
|
|
3488
|
+
];
|
|
3489
|
+
for (const [transactionIndex, transaction] of operation.plan.transactions.entries()) {
|
|
3490
|
+
try {
|
|
3491
|
+
validateTransactionSigners(transaction, host, options);
|
|
3492
|
+
await options.onTransactionStart?.({ operation, transaction, transactionIndex });
|
|
3493
|
+
const result = await host.transaction(transaction.instructions, {
|
|
3494
|
+
wallet: options.wallet,
|
|
3495
|
+
send: options.send,
|
|
3496
|
+
errors: [...transaction.errors],
|
|
3497
|
+
signers: signers.length > 0 ? signers : undefined,
|
|
3498
|
+
});
|
|
3499
|
+
const receipt = {
|
|
3500
|
+
transactionIndex,
|
|
3501
|
+
transactionName: transaction.name,
|
|
3502
|
+
signature: result.signature,
|
|
3503
|
+
slot: result.slot,
|
|
3504
|
+
};
|
|
3505
|
+
receipts.push(receipt);
|
|
3506
|
+
await options.onTransactionSuccess?.({
|
|
3507
|
+
operation,
|
|
3508
|
+
transaction,
|
|
3509
|
+
transactionIndex,
|
|
3510
|
+
receipt,
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
catch (cause) {
|
|
3514
|
+
throw new OperationExecutionError({
|
|
3515
|
+
operation,
|
|
3516
|
+
failedTransaction: transaction,
|
|
3517
|
+
failedTransactionIndex: transactionIndex,
|
|
3518
|
+
completedReceipts: receipts,
|
|
3519
|
+
cause,
|
|
3520
|
+
});
|
|
3521
|
+
}
|
|
2602
3522
|
}
|
|
2603
|
-
|
|
2604
|
-
return
|
|
3523
|
+
if (operation.kind === 'flow') {
|
|
3524
|
+
return {
|
|
3525
|
+
kind: 'flow',
|
|
3526
|
+
operationName: operation.name,
|
|
3527
|
+
artifacts: operation.artifacts,
|
|
3528
|
+
signatures: nonEmpty(receipts.map((receipt) => receipt.signature), `Operation '${operation.name}' signatures`),
|
|
3529
|
+
transactions: nonEmpty(receipts, `Operation '${operation.name}' receipts`),
|
|
3530
|
+
};
|
|
2605
3531
|
}
|
|
3532
|
+
return {
|
|
3533
|
+
kind: operation.kind,
|
|
3534
|
+
operationName: operation.name,
|
|
3535
|
+
artifacts: operation.artifacts,
|
|
3536
|
+
signatures: [receipts[0].signature],
|
|
3537
|
+
transaction: receipts[0],
|
|
3538
|
+
};
|
|
2606
3539
|
}
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
for (const segment of path) {
|
|
2611
|
-
if (current === null || current === undefined)
|
|
2612
|
-
return undefined;
|
|
2613
|
-
if (typeof current !== 'object')
|
|
2614
|
-
return undefined;
|
|
2615
|
-
current = current[segment];
|
|
3540
|
+
function convertToJsonValue(value, ancestors) {
|
|
3541
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') {
|
|
3542
|
+
return value;
|
|
2616
3543
|
}
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
function compareSortValues(a, b) {
|
|
2620
|
-
if (a === b)
|
|
2621
|
-
return 0;
|
|
2622
|
-
if (a === undefined || a === null)
|
|
2623
|
-
return -1;
|
|
2624
|
-
if (b === undefined || b === null)
|
|
2625
|
-
return 1;
|
|
2626
|
-
if (typeof a === 'number' && typeof b === 'number') {
|
|
2627
|
-
return a - b;
|
|
3544
|
+
if (typeof value === 'number') {
|
|
3545
|
+
return Number.isFinite(value) ? value : null;
|
|
2628
3546
|
}
|
|
2629
|
-
if (typeof
|
|
2630
|
-
return
|
|
3547
|
+
if (typeof value === 'bigint') {
|
|
3548
|
+
return value.toString();
|
|
2631
3549
|
}
|
|
2632
|
-
if (
|
|
2633
|
-
|
|
3550
|
+
if (value === undefined
|
|
3551
|
+
|| typeof value === 'function'
|
|
3552
|
+
|| typeof value === 'symbol') {
|
|
3553
|
+
return undefined;
|
|
2634
3554
|
}
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
class ViewData {
|
|
2638
|
-
constructor(sortConfig) {
|
|
2639
|
-
this.entities = new Map();
|
|
2640
|
-
this.accessOrder = [];
|
|
2641
|
-
this.sortedKeys = [];
|
|
2642
|
-
this.sortConfig = sortConfig;
|
|
3555
|
+
if (typeof value !== 'object') {
|
|
3556
|
+
return String(value);
|
|
2643
3557
|
}
|
|
2644
|
-
|
|
2645
|
-
|
|
3558
|
+
if (ancestors.has(value)) {
|
|
3559
|
+
throw new Error('Cannot convert a circular value to JSON');
|
|
2646
3560
|
}
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
this.updateSortedPosition(key, value, isNew);
|
|
3561
|
+
ancestors.add(value);
|
|
3562
|
+
try {
|
|
3563
|
+
if (ArrayBuffer.isView(value)) {
|
|
3564
|
+
return Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
|
|
2652
3565
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
this.accessOrder.push(key);
|
|
2656
|
-
}
|
|
2657
|
-
else {
|
|
2658
|
-
this.touch(key);
|
|
2659
|
-
}
|
|
3566
|
+
if (value instanceof ArrayBuffer) {
|
|
3567
|
+
return Array.from(new Uint8Array(value));
|
|
2660
3568
|
}
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
if (!isNew) {
|
|
2664
|
-
const existingIdx = this.sortedKeys.indexOf(key);
|
|
2665
|
-
if (existingIdx !== -1) {
|
|
2666
|
-
this.sortedKeys.splice(existingIdx, 1);
|
|
2667
|
-
}
|
|
3569
|
+
if (Array.isArray(value)) {
|
|
3570
|
+
return value.map((entry) => convertToJsonValue(entry, ancestors) ?? null);
|
|
2668
3571
|
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
let insertIdx = this.binarySearchInsertPosition(sortValue, key, isDesc);
|
|
2672
|
-
this.sortedKeys.splice(insertIdx, 0, key);
|
|
2673
|
-
}
|
|
2674
|
-
binarySearchInsertPosition(sortValue, key, isDesc) {
|
|
2675
|
-
let low = 0;
|
|
2676
|
-
let high = this.sortedKeys.length;
|
|
2677
|
-
while (low < high) {
|
|
2678
|
-
const mid = Math.floor((low + high) / 2);
|
|
2679
|
-
const midKey = this.sortedKeys[mid];
|
|
2680
|
-
if (midKey === undefined)
|
|
2681
|
-
break;
|
|
2682
|
-
const midEntity = this.entities.get(midKey);
|
|
2683
|
-
const midValue = getNestedValue(midEntity, this.sortConfig.field);
|
|
2684
|
-
let cmp = compareSortValues(sortValue, midValue);
|
|
2685
|
-
if (isDesc)
|
|
2686
|
-
cmp = -cmp;
|
|
2687
|
-
if (cmp === 0) {
|
|
2688
|
-
cmp = key.localeCompare(midKey);
|
|
2689
|
-
}
|
|
2690
|
-
if (cmp < 0) {
|
|
2691
|
-
high = mid;
|
|
2692
|
-
}
|
|
2693
|
-
else {
|
|
2694
|
-
low = mid + 1;
|
|
2695
|
-
}
|
|
3572
|
+
if (value instanceof Set) {
|
|
3573
|
+
return [...value].map((entry) => convertToJsonValue(entry, ancestors) ?? null);
|
|
2696
3574
|
}
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
3575
|
+
if (value instanceof Map) {
|
|
3576
|
+
const result = {};
|
|
3577
|
+
for (const [key, entry] of value) {
|
|
3578
|
+
const converted = convertToJsonValue(entry, ancestors);
|
|
3579
|
+
if (converted !== undefined) {
|
|
3580
|
+
result[String(key)] = converted;
|
|
3581
|
+
}
|
|
2704
3582
|
}
|
|
3583
|
+
return result;
|
|
2705
3584
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
3585
|
+
const toJSON = value.toJSON;
|
|
3586
|
+
if (typeof toJSON === 'function') {
|
|
3587
|
+
return convertToJsonValue(toJSON.call(value), ancestors) ?? null;
|
|
3588
|
+
}
|
|
3589
|
+
const result = {};
|
|
3590
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
3591
|
+
const converted = convertToJsonValue(entry, ancestors);
|
|
3592
|
+
if (converted !== undefined) {
|
|
3593
|
+
result[key] = converted;
|
|
2710
3594
|
}
|
|
2711
3595
|
}
|
|
2712
|
-
return
|
|
3596
|
+
return result;
|
|
2713
3597
|
}
|
|
2714
|
-
|
|
2715
|
-
|
|
3598
|
+
finally {
|
|
3599
|
+
ancestors.delete(value);
|
|
2716
3600
|
}
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
3601
|
+
}
|
|
3602
|
+
function toJsonValue(value) {
|
|
3603
|
+
return convertToJsonValue(value, new Set()) ?? null;
|
|
3604
|
+
}
|
|
3605
|
+
function describePreparedOperation(operation) {
|
|
3606
|
+
return {
|
|
3607
|
+
kind: operation.kind,
|
|
3608
|
+
name: operation.name,
|
|
3609
|
+
artifacts: toJsonValue(operation.artifacts),
|
|
3610
|
+
transactions: operation.plan.transactions.map((transaction) => ({
|
|
3611
|
+
name: transaction.name,
|
|
3612
|
+
requiredSignerAddresses: transaction.requiredSignerAddresses,
|
|
3613
|
+
errors: transaction.errors,
|
|
3614
|
+
instructions: transaction.instructions.map((instruction) => ({
|
|
3615
|
+
programId: instruction.programId,
|
|
3616
|
+
keys: instruction.keys,
|
|
3617
|
+
data: Array.from(instruction.data),
|
|
3618
|
+
})),
|
|
3619
|
+
})),
|
|
3620
|
+
};
|
|
3621
|
+
}
|
|
3622
|
+
function formatPreparedOperation(operation) {
|
|
3623
|
+
const lines = [
|
|
3624
|
+
`${operation.kind}: ${operation.name}`,
|
|
3625
|
+
`Transactions: ${operation.plan.transactions.length}`,
|
|
3626
|
+
];
|
|
3627
|
+
for (const [index, transaction] of operation.plan.transactions.entries()) {
|
|
3628
|
+
lines.push(` ${index + 1}. ${transaction.name} (${transaction.instructions.length} instruction${transaction.instructions.length === 1 ? '' : 's'})`);
|
|
3629
|
+
if (transaction.requiredSignerAddresses.length > 0) {
|
|
3630
|
+
lines.push(` Signers: ${transaction.requiredSignerAddresses.join(', ')}`);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
return lines.join('\n');
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
function mergeAttachedPrograms(stack, attachedPrograms) {
|
|
3637
|
+
const merged = { ...(attachedPrograms ?? {}) };
|
|
3638
|
+
for (const [name, definition] of Object.entries(stack.programs ?? {})) {
|
|
3639
|
+
if (name in merged) {
|
|
3640
|
+
console.warn(`Ignoring attached program '${name}' for stack '${stack.name}' because the stack already defines that key`);
|
|
2720
3641
|
}
|
|
2721
|
-
|
|
3642
|
+
merged[name] = definition;
|
|
2722
3643
|
}
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
return
|
|
3644
|
+
return merged;
|
|
3645
|
+
}
|
|
3646
|
+
function normalizeProgramAccountWireKeys(value) {
|
|
3647
|
+
if (Array.isArray(value)) {
|
|
3648
|
+
return value.map(normalizeProgramAccountWireKeys);
|
|
2728
3649
|
}
|
|
2729
|
-
|
|
2730
|
-
return
|
|
3650
|
+
if (!value || typeof value !== 'object') {
|
|
3651
|
+
return value;
|
|
2731
3652
|
}
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
3653
|
+
return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [
|
|
3654
|
+
key.replace(/[A-Z]/g, (letter, index) => `${index === 0 ? '' : '_'}${letter.toLowerCase()}`),
|
|
3655
|
+
normalizeProgramAccountWireKeys(nestedValue),
|
|
3656
|
+
]));
|
|
3657
|
+
}
|
|
3658
|
+
function parseProgramAccountValue(definition, value) {
|
|
3659
|
+
const schema = definition.schema;
|
|
3660
|
+
if (!schema) {
|
|
3661
|
+
return value;
|
|
2740
3662
|
}
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
if (oldest !== undefined) {
|
|
2745
|
-
this.entities.delete(oldest);
|
|
2746
|
-
}
|
|
2747
|
-
return oldest;
|
|
2748
|
-
}
|
|
2749
|
-
const oldest = this.accessOrder.shift();
|
|
2750
|
-
if (oldest !== undefined) {
|
|
2751
|
-
this.entities.delete(oldest);
|
|
2752
|
-
}
|
|
2753
|
-
return oldest;
|
|
3663
|
+
const parsed = schema.safeParse(value);
|
|
3664
|
+
if (parsed.success) {
|
|
3665
|
+
return parsed.data;
|
|
2754
3666
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
this.sortConfig = config;
|
|
2759
|
-
this.rebuildSortedKeys();
|
|
3667
|
+
const normalized = schema.safeParse(normalizeProgramAccountWireKeys(value));
|
|
3668
|
+
if (normalized.success) {
|
|
3669
|
+
return normalized.data;
|
|
2760
3670
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
3671
|
+
throw new Error(`Program account read '${definition.account}' failed schema validation`);
|
|
3672
|
+
}
|
|
3673
|
+
function cloneStackWithPrograms(stack, programs) {
|
|
3674
|
+
const cloned = Object.create(Object.getPrototypeOf(stack), Object.getOwnPropertyDescriptors(stack));
|
|
3675
|
+
cloned.programs = programs;
|
|
3676
|
+
return cloned;
|
|
3677
|
+
}
|
|
3678
|
+
function withPrograms(stack, attachedPrograms) {
|
|
3679
|
+
return cloneStackWithPrograms(stack, mergeAttachedPrograms(stack, attachedPrograms));
|
|
3680
|
+
}
|
|
3681
|
+
let Arete$1 = class Arete {
|
|
3682
|
+
constructor(url, httpBaseUrl, options) {
|
|
3683
|
+
this.stack = options.stack;
|
|
3684
|
+
this._wallet = options.wallet;
|
|
3685
|
+
this.executionDefaults = options.execution;
|
|
3686
|
+
this.httpBaseUrl = httpBaseUrl;
|
|
3687
|
+
this.fetchImpl = options.fetch ?? this.resolveFetchImpl();
|
|
3688
|
+
this.storage = new SortedStorageDecorator(options.storage ?? new MemoryAdapter());
|
|
3689
|
+
this.processor = new FrameProcessor(this.storage, {
|
|
3690
|
+
maxEntriesPerView: options.maxEntriesPerView,
|
|
3691
|
+
flushIntervalMs: options.flushIntervalMs,
|
|
3692
|
+
schemas: this.stack.schemas,
|
|
3693
|
+
patchSchemas: this.stack.patchSchemas,
|
|
2776
3694
|
});
|
|
2777
|
-
this.
|
|
2778
|
-
|
|
3695
|
+
this.connection = new ConnectionManager({
|
|
3696
|
+
websocketUrl: url,
|
|
3697
|
+
reconnectIntervals: options.reconnectIntervals,
|
|
3698
|
+
maxReconnectAttempts: options.maxReconnectAttempts,
|
|
3699
|
+
auth: options.auth,
|
|
3700
|
+
});
|
|
3701
|
+
this.subscriptionRegistry = new SubscriptionRegistry(this.connection);
|
|
3702
|
+
this.connection.onFrame((frame) => {
|
|
3703
|
+
this.processor.handleFrame(frame);
|
|
3704
|
+
});
|
|
3705
|
+
this._views = createTypedViews(this.stack, this.storage, this.subscriptionRegistry);
|
|
3706
|
+
this._queries = this.buildQueries();
|
|
3707
|
+
this._chain = createChainClient(this.httpBaseUrl, this.authenticatedFetch.bind(this));
|
|
3708
|
+
this._programs = this.buildPrograms();
|
|
2779
3709
|
}
|
|
2780
|
-
|
|
2781
|
-
|
|
3710
|
+
resolveFetchImpl() {
|
|
3711
|
+
if (typeof globalThis.fetch !== 'function') {
|
|
3712
|
+
throw new AreteError('A fetch implementation is required for HTTP point reads (provide ConnectOptions.fetch or use an environment with global fetch)', 'INVALID_CONFIG');
|
|
3713
|
+
}
|
|
3714
|
+
return globalThis.fetch.bind(globalThis);
|
|
2782
3715
|
}
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
return source;
|
|
3716
|
+
buildQueries() {
|
|
3717
|
+
const queries = {};
|
|
3718
|
+
for (const [name, definition] of Object.entries(this.stack.queries ?? {})) {
|
|
3719
|
+
queries[name] = this.createQueryExecutor(definition);
|
|
3720
|
+
}
|
|
3721
|
+
return queries;
|
|
2790
3722
|
}
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
const
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
if (appendPaths.includes(fieldPath)) {
|
|
2798
|
-
result[key] = [...targetValue, ...sourceValue];
|
|
3723
|
+
buildPrograms() {
|
|
3724
|
+
const bases = {};
|
|
3725
|
+
for (const [name, definition] of Object.entries(this.stack.programs ?? {})) {
|
|
3726
|
+
const instructions = {};
|
|
3727
|
+
for (const [instructionName, handler] of Object.entries(definition.rawInstructions ?? {})) {
|
|
3728
|
+
instructions[instructionName] = this.createTypedInstruction(handler);
|
|
2799
3729
|
}
|
|
2800
|
-
|
|
2801
|
-
|
|
3730
|
+
const accounts = {};
|
|
3731
|
+
for (const [accountName, accountDefinition] of Object.entries(definition.accounts ?? {})) {
|
|
3732
|
+
accounts[accountName] = this.createAccountReader(accountDefinition);
|
|
2802
3733
|
}
|
|
3734
|
+
const queries = {};
|
|
3735
|
+
for (const [queryName, queryDefinition] of Object.entries(definition.queries ?? {})) {
|
|
3736
|
+
queries[queryName] = this.createQueryExecutor(queryDefinition);
|
|
3737
|
+
}
|
|
3738
|
+
bases[name] = {
|
|
3739
|
+
name: definition.name,
|
|
3740
|
+
programId: definition.programId,
|
|
3741
|
+
schemas: definition.schemas,
|
|
3742
|
+
pdas: definition.pdas ?? {},
|
|
3743
|
+
accounts,
|
|
3744
|
+
queries,
|
|
3745
|
+
raw: instructions,
|
|
3746
|
+
addresses: definition.addresses ?? {},
|
|
3747
|
+
constants: definition.constants ?? {},
|
|
3748
|
+
defaults: definition.defaults ?? {},
|
|
3749
|
+
math: definition.math ?? {},
|
|
3750
|
+
};
|
|
2803
3751
|
}
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
}
|
|
2807
|
-
|
|
2808
|
-
|
|
3752
|
+
const programs = {};
|
|
3753
|
+
const client = this;
|
|
3754
|
+
for (const [name, definition] of Object.entries(this.stack.programs ?? {})) {
|
|
3755
|
+
const base = bases[name];
|
|
3756
|
+
const connectedProgram = {
|
|
3757
|
+
...base,
|
|
3758
|
+
instructions: {},
|
|
3759
|
+
transactions: {},
|
|
3760
|
+
flows: {},
|
|
3761
|
+
};
|
|
3762
|
+
const runtime = getProgramRuntimeExtensions(definition);
|
|
3763
|
+
const operations = runtime?.createOperations({
|
|
3764
|
+
chain: this._chain,
|
|
3765
|
+
get wallet() {
|
|
3766
|
+
return client._wallet;
|
|
3767
|
+
},
|
|
3768
|
+
program: connectedProgram,
|
|
3769
|
+
});
|
|
3770
|
+
connectedProgram.instructions = operations?.instructions ?? {};
|
|
3771
|
+
connectedProgram.transactions = operations?.transactions ?? {};
|
|
3772
|
+
connectedProgram.flows = operations?.flows ?? {};
|
|
3773
|
+
programs[name] = connectedProgram;
|
|
2809
3774
|
}
|
|
3775
|
+
return programs;
|
|
2810
3776
|
}
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
this.views = new Map();
|
|
2816
|
-
this.viewConfigs = new Map();
|
|
2817
|
-
this.updateCallbacks = new Set();
|
|
2818
|
-
this.richUpdateCallbacks = new Set();
|
|
2819
|
-
this.maxEntriesPerView = config.maxEntriesPerView === undefined
|
|
2820
|
-
? DEFAULT_MAX_ENTRIES_PER_VIEW
|
|
2821
|
-
: config.maxEntriesPerView;
|
|
3777
|
+
createTypedInstruction(handler) {
|
|
3778
|
+
return {
|
|
3779
|
+
build: (params, options) => buildInstruction(handler, params, this.withWallet(options)),
|
|
3780
|
+
};
|
|
2822
3781
|
}
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
3782
|
+
createAccountReader(definition) {
|
|
3783
|
+
return {
|
|
3784
|
+
fetch: async (address) => {
|
|
3785
|
+
const result = await this.readJson(`${definition.path}/${encodeURIComponent(address)}`);
|
|
3786
|
+
return result === null ? null : parseProgramAccountValue(definition, result);
|
|
3787
|
+
},
|
|
3788
|
+
fetchMany: async (addresses) => {
|
|
3789
|
+
const result = await this.readJson(definition.path, {
|
|
3790
|
+
method: 'POST',
|
|
3791
|
+
body: { addresses },
|
|
3792
|
+
});
|
|
3793
|
+
return result.map((value) => value === null ? null : parseProgramAccountValue(definition, value));
|
|
3794
|
+
},
|
|
3795
|
+
exists: async (address) => {
|
|
3796
|
+
const result = await this.readJson(`${definition.path}/${encodeURIComponent(address)}/exists`);
|
|
3797
|
+
return result.exists;
|
|
3798
|
+
},
|
|
3799
|
+
};
|
|
2829
3800
|
}
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
this.
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
3801
|
+
createQueryExecutor(definition) {
|
|
3802
|
+
return async (params) => {
|
|
3803
|
+
const result = await this.readJson(definition.path, {
|
|
3804
|
+
method: definition.method ?? 'POST',
|
|
3805
|
+
body: params,
|
|
3806
|
+
});
|
|
3807
|
+
if (!definition.schema) {
|
|
3808
|
+
return result;
|
|
3809
|
+
}
|
|
3810
|
+
const parsed = definition.schema.safeParse(result);
|
|
3811
|
+
if (!parsed.success) {
|
|
3812
|
+
throw new Error(`Query '${definition.name}' failed schema validation`);
|
|
3813
|
+
}
|
|
3814
|
+
return parsed.data;
|
|
3815
|
+
};
|
|
2840
3816
|
}
|
|
2841
|
-
|
|
2842
|
-
const
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
}
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
3817
|
+
async readJson(path, options) {
|
|
3818
|
+
const response = await this.authenticatedFetch(this.resolveReadUrl(path), {
|
|
3819
|
+
method: options?.method ?? 'GET',
|
|
3820
|
+
headers: options?.body === undefined ? undefined : { 'content-type': 'application/json' },
|
|
3821
|
+
body: options?.body === undefined ? undefined : JSON.stringify(options.body),
|
|
3822
|
+
});
|
|
3823
|
+
return parseReadResponse(response, path);
|
|
3824
|
+
}
|
|
3825
|
+
async authenticatedFetch(input, init) {
|
|
3826
|
+
const attempt = async (forceRefresh = false) => {
|
|
3827
|
+
const token = await this.connection.getHttpAuthToken(forceRefresh);
|
|
3828
|
+
const headers = new Headers(init?.headers ?? undefined);
|
|
3829
|
+
if (token) {
|
|
3830
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
3831
|
+
}
|
|
3832
|
+
return this.fetchImpl(input, {
|
|
3833
|
+
...init,
|
|
3834
|
+
headers,
|
|
3835
|
+
});
|
|
3836
|
+
};
|
|
3837
|
+
let response = await attempt(false);
|
|
3838
|
+
if (!response.ok) {
|
|
3839
|
+
const wireErrorCode = response.headers.get('X-Error-Code');
|
|
3840
|
+
const errorCode = wireErrorCode ? parseErrorCode(wireErrorCode) : undefined;
|
|
3841
|
+
if (errorCode && shouldRefreshToken(errorCode)) {
|
|
3842
|
+
this.connection.clearHttpAuthToken();
|
|
3843
|
+
response = await attempt(true);
|
|
3844
|
+
}
|
|
2851
3845
|
}
|
|
3846
|
+
return response;
|
|
2852
3847
|
}
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
3848
|
+
resolveReadUrl(path) {
|
|
3849
|
+
return `${this.httpBaseUrl.replace(/\/$/, '')}${path.startsWith('/') ? path : `/${path}`}`;
|
|
3850
|
+
}
|
|
3851
|
+
/** Merge the client's default wallet into call options (call options win). */
|
|
3852
|
+
withWallet(options) {
|
|
3853
|
+
const merged = { ...(options ?? {}) };
|
|
3854
|
+
if (!merged.wallet && this._wallet) {
|
|
3855
|
+
merged.wallet = this._wallet;
|
|
2860
3856
|
}
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
3857
|
+
return merged;
|
|
3858
|
+
}
|
|
3859
|
+
static async connect(stack, options) {
|
|
3860
|
+
const requestedUrl = options?.url ?? stack.endpoints.ws;
|
|
3861
|
+
const autoReconnect = options?.autoReconnect !== false;
|
|
3862
|
+
const httpOnly = options?.transport === 'http' || (!requestedUrl && !autoReconnect);
|
|
3863
|
+
const url = httpOnly ? null : requestedUrl;
|
|
3864
|
+
const httpUrl = options?.httpUrl
|
|
3865
|
+
?? stack.endpoints.http
|
|
3866
|
+
?? (requestedUrl ? deriveHttpEndpoint(requestedUrl) : undefined);
|
|
3867
|
+
if (!httpOnly && !url) {
|
|
3868
|
+
throw new AreteError('WebSocket URL is required (provide url option or define endpoints.ws in stack)', 'INVALID_CONFIG');
|
|
3869
|
+
}
|
|
3870
|
+
if (!httpUrl) {
|
|
3871
|
+
throw new AreteError('HTTP endpoint is required for transport: "http" (provide httpUrl option or define endpoints.http in stack)', 'INVALID_CONFIG');
|
|
3872
|
+
}
|
|
3873
|
+
const attachedPrograms = options?.programs;
|
|
3874
|
+
const effectiveStack = withPrograms(stack, attachedPrograms);
|
|
3875
|
+
const internalOptions = {
|
|
3876
|
+
stack: effectiveStack,
|
|
3877
|
+
httpUrl,
|
|
3878
|
+
storage: options?.storage,
|
|
3879
|
+
maxEntriesPerView: options?.maxEntriesPerView,
|
|
3880
|
+
flushIntervalMs: options?.flushIntervalMs,
|
|
3881
|
+
autoReconnect: options?.autoReconnect,
|
|
3882
|
+
reconnectIntervals: options?.reconnectIntervals,
|
|
3883
|
+
maxReconnectAttempts: options?.maxReconnectAttempts,
|
|
3884
|
+
validateFrames: options?.validateFrames,
|
|
3885
|
+
auth: options?.auth,
|
|
3886
|
+
wallet: options?.wallet,
|
|
3887
|
+
fetch: options?.fetch,
|
|
3888
|
+
execution: options?.execution,
|
|
3889
|
+
};
|
|
3890
|
+
const client = new Arete(url, httpUrl, internalOptions);
|
|
3891
|
+
if (!httpOnly && autoReconnect) {
|
|
3892
|
+
await client.connection.connect();
|
|
2870
3893
|
}
|
|
2871
|
-
|
|
3894
|
+
return applyConnectedStackExtensions(client, effectiveStack);
|
|
2872
3895
|
}
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
3896
|
+
get views() {
|
|
3897
|
+
return this._views;
|
|
3898
|
+
}
|
|
3899
|
+
get queries() {
|
|
3900
|
+
return this._queries;
|
|
3901
|
+
}
|
|
3902
|
+
get programs() {
|
|
3903
|
+
return this._programs;
|
|
3904
|
+
}
|
|
3905
|
+
get chain() {
|
|
3906
|
+
return this._chain;
|
|
3907
|
+
}
|
|
3908
|
+
/** The default wallet adapter, if one was configured. */
|
|
3909
|
+
get wallet() {
|
|
3910
|
+
return this._wallet;
|
|
3911
|
+
}
|
|
3912
|
+
/** The connected wallet address, if a default wallet was configured. */
|
|
3913
|
+
get publicKey() {
|
|
3914
|
+
return this._wallet?.publicKey;
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Set (or clear) the default wallet adapter used for instruction execution.
|
|
3918
|
+
* Useful for connecting/disconnecting a wallet after the client is created.
|
|
3919
|
+
*/
|
|
3920
|
+
setWallet(wallet) {
|
|
3921
|
+
this._wallet = wallet;
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* Sign and send a batch of pre-built instructions as a single transaction.
|
|
3925
|
+
*
|
|
3926
|
+
* Build instructions with `client.programs.<program>.raw.<name>.build(params)`
|
|
3927
|
+
* and compose them here. RPC/compilation/confirmation are owned by the adapter.
|
|
3928
|
+
*
|
|
3929
|
+
* On failure, the error is parsed against `options.errors` when given,
|
|
3930
|
+
* otherwise against error metadata aggregated from all the stack's handlers
|
|
3931
|
+
* (deduped by code, first-wins — if the stack bundles programs with
|
|
3932
|
+
* overlapping error codes, pass `options.errors` or use the per-instruction
|
|
3933
|
+
* call path for precise attribution).
|
|
3934
|
+
*/
|
|
3935
|
+
async transaction(instructions, options) {
|
|
3936
|
+
const wallet = options?.wallet ?? this._wallet;
|
|
3937
|
+
if (!wallet) {
|
|
3938
|
+
throw new Error('Wallet required to sign and send transaction');
|
|
2880
3939
|
}
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
this.notifyUpdate(viewPath, frame.key, {
|
|
2888
|
-
type: 'upsert',
|
|
2889
|
-
key: frame.key,
|
|
2890
|
-
data: frame.data,
|
|
2891
|
-
});
|
|
2892
|
-
this.notifyRichUpdate(viewPath, frame.key, previousValue, frame.data, frame.op);
|
|
2893
|
-
break;
|
|
2894
|
-
case 'patch': {
|
|
2895
|
-
const existing = viewData.get(frame.key);
|
|
2896
|
-
const appendPaths = frame.append ?? [];
|
|
2897
|
-
const merged = existing
|
|
2898
|
-
? deepMergeWithAppend(existing, frame.data, appendPaths)
|
|
2899
|
-
: frame.data;
|
|
2900
|
-
viewData.set(frame.key, merged);
|
|
2901
|
-
this.enforceMaxEntries(viewData);
|
|
2902
|
-
this.notifyUpdate(viewPath, frame.key, {
|
|
2903
|
-
type: 'patch',
|
|
2904
|
-
key: frame.key,
|
|
2905
|
-
data: frame.data,
|
|
2906
|
-
});
|
|
2907
|
-
this.notifyRichUpdate(viewPath, frame.key, previousValue, merged, 'patch', frame.data);
|
|
2908
|
-
break;
|
|
3940
|
+
try {
|
|
3941
|
+
const sendOptions = {
|
|
3942
|
+
...(options?.send ?? {}),
|
|
3943
|
+
};
|
|
3944
|
+
if (options?.signers !== undefined) {
|
|
3945
|
+
sendOptions.signers = options.signers;
|
|
2909
3946
|
}
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
3947
|
+
const result = await wallet.signAndSend(instructions, sendOptions);
|
|
3948
|
+
return { signature: result.signature, slot: result.slot };
|
|
3949
|
+
}
|
|
3950
|
+
catch (err) {
|
|
3951
|
+
const programError = parseInstructionError(err, options?.errors ?? this.aggregateErrors());
|
|
3952
|
+
if (programError) {
|
|
3953
|
+
throw new InstructionError(`${programError.name} (${programError.code}): ${programError.message}`, programError, err);
|
|
3954
|
+
}
|
|
3955
|
+
throw err;
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
execute(prepared, options) {
|
|
3959
|
+
const defaults = this.executionDefaults;
|
|
3960
|
+
return executePreparedOperation(this, prepared, {
|
|
3961
|
+
wallet: options?.wallet ?? defaults?.wallet,
|
|
3962
|
+
send: defaults?.send || options?.send
|
|
3963
|
+
? { ...(defaults?.send ?? {}), ...(options?.send ?? {}) }
|
|
3964
|
+
: undefined,
|
|
3965
|
+
signers: options?.signers ?? defaults?.signers,
|
|
3966
|
+
signerRegistry: options?.signerRegistry ?? defaults?.signerRegistry,
|
|
3967
|
+
availableSignerAddresses: options?.availableSignerAddresses ?? defaults?.availableSignerAddresses,
|
|
3968
|
+
onTransactionStart: options?.onTransactionStart ?? defaults?.onTransactionStart,
|
|
3969
|
+
onTransactionSuccess: options?.onTransactionSuccess ?? defaults?.onTransactionSuccess,
|
|
3970
|
+
});
|
|
3971
|
+
}
|
|
3972
|
+
/** Error metadata from every handler in the stack, deduped by code. */
|
|
3973
|
+
aggregateErrors() {
|
|
3974
|
+
if (!this._aggregatedErrors) {
|
|
3975
|
+
const all = [];
|
|
3976
|
+
const seen = new Set();
|
|
3977
|
+
const collect = (handler) => {
|
|
3978
|
+
for (const error of handler.errors ?? []) {
|
|
3979
|
+
if (!seen.has(error.code)) {
|
|
3980
|
+
seen.add(error.code);
|
|
3981
|
+
all.push(error);
|
|
3982
|
+
}
|
|
2918
3983
|
}
|
|
2919
|
-
|
|
3984
|
+
};
|
|
3985
|
+
for (const program of Object.values(this.stack.programs ?? {})) {
|
|
3986
|
+
for (const handler of Object.values(program.rawInstructions ?? {})) {
|
|
3987
|
+
collect(handler);
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
this._aggregatedErrors = all;
|
|
2920
3991
|
}
|
|
3992
|
+
return this._aggregatedErrors;
|
|
2921
3993
|
}
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
if (!viewData)
|
|
2925
|
-
return [];
|
|
2926
|
-
return viewData.values();
|
|
3994
|
+
get connectionState() {
|
|
3995
|
+
return this.connection.getState();
|
|
2927
3996
|
}
|
|
2928
|
-
get(
|
|
2929
|
-
|
|
2930
|
-
if (!viewData)
|
|
2931
|
-
return null;
|
|
2932
|
-
const value = viewData.get(key);
|
|
2933
|
-
return value !== undefined ? value : null;
|
|
3997
|
+
get stackName() {
|
|
3998
|
+
return this.stack.name;
|
|
2934
3999
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
if (!viewData)
|
|
2938
|
-
return undefined;
|
|
2939
|
-
return viewData.values();
|
|
4000
|
+
get store() {
|
|
4001
|
+
return this.storage;
|
|
2940
4002
|
}
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
if (!viewData)
|
|
2944
|
-
return undefined;
|
|
2945
|
-
const value = viewData.get(key);
|
|
2946
|
-
return value !== undefined ? value : null;
|
|
4003
|
+
onConnectionStateChange(callback) {
|
|
4004
|
+
return this.connection.onStateChange(callback);
|
|
2947
4005
|
}
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
if (!viewData)
|
|
2951
|
-
return [];
|
|
2952
|
-
return viewData.keys();
|
|
4006
|
+
onFrame(callback) {
|
|
4007
|
+
return this.connection.onFrame(callback);
|
|
2953
4008
|
}
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
return viewData?.size ?? 0;
|
|
4009
|
+
onSocketIssue(callback) {
|
|
4010
|
+
return this.connection.onSocketIssue(callback);
|
|
2957
4011
|
}
|
|
2958
|
-
|
|
2959
|
-
this.
|
|
4012
|
+
async connect() {
|
|
4013
|
+
await this.connection.connect();
|
|
2960
4014
|
}
|
|
2961
|
-
|
|
2962
|
-
this.
|
|
2963
|
-
this.
|
|
4015
|
+
disconnect() {
|
|
4016
|
+
this.subscriptionRegistry.clear();
|
|
4017
|
+
this.connection.disconnect();
|
|
2964
4018
|
}
|
|
2965
|
-
|
|
2966
|
-
return this.
|
|
4019
|
+
isConnected() {
|
|
4020
|
+
return this.connection.isConnected();
|
|
2967
4021
|
}
|
|
2968
|
-
|
|
2969
|
-
this.
|
|
2970
|
-
const existingView = this.views.get(viewPath);
|
|
2971
|
-
if (existingView && config.sort) {
|
|
2972
|
-
existingView.setSortConfig(config.sort);
|
|
2973
|
-
}
|
|
4022
|
+
clearStore() {
|
|
4023
|
+
this.storage.clear();
|
|
2974
4024
|
}
|
|
2975
|
-
|
|
2976
|
-
this.
|
|
2977
|
-
return () => {
|
|
2978
|
-
this.updateCallbacks.delete(callback);
|
|
2979
|
-
};
|
|
4025
|
+
getStore() {
|
|
4026
|
+
return this.storage;
|
|
2980
4027
|
}
|
|
2981
|
-
|
|
2982
|
-
this.
|
|
2983
|
-
return () => {
|
|
2984
|
-
this.richUpdateCallbacks.delete(callback);
|
|
2985
|
-
};
|
|
4028
|
+
getConnection() {
|
|
4029
|
+
return this.connection;
|
|
2986
4030
|
}
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
if (path === viewPath) {
|
|
2990
|
-
callback(update);
|
|
2991
|
-
}
|
|
2992
|
-
};
|
|
2993
|
-
this.updateCallbacks.add(handler);
|
|
2994
|
-
return () => {
|
|
2995
|
-
this.updateCallbacks.delete(handler);
|
|
2996
|
-
};
|
|
4031
|
+
getSubscriptionRegistry() {
|
|
4032
|
+
return this.subscriptionRegistry;
|
|
2997
4033
|
}
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
4034
|
+
};
|
|
4035
|
+
|
|
4036
|
+
function createSignerRegistry(entries = []) {
|
|
4037
|
+
const signers = new Map();
|
|
4038
|
+
for (const [address, signer] of entries) {
|
|
4039
|
+
if (!address) {
|
|
4040
|
+
throw new Error('Signer registry addresses must not be empty');
|
|
4041
|
+
}
|
|
4042
|
+
signers.set(address, signer);
|
|
4043
|
+
}
|
|
4044
|
+
return {
|
|
4045
|
+
register(address, signer) {
|
|
4046
|
+
if (!address) {
|
|
4047
|
+
throw new Error('Signer registry addresses must not be empty');
|
|
3002
4048
|
}
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
}
|
|
4049
|
+
signers.set(address, signer);
|
|
4050
|
+
},
|
|
4051
|
+
unregister(address) {
|
|
4052
|
+
return signers.delete(address);
|
|
4053
|
+
},
|
|
4054
|
+
get(address) {
|
|
4055
|
+
return signers.get(address);
|
|
4056
|
+
},
|
|
4057
|
+
has(address) {
|
|
4058
|
+
return signers.has(address);
|
|
4059
|
+
},
|
|
4060
|
+
addresses() {
|
|
4061
|
+
return [...signers.keys()];
|
|
4062
|
+
},
|
|
4063
|
+
values() {
|
|
4064
|
+
return [...signers.values()];
|
|
4065
|
+
},
|
|
4066
|
+
entries() {
|
|
4067
|
+
return [...signers.entries()];
|
|
4068
|
+
},
|
|
4069
|
+
clear() {
|
|
4070
|
+
signers.clear();
|
|
4071
|
+
},
|
|
4072
|
+
};
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
function resolveMemberConnectOptions(stack, member, options, forceHttpOnly) {
|
|
4076
|
+
const transport = member?.transport ?? options?.transport ?? (forceHttpOnly ? 'http' : 'ws');
|
|
4077
|
+
// Resolution order: per-member override → the stack's own endpoints →
|
|
4078
|
+
// session-wide fallback endpoints.
|
|
4079
|
+
const url = member?.url ?? (stack.endpoints.ws || options?.endpoints?.ws);
|
|
4080
|
+
const httpUrl = member?.httpUrl ?? stack.endpoints.http ?? options?.endpoints?.http;
|
|
4081
|
+
return {
|
|
4082
|
+
url,
|
|
4083
|
+
httpUrl,
|
|
4084
|
+
transport,
|
|
4085
|
+
auth: member?.auth ?? options?.auth,
|
|
4086
|
+
storage: member?.storage,
|
|
4087
|
+
autoReconnect: member?.autoReconnect,
|
|
4088
|
+
wallet: options?.wallet,
|
|
4089
|
+
fetch: options?.fetch,
|
|
4090
|
+
};
|
|
4091
|
+
}
|
|
4092
|
+
function programAsStack(name, program) {
|
|
4093
|
+
return {
|
|
4094
|
+
name,
|
|
4095
|
+
endpoints: { ws: '' },
|
|
4096
|
+
views: {},
|
|
4097
|
+
programs: { [name]: program },
|
|
4098
|
+
};
|
|
4099
|
+
}
|
|
4100
|
+
async function createSession(definition, options) {
|
|
4101
|
+
const stackEntries = Object.entries(definition.stacks ?? {});
|
|
4102
|
+
const programEntries = Object.entries(definition.programs ?? {});
|
|
4103
|
+
if (stackEntries.length === 0 && programEntries.length === 0) {
|
|
4104
|
+
throw new AreteError('createSession requires at least one stack or program member', 'INVALID_CONFIG');
|
|
4105
|
+
}
|
|
4106
|
+
let wallet = options?.wallet;
|
|
4107
|
+
const signerRegistry = options?.signerRegistry ?? createSignerRegistry();
|
|
4108
|
+
const connectedStacks = await Promise.all(stackEntries.map(async ([key, stack]) => {
|
|
4109
|
+
const memberOptions = options?.stacks?.[key];
|
|
4110
|
+
const effectiveStack = withPrograms(stack, memberOptions?.programs);
|
|
4111
|
+
const connectOptions = resolveMemberConnectOptions(effectiveStack, memberOptions, options, false);
|
|
4112
|
+
const client = await Arete$1.connect(effectiveStack, connectOptions);
|
|
4113
|
+
return [key, client];
|
|
4114
|
+
}));
|
|
4115
|
+
const connectedPrograms = await Promise.all(programEntries.map(async ([key, program]) => {
|
|
4116
|
+
const syntheticStack = programAsStack(key, program);
|
|
4117
|
+
const connectOptions = resolveMemberConnectOptions(syntheticStack, options?.programs?.[key], options, true);
|
|
4118
|
+
const client = await Arete$1.connect(syntheticStack, connectOptions);
|
|
4119
|
+
return [key, client];
|
|
4120
|
+
}));
|
|
4121
|
+
const memberClients = [
|
|
4122
|
+
...connectedStacks.map(([, client]) => client),
|
|
4123
|
+
...connectedPrograms.map(([, client]) => client),
|
|
4124
|
+
];
|
|
4125
|
+
const executionHost = memberClients[0];
|
|
4126
|
+
const stacks = Object.fromEntries(connectedStacks);
|
|
4127
|
+
const explicitProgramKeys = new Set(connectedPrograms.map(([key]) => key));
|
|
4128
|
+
const programOwners = new Map();
|
|
4129
|
+
const connectedProgramEntries = connectedPrograms.map(([key, client]) => [
|
|
4130
|
+
key,
|
|
4131
|
+
client.programs[key],
|
|
4132
|
+
]);
|
|
4133
|
+
const promotedPrograms = Object.fromEntries(connectedProgramEntries);
|
|
4134
|
+
for (const [stackKey, client] of connectedStacks) {
|
|
4135
|
+
for (const [programKey, program] of Object.entries(client.programs)) {
|
|
4136
|
+
if (explicitProgramKeys.has(programKey)) {
|
|
4137
|
+
continue;
|
|
4138
|
+
}
|
|
4139
|
+
const existingOwner = programOwners.get(programKey);
|
|
4140
|
+
if (existingOwner) {
|
|
4141
|
+
console.warn(`Program '${programKey}' is bundled by stacks '${existingOwner.stack}'` +
|
|
4142
|
+
` (${existingOwner.programId ?? 'unknown program ID'}) and '${stackKey}'` +
|
|
4143
|
+
` (${program.programId ?? 'unknown program ID'}); session.programs.${programKey}` +
|
|
4144
|
+
` uses '${existingOwner.stack}' because it was connected first`);
|
|
4145
|
+
continue;
|
|
4146
|
+
}
|
|
4147
|
+
promotedPrograms[programKey] = program;
|
|
4148
|
+
programOwners.set(programKey, { stack: stackKey, programId: program.programId });
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
const programs = promotedPrograms;
|
|
4152
|
+
const chain = options?.chain ?? (options?.endpoints?.http !== undefined
|
|
4153
|
+
? createChainClient(options.endpoints.http, (options?.fetch ?? globalThis.fetch?.bind(globalThis)))
|
|
4154
|
+
: executionHost.chain);
|
|
4155
|
+
const session = {
|
|
4156
|
+
stacks,
|
|
4157
|
+
programs,
|
|
4158
|
+
get wallet() {
|
|
4159
|
+
return wallet;
|
|
4160
|
+
},
|
|
4161
|
+
signerRegistry,
|
|
4162
|
+
chain,
|
|
4163
|
+
transaction(instructions, transactionOptions) {
|
|
4164
|
+
const defaults = options?.execution;
|
|
4165
|
+
const configuredSigners = transactionOptions?.signers ?? defaults?.signers;
|
|
4166
|
+
const signers = [...new Set([...signerRegistry.values(), ...(configuredSigners ?? [])])];
|
|
4167
|
+
return executionHost.transaction(instructions, {
|
|
4168
|
+
wallet: transactionOptions?.wallet ?? defaults?.wallet,
|
|
4169
|
+
send: defaults?.send || transactionOptions?.send
|
|
4170
|
+
? { ...(defaults?.send ?? {}), ...(transactionOptions?.send ?? {}) }
|
|
4171
|
+
: undefined,
|
|
4172
|
+
errors: transactionOptions?.errors,
|
|
4173
|
+
signers: signers.length > 0 ? signers : undefined,
|
|
4174
|
+
});
|
|
4175
|
+
},
|
|
4176
|
+
execute(prepared, executionOptions) {
|
|
4177
|
+
const defaults = options?.execution;
|
|
4178
|
+
const configuredSigners = executionOptions?.signers ?? defaults?.signers;
|
|
4179
|
+
return executionHost.execute(prepared, {
|
|
4180
|
+
wallet: executionOptions?.wallet ?? defaults?.wallet,
|
|
4181
|
+
send: defaults?.send || executionOptions?.send
|
|
4182
|
+
? { ...(defaults?.send ?? {}), ...(executionOptions?.send ?? {}) }
|
|
4183
|
+
: undefined,
|
|
4184
|
+
signers: configuredSigners,
|
|
4185
|
+
signerRegistry,
|
|
4186
|
+
availableSignerAddresses: executionOptions?.availableSignerAddresses ?? defaults?.availableSignerAddresses,
|
|
4187
|
+
onTransactionStart: executionOptions?.onTransactionStart ?? defaults?.onTransactionStart,
|
|
4188
|
+
onTransactionSuccess: executionOptions?.onTransactionSuccess ?? defaults?.onTransactionSuccess,
|
|
4189
|
+
});
|
|
4190
|
+
},
|
|
4191
|
+
setWallet(nextWallet) {
|
|
4192
|
+
wallet = nextWallet;
|
|
4193
|
+
for (const client of memberClients) {
|
|
4194
|
+
client.setWallet(nextWallet);
|
|
4195
|
+
}
|
|
4196
|
+
},
|
|
4197
|
+
close() {
|
|
4198
|
+
for (const client of memberClients) {
|
|
4199
|
+
client.disconnect();
|
|
4200
|
+
}
|
|
4201
|
+
},
|
|
4202
|
+
};
|
|
4203
|
+
return session;
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
function instructionOperation(prepare) {
|
|
4207
|
+
return {
|
|
4208
|
+
kind: 'instruction',
|
|
4209
|
+
prepare: async (input) => prepare(input),
|
|
4210
|
+
};
|
|
4211
|
+
}
|
|
4212
|
+
function transactionOperation(prepare) {
|
|
4213
|
+
return {
|
|
4214
|
+
kind: 'transaction',
|
|
4215
|
+
prepare: async (input) => prepare(input),
|
|
4216
|
+
};
|
|
4217
|
+
}
|
|
4218
|
+
function flowOperation(prepare) {
|
|
4219
|
+
return {
|
|
4220
|
+
kind: 'flow',
|
|
4221
|
+
prepare: async (input) => prepare(input),
|
|
4222
|
+
};
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
function chainAccountLoader(chain) {
|
|
4226
|
+
return {
|
|
4227
|
+
async getAccount(address) {
|
|
4228
|
+
const account = await chain.account(address);
|
|
4229
|
+
return account ? { data: account.data } : null;
|
|
4230
|
+
},
|
|
4231
|
+
};
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
const SPL_TOKEN_PROGRAM_ADDRESS = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
|
|
4235
|
+
const TOKEN_2022_PROGRAM_ADDRESS = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
|
|
4236
|
+
const ASSOCIATED_TOKEN_PROGRAM_ADDRESS = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
|
|
4237
|
+
const SYSTEM_PROGRAM_ADDRESS = '11111111111111111111111111111111';
|
|
4238
|
+
async function resolveTokenProgramAddress(chain, mint, override) {
|
|
4239
|
+
if (override) {
|
|
4240
|
+
return override;
|
|
4241
|
+
}
|
|
4242
|
+
const mintAccount = await chain.mint(mint);
|
|
4243
|
+
if (!mintAccount) {
|
|
4244
|
+
throw new Error(`Mint account not found while resolving token program: ${mint}`);
|
|
4245
|
+
}
|
|
4246
|
+
if (mintAccount.ownerProgram !== SPL_TOKEN_PROGRAM_ADDRESS &&
|
|
4247
|
+
mintAccount.ownerProgram !== TOKEN_2022_PROGRAM_ADDRESS) {
|
|
4248
|
+
throw new Error(`Mint ${mint} is owned by unsupported token program ${mintAccount.ownerProgram}`);
|
|
4249
|
+
}
|
|
4250
|
+
return mintAccount.ownerProgram;
|
|
4251
|
+
}
|
|
4252
|
+
function deriveAssociatedTokenAccount(input) {
|
|
4253
|
+
const [address] = findProgramAddressSync([
|
|
4254
|
+
createPublicKeySeed(input.owner),
|
|
4255
|
+
createPublicKeySeed(input.tokenProgram ?? SPL_TOKEN_PROGRAM_ADDRESS),
|
|
4256
|
+
createPublicKeySeed(input.mint),
|
|
4257
|
+
], ASSOCIATED_TOKEN_PROGRAM_ADDRESS);
|
|
4258
|
+
return address;
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4261
|
+
/** Convert a UI amount ("1.5") to raw base units using string math (no float precision loss). */
|
|
4262
|
+
function parseUiAmountToRaw(value, decimals) {
|
|
4263
|
+
const trimmed = String(value).trim();
|
|
4264
|
+
if (!/^\d+(?:\.\d+)?$/.test(trimmed)) {
|
|
4265
|
+
throw new Error(`Invalid UI amount: ${value}`);
|
|
4266
|
+
}
|
|
4267
|
+
const [wholePart, fractionPart = ''] = trimmed.split('.');
|
|
4268
|
+
if (fractionPart.length > decimals) {
|
|
4269
|
+
const excess = fractionPart.slice(decimals);
|
|
4270
|
+
if (/[1-9]/.test(excess)) {
|
|
4271
|
+
throw new Error(`UI amount ${value} has more fractional digits than the mint's ${decimals} decimals`);
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
const fraction = fractionPart.padEnd(decimals, '0');
|
|
4275
|
+
const whole = BigInt(wholePart || '0') * 10n ** BigInt(decimals);
|
|
4276
|
+
const fractional = BigInt(fraction.slice(0, decimals) || '0');
|
|
4277
|
+
return whole + fractional;
|
|
4278
|
+
}
|
|
4279
|
+
/** Format raw base units as a UI decimal string (inverse of {@link parseUiAmountToRaw}). */
|
|
4280
|
+
function formatRawToUi(raw, decimals) {
|
|
4281
|
+
const value = BigInt(raw);
|
|
4282
|
+
const negative = value < 0n;
|
|
4283
|
+
const magnitude = negative ? -value : value;
|
|
4284
|
+
const scale = 10n ** BigInt(decimals);
|
|
4285
|
+
const whole = magnitude / scale;
|
|
4286
|
+
const fraction = magnitude % scale;
|
|
4287
|
+
const sign = negative ? '-' : '';
|
|
4288
|
+
if (decimals === 0 || fraction === 0n) {
|
|
4289
|
+
return `${sign}${whole.toString()}`;
|
|
4290
|
+
}
|
|
4291
|
+
const fractionText = fraction.toString().padStart(decimals, '0').replace(/0+$/, '');
|
|
4292
|
+
return `${sign}${whole.toString()}.${fractionText}`;
|
|
4293
|
+
}
|
|
4294
|
+
/** Resolve an {@link AmountInput} to raw base units with known decimals. */
|
|
4295
|
+
function toRawAmount(amount, decimals) {
|
|
4296
|
+
if (typeof amount === 'bigint') {
|
|
4297
|
+
return amount;
|
|
3008
4298
|
}
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
callback(viewPath, key, update);
|
|
3012
|
-
}
|
|
4299
|
+
if ('raw' in amount) {
|
|
4300
|
+
return BigInt(amount.raw);
|
|
3013
4301
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
}
|
|
4302
|
+
return parseUiAmountToRaw(amount.ui, decimals);
|
|
4303
|
+
}
|
|
4304
|
+
/** Fetch a mint's decimals via the chain read endpoint, throwing when unavailable. */
|
|
4305
|
+
async function getMintDecimals(chain, mint) {
|
|
4306
|
+
const account = await chain.mint(mint);
|
|
4307
|
+
if (!account || account.decimals == null) {
|
|
4308
|
+
throw new Error(`Mint ${mint} is missing decimals on the configured read endpoint.`);
|
|
3021
4309
|
}
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
4310
|
+
return account.decimals;
|
|
4311
|
+
}
|
|
4312
|
+
/**
|
|
4313
|
+
* Resolve an {@link AmountInput} to raw base units, fetching the mint's
|
|
4314
|
+
* decimals only when they are unknown and actually needed (a bare bigint or
|
|
4315
|
+
* `{raw}` input with explicit `decimals` never touches the network).
|
|
4316
|
+
*/
|
|
4317
|
+
async function resolveAmount(chain, input) {
|
|
4318
|
+
const needsDecimalsForParse = typeof input.amount !== 'bigint' && 'ui' in input.amount;
|
|
4319
|
+
if (!needsDecimalsForParse) {
|
|
4320
|
+
const raw = typeof input.amount === 'bigint'
|
|
4321
|
+
? input.amount
|
|
4322
|
+
: BigInt(input.amount.raw);
|
|
4323
|
+
const decimals = input.decimals ?? await getMintDecimals(chain, input.mint);
|
|
4324
|
+
return { raw, decimals };
|
|
4325
|
+
}
|
|
4326
|
+
const decimals = input.decimals ?? await getMintDecimals(chain, input.mint);
|
|
4327
|
+
return { raw: toRawAmount(input.amount, decimals), decimals };
|
|
4328
|
+
}
|
|
4329
|
+
/**
|
|
4330
|
+
* Resolve an {@link AmountInput} to raw base units without forcing a decimals
|
|
4331
|
+
* fetch when the input is already expressed in raw units.
|
|
4332
|
+
*/
|
|
4333
|
+
async function resolveAmountToRaw(chain, input) {
|
|
4334
|
+
if (typeof input.amount === 'bigint') {
|
|
4335
|
+
return input.amount;
|
|
4336
|
+
}
|
|
4337
|
+
if ('raw' in input.amount) {
|
|
4338
|
+
return BigInt(input.amount.raw);
|
|
3027
4339
|
}
|
|
4340
|
+
const decimals = input.decimals ?? await getMintDecimals(chain, input.mint);
|
|
4341
|
+
return toRawAmount(input.amount, decimals);
|
|
3028
4342
|
}
|
|
4343
|
+
/** Resolve a named set of {@link AmountInput} values to raw base units. */
|
|
4344
|
+
async function resolveAmountsToRaw(chain, inputs) {
|
|
4345
|
+
const entries = await Promise.all(Object.entries(inputs).map(async ([name, input]) => {
|
|
4346
|
+
const raw = await resolveAmountToRaw(chain, input);
|
|
4347
|
+
return [name, raw];
|
|
4348
|
+
}));
|
|
4349
|
+
return Object.fromEntries(entries);
|
|
4350
|
+
}
|
|
4351
|
+
|
|
4352
|
+
const Arete = Object.assign(Arete$1, { session: createSession });
|
|
3029
4353
|
|
|
3030
|
-
export { Arete, AreteError, ConnectionManager, DEFAULT_CONFIG, DEFAULT_MAX_ENTRIES_PER_VIEW,
|
|
4354
|
+
export { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, Arete, AreteError, ConnectionManager, DEFAULT_CONFIG, DEFAULT_MAX_ENTRIES_PER_VIEW, FrameProcessor, InstructionError, MemoryAdapter, OperationExecutionError, PROGRAM_OPERATION_EXTENSIONS, ReadRequestError, SPL_TOKEN_PROGRAM_ADDRESS, STACK_RUNTIME_EXTENSIONS, SYSTEM_PROGRAM_ADDRESS, SubscriptionRegistry, TOKEN_2022_PROGRAM_ADDRESS, account, appendFlowTransactions, appendTransactionInstructions, applyConnectedStackExtensions, arg, buildInstruction, bytes, chainAccountLoader, createChainClient, createEntityStream, createInstructionExecutor, createInstructionHandler, createPreparedFlow, createPreparedInstruction, createPreparedTransaction, createPreparedTransactionBody, createProgramPdas, createPublicKeySeed, createRichUpdateStream, createSeed, createSession, createSignerRegistry, createTypedListView, createTypedStateView, createTypedViews, createUpdateStream, decodeBase58, defineProgramExtensions, defineStackExtensions, deriveAssociatedTokenAccount, deriveHttpEndpoint, findProgramAddress as derivePda, describePreparedOperation, encodeBase58, executeInstruction, executePreparedOperation, extendProgram, extendPrograms, extendStack, findProgramAddress, findProgramAddressSync, flowOperation, formatPreparedOperation, formatProgramError, formatRawToUi, getMintDecimals, getProgramRuntimeExtensions, getStackRuntimeExtensions, instructionOperation, isEntityFrame, isSnapshotFrame, isSubscribedFrame, isValidFrame, literal, parseFrame, parseFrameFromBlob, parseInstructionError, parseUiAmountToRaw, pda, prependFlowTransactionInstructions, prependTransactionInstructions, programAccountRead, programQuery, resolveAccounts, resolveAmount, resolveAmountToRaw, resolveAmountsToRaw, resolveTokenProgramAddress, serializeInstructionData, stackQuery, toJsonValue, toRawAmount, transactionOperation, validateAccountResolution, withPrograms };
|
|
3031
4355
|
//# sourceMappingURL=index.esm.js.map
|