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