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