@usearete/sdk 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/index.cjs +808 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +214 -20
- package/dist/index.esm.js +807 -80
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -512,6 +512,7 @@ const MIN_REFRESH_DELAY_MS = 1000;
|
|
|
512
512
|
const DEFAULT_QUERY_PARAMETER = 'hs_token';
|
|
513
513
|
const DEFAULT_HOSTED_TOKEN_ENDPOINT = 'https://api.arete.run/ws/sessions';
|
|
514
514
|
const HOSTED_WEBSOCKET_SUFFIX = '.stack.arete.run';
|
|
515
|
+
const MAX_HTTP_AUTH_TOKEN_STATES = 32;
|
|
515
516
|
function normalizeTokenResult(result) {
|
|
516
517
|
if (typeof result === 'string') {
|
|
517
518
|
return { token: result };
|
|
@@ -550,6 +551,71 @@ function parseJwtExpiry(token) {
|
|
|
550
551
|
function normalizeExpiryTimestamp(expiresAt, expires_at) {
|
|
551
552
|
return expiresAt ?? expires_at;
|
|
552
553
|
}
|
|
554
|
+
function normalizeScopes(scopes) {
|
|
555
|
+
return [...new Set(scopes)].sort();
|
|
556
|
+
}
|
|
557
|
+
function isProgramReadBindingAuthTarget(value) {
|
|
558
|
+
if (typeof value !== 'object' || value === null) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
const candidate = value;
|
|
562
|
+
return candidate['targetKind'] === 'program-read-binding'
|
|
563
|
+
&& typeof candidate['targetId'] === 'string'
|
|
564
|
+
&& typeof candidate['programReleaseHash'] === 'string';
|
|
565
|
+
}
|
|
566
|
+
function isAuthTokenTarget(value) {
|
|
567
|
+
if (isProgramReadBindingAuthTarget(value)) {
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
if (typeof value !== 'object' || value === null) {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
const candidate = value;
|
|
574
|
+
return candidate['targetKind'] === 'solana-gateway-binding'
|
|
575
|
+
&& typeof candidate['targetId'] === 'string'
|
|
576
|
+
&& candidate['programReleaseHash'] === undefined;
|
|
577
|
+
}
|
|
578
|
+
function isAuthTokenRequest(value) {
|
|
579
|
+
return typeof value === 'object'
|
|
580
|
+
&& value !== null
|
|
581
|
+
&& Array.isArray(value.scopes);
|
|
582
|
+
}
|
|
583
|
+
function hasAuthTokenTarget(request) {
|
|
584
|
+
return isAuthTokenTarget(request);
|
|
585
|
+
}
|
|
586
|
+
function normalizeAuthTokenRequest(request) {
|
|
587
|
+
const scopes = normalizeScopes(request.scopes);
|
|
588
|
+
const hasTargetField = request.targetKind !== undefined
|
|
589
|
+
|| request.targetId !== undefined
|
|
590
|
+
|| request.programReleaseHash !== undefined;
|
|
591
|
+
if (!hasTargetField) {
|
|
592
|
+
return { scopes };
|
|
593
|
+
}
|
|
594
|
+
if (!hasAuthTokenTarget(request)) {
|
|
595
|
+
throw new AreteError('Targeted authentication requires a complete supported target identity', 'INVALID_CONFIG');
|
|
596
|
+
}
|
|
597
|
+
if (isProgramReadBindingAuthTarget(request)) {
|
|
598
|
+
return {
|
|
599
|
+
scopes,
|
|
600
|
+
targetKind: request.targetKind,
|
|
601
|
+
targetId: request.targetId,
|
|
602
|
+
programReleaseHash: request.programReleaseHash,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
scopes,
|
|
607
|
+
targetKind: request.targetKind,
|
|
608
|
+
targetId: request.targetId,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function createTargetedHttpTokenIdentity(request) {
|
|
612
|
+
return JSON.stringify([
|
|
613
|
+
request.targetKind,
|
|
614
|
+
request.targetId,
|
|
615
|
+
request.programReleaseHash ?? null,
|
|
616
|
+
normalizeScopes(request.scopes),
|
|
617
|
+
]);
|
|
618
|
+
}
|
|
553
619
|
function isRefreshAuthResponseMessage(value) {
|
|
554
620
|
if (typeof value !== 'object' || value === null) {
|
|
555
621
|
return false;
|
|
@@ -594,6 +660,7 @@ class ConnectionManager {
|
|
|
594
660
|
this.tokenRefreshTimeout = null;
|
|
595
661
|
this.tokenRefreshInFlight = null;
|
|
596
662
|
this.tokenRequestInFlight = null;
|
|
663
|
+
this.httpAuthTokenStates = new Map();
|
|
597
664
|
this.currentState = 'disconnected';
|
|
598
665
|
this.subscriptionQueue = new Map();
|
|
599
666
|
this.activeSubscriptions = new Map();
|
|
@@ -615,6 +682,12 @@ class ConnectionManager {
|
|
|
615
682
|
this.maxReconnectAttempts =
|
|
616
683
|
config.maxReconnectAttempts ?? DEFAULT_CONFIG.maxReconnectAttempts;
|
|
617
684
|
this.authConfig = config.auth;
|
|
685
|
+
this.authFetch = config.fetch ?? ((input, init) => {
|
|
686
|
+
if (typeof globalThis.fetch !== 'function') {
|
|
687
|
+
throw new AreteError('A fetch implementation is required for authentication token requests', 'INVALID_CONFIG');
|
|
688
|
+
}
|
|
689
|
+
return globalThis.fetch(input, init);
|
|
690
|
+
});
|
|
618
691
|
if (config.initialSubscriptions) {
|
|
619
692
|
for (const subscription of config.initialSubscriptions) {
|
|
620
693
|
this.addQueuedSubscription(subscription);
|
|
@@ -625,8 +698,10 @@ class ConnectionManager {
|
|
|
625
698
|
if (this.authConfig?.tokenEndpoint) {
|
|
626
699
|
return this.authConfig.tokenEndpoint;
|
|
627
700
|
}
|
|
628
|
-
//
|
|
629
|
-
|
|
701
|
+
// Hosted stacks always mint through the Arete Cloud token endpoint.
|
|
702
|
+
// Without a publishable key the session is minted anonymously — the API
|
|
703
|
+
// rate limits by IP/origin and issues low-trust public limits instead.
|
|
704
|
+
if (this.hostedAreteUrl) {
|
|
630
705
|
return DEFAULT_HOSTED_TOKEN_ENDPOINT;
|
|
631
706
|
}
|
|
632
707
|
return undefined;
|
|
@@ -671,9 +746,13 @@ class ConnectionManager {
|
|
|
671
746
|
return scopes.every((scope) => this.tokenScopes.has(scope));
|
|
672
747
|
}
|
|
673
748
|
async getOrRefreshToken(forceRefresh = false, requiredScopes = ['read']) {
|
|
674
|
-
|
|
749
|
+
const normalizedRequiredScopes = normalizeScopes(requiredScopes);
|
|
750
|
+
for (const scope of normalizedRequiredScopes)
|
|
675
751
|
this.requestedScopes.add(scope);
|
|
676
|
-
if (!forceRefresh
|
|
752
|
+
if (!forceRefresh
|
|
753
|
+
&& this.currentToken
|
|
754
|
+
&& !this.isTokenExpired()
|
|
755
|
+
&& this.tokenCovers(normalizedRequiredScopes)) {
|
|
677
756
|
return this.currentToken;
|
|
678
757
|
}
|
|
679
758
|
if (this.tokenRequestInFlight) {
|
|
@@ -681,18 +760,20 @@ class ConnectionManager {
|
|
|
681
760
|
if (!this.currentToken) {
|
|
682
761
|
return undefined;
|
|
683
762
|
}
|
|
684
|
-
if (this.currentToken
|
|
763
|
+
if (this.currentToken
|
|
764
|
+
&& !this.isTokenExpired()
|
|
765
|
+
&& this.tokenCovers(normalizedRequiredScopes)) {
|
|
685
766
|
return this.currentToken;
|
|
686
767
|
}
|
|
687
|
-
throw new AreteError(`Authentication token was not granted required scopes: ${
|
|
768
|
+
throw new AreteError(`Authentication token was not granted required scopes: ${normalizedRequiredScopes.join(', ')}`, 'AUTH_REQUIRED');
|
|
688
769
|
}
|
|
689
770
|
let request;
|
|
690
|
-
request = Promise.resolve().then(() => this.fetchAuthToken(
|
|
771
|
+
request = Promise.resolve().then(() => this.fetchAuthToken(normalizeScopes([...this.tokenScopes, ...this.requestedScopes]), () => this.tokenRequestInFlight === request));
|
|
691
772
|
this.tokenRequestInFlight = request;
|
|
692
773
|
try {
|
|
693
774
|
const token = await request;
|
|
694
|
-
if (token && !this.tokenCovers(
|
|
695
|
-
throw new AreteError(`Authentication token was not granted required scopes: ${
|
|
775
|
+
if (token && !this.tokenCovers(normalizedRequiredScopes)) {
|
|
776
|
+
throw new AreteError(`Authentication token was not granted required scopes: ${normalizedRequiredScopes.join(', ')}`, 'AUTH_REQUIRED');
|
|
696
777
|
}
|
|
697
778
|
return token;
|
|
698
779
|
}
|
|
@@ -703,27 +784,27 @@ class ConnectionManager {
|
|
|
703
784
|
}
|
|
704
785
|
}
|
|
705
786
|
async fetchAuthToken(scopes, isCurrent) {
|
|
787
|
+
const request = { scopes: normalizeScopes(scopes) };
|
|
788
|
+
const result = await this.requestAuthToken(request, isCurrent);
|
|
789
|
+
return result === undefined ? undefined : this.updateTokenState(result, request.scopes);
|
|
790
|
+
}
|
|
791
|
+
async requestAuthToken(request, isCurrent) {
|
|
706
792
|
const strategy = this.getAuthStrategy();
|
|
707
793
|
const requireCurrent = () => {
|
|
708
794
|
if (!isCurrent()) {
|
|
709
795
|
throw new AreteError('Authentication request was superseded', 'CONNECTION_CANCELLED');
|
|
710
796
|
}
|
|
711
797
|
};
|
|
712
|
-
// For hosted Arete URLs, auth is required - fail early with clear message
|
|
713
|
-
if (strategy.kind === 'none' && this.hostedAreteUrl) {
|
|
714
|
-
throw new AreteError('Arete authentication required. Please provide auth.publishableKey to AreteProvider. ' +
|
|
715
|
-
'Get your key from https://arete.run/dashboard', 'AUTH_REQUIRED');
|
|
716
|
-
}
|
|
717
798
|
switch (strategy.kind) {
|
|
718
799
|
case 'static-token': {
|
|
719
800
|
requireCurrent();
|
|
720
|
-
return
|
|
801
|
+
return strategy.token;
|
|
721
802
|
}
|
|
722
803
|
case 'token-provider':
|
|
723
804
|
try {
|
|
724
|
-
const result = await strategy.getToken(
|
|
805
|
+
const result = await strategy.getToken(request);
|
|
725
806
|
requireCurrent();
|
|
726
|
-
return
|
|
807
|
+
return result;
|
|
727
808
|
}
|
|
728
809
|
catch (error) {
|
|
729
810
|
if (error instanceof AreteError) {
|
|
@@ -733,9 +814,9 @@ class ConnectionManager {
|
|
|
733
814
|
}
|
|
734
815
|
case 'token-endpoint':
|
|
735
816
|
try {
|
|
736
|
-
const result = await this.fetchTokenFromEndpoint(strategy.endpoint,
|
|
817
|
+
const result = await this.fetchTokenFromEndpoint(strategy.endpoint, request);
|
|
737
818
|
requireCurrent();
|
|
738
|
-
return
|
|
819
|
+
return result;
|
|
739
820
|
}
|
|
740
821
|
catch (error) {
|
|
741
822
|
if (error instanceof AreteError) {
|
|
@@ -748,14 +829,97 @@ class ConnectionManager {
|
|
|
748
829
|
return undefined;
|
|
749
830
|
}
|
|
750
831
|
}
|
|
751
|
-
|
|
832
|
+
updateHttpTokenState(state, result, request) {
|
|
833
|
+
const normalized = normalizeTokenResult(result);
|
|
834
|
+
if (!normalized.token) {
|
|
835
|
+
throw new AreteError('Authentication provider returned an empty token', 'TOKEN_INVALID');
|
|
836
|
+
}
|
|
837
|
+
const grantedScopes = new Set(normalized.scopes ?? request.scopes);
|
|
838
|
+
if (!request.scopes.every((scope) => grantedScopes.has(scope))) {
|
|
839
|
+
throw new AreteError(`Authentication token was not granted required scopes: ${request.scopes.join(', ')}`, 'AUTH_REQUIRED');
|
|
840
|
+
}
|
|
841
|
+
const expiresAt = normalizeExpiryTimestamp(normalized.expiresAt, normalized.expires_at)
|
|
842
|
+
?? parseJwtExpiry(normalized.token);
|
|
843
|
+
if (this.isExpiryExpired(expiresAt)) {
|
|
844
|
+
throw new AreteError('Authentication token is expired', 'TOKEN_EXPIRED');
|
|
845
|
+
}
|
|
846
|
+
state.token = normalized.token;
|
|
847
|
+
state.expiresAt = expiresAt;
|
|
848
|
+
return normalized.token;
|
|
849
|
+
}
|
|
850
|
+
async getTargetedHttpAuthToken(request, forceRefresh) {
|
|
851
|
+
const identity = createTargetedHttpTokenIdentity(request);
|
|
852
|
+
let state = this.httpAuthTokenStates.get(identity);
|
|
853
|
+
if (!forceRefresh && state?.token && !this.isExpiryExpired(state.expiresAt)) {
|
|
854
|
+
this.touchHttpAuthTokenState(identity, state);
|
|
855
|
+
return state.token;
|
|
856
|
+
}
|
|
857
|
+
if (state?.inFlight) {
|
|
858
|
+
this.touchHttpAuthTokenState(identity, state);
|
|
859
|
+
return state.inFlight;
|
|
860
|
+
}
|
|
861
|
+
if (!state) {
|
|
862
|
+
state = {};
|
|
863
|
+
this.httpAuthTokenStates.set(identity, state);
|
|
864
|
+
}
|
|
865
|
+
const tokenState = state;
|
|
866
|
+
let inFlight;
|
|
867
|
+
inFlight = Promise.resolve().then(async () => {
|
|
868
|
+
const result = await this.requestAuthToken(request, () => this.httpAuthTokenStates.get(identity) === tokenState
|
|
869
|
+
&& tokenState.inFlight === inFlight);
|
|
870
|
+
return result === undefined
|
|
871
|
+
? undefined
|
|
872
|
+
: this.updateHttpTokenState(tokenState, result, request);
|
|
873
|
+
});
|
|
874
|
+
tokenState.inFlight = inFlight;
|
|
875
|
+
this.touchHttpAuthTokenState(identity, tokenState);
|
|
876
|
+
this.trimHttpAuthTokenStates();
|
|
877
|
+
try {
|
|
878
|
+
return await inFlight;
|
|
879
|
+
}
|
|
880
|
+
finally {
|
|
881
|
+
if (this.httpAuthTokenStates.get(identity) === tokenState
|
|
882
|
+
&& tokenState.inFlight === inFlight) {
|
|
883
|
+
tokenState.inFlight = undefined;
|
|
884
|
+
}
|
|
885
|
+
this.trimHttpAuthTokenStates();
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
touchHttpAuthTokenState(identity, state) {
|
|
889
|
+
this.httpAuthTokenStates.delete(identity);
|
|
890
|
+
this.httpAuthTokenStates.set(identity, state);
|
|
891
|
+
}
|
|
892
|
+
trimHttpAuthTokenStates() {
|
|
893
|
+
if (this.httpAuthTokenStates.size <= MAX_HTTP_AUTH_TOKEN_STATES) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
for (const [identity, state] of this.httpAuthTokenStates) {
|
|
897
|
+
if (!state.inFlight) {
|
|
898
|
+
this.httpAuthTokenStates.delete(identity);
|
|
899
|
+
}
|
|
900
|
+
if (this.httpAuthTokenStates.size <= MAX_HTTP_AUTH_TOKEN_STATES) {
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
createTokenEndpointRequestBody(request) {
|
|
906
|
+
if (hasAuthTokenTarget(request)) {
|
|
907
|
+
const target = {
|
|
908
|
+
targetKind: request.targetKind,
|
|
909
|
+
targetId: request.targetId,
|
|
910
|
+
scopes: request.scopes,
|
|
911
|
+
};
|
|
912
|
+
return isProgramReadBindingAuthTarget(request)
|
|
913
|
+
? { ...target, programReleaseHash: request.programReleaseHash }
|
|
914
|
+
: target;
|
|
915
|
+
}
|
|
752
916
|
return {
|
|
753
917
|
websocket_url: this.websocketUrl ?? '',
|
|
754
|
-
scopes,
|
|
918
|
+
scopes: request.scopes,
|
|
755
919
|
};
|
|
756
920
|
}
|
|
757
|
-
async fetchTokenFromEndpoint(tokenEndpoint,
|
|
758
|
-
const response = await
|
|
921
|
+
async fetchTokenFromEndpoint(tokenEndpoint, request) {
|
|
922
|
+
const response = await this.authFetch(tokenEndpoint, {
|
|
759
923
|
method: 'POST',
|
|
760
924
|
headers: {
|
|
761
925
|
...(this.authConfig?.publishableKey
|
|
@@ -765,7 +929,7 @@ class ConnectionManager {
|
|
|
765
929
|
'Content-Type': 'application/json',
|
|
766
930
|
},
|
|
767
931
|
credentials: this.authConfig?.tokenEndpointCredentials,
|
|
768
|
-
body: JSON.stringify(this.createTokenEndpointRequestBody(
|
|
932
|
+
body: JSON.stringify(this.createTokenEndpointRequestBody(request)),
|
|
769
933
|
});
|
|
770
934
|
if (!response.ok) {
|
|
771
935
|
const rawError = await response.text();
|
|
@@ -800,11 +964,14 @@ class ConnectionManager {
|
|
|
800
964
|
}
|
|
801
965
|
return data;
|
|
802
966
|
}
|
|
803
|
-
|
|
804
|
-
if (!
|
|
967
|
+
isExpiryExpired(expiresAt) {
|
|
968
|
+
if (!expiresAt) {
|
|
805
969
|
return false;
|
|
806
970
|
}
|
|
807
|
-
return Date.now() >= (
|
|
971
|
+
return Date.now() >= (expiresAt - TOKEN_REFRESH_BUFFER_SECONDS) * 1000;
|
|
972
|
+
}
|
|
973
|
+
isTokenExpired() {
|
|
974
|
+
return this.isExpiryExpired(this.tokenExpiry);
|
|
808
975
|
}
|
|
809
976
|
scheduleTokenRefresh() {
|
|
810
977
|
this.clearTokenRefreshTimeout();
|
|
@@ -952,17 +1119,75 @@ class ConnectionManager {
|
|
|
952
1119
|
getState() {
|
|
953
1120
|
return this.currentState;
|
|
954
1121
|
}
|
|
955
|
-
async getHttpAuthToken(
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1122
|
+
async getHttpAuthToken(requestOrScopesOrTargetOrForce = ['read'], scopesTargetOrForce = false, targetOrForce = false) {
|
|
1123
|
+
let request;
|
|
1124
|
+
let force = false;
|
|
1125
|
+
if (typeof requestOrScopesOrTargetOrForce === 'boolean') {
|
|
1126
|
+
request = { scopes: ['read'] };
|
|
1127
|
+
force = requestOrScopesOrTargetOrForce;
|
|
1128
|
+
}
|
|
1129
|
+
else if (Array.isArray(requestOrScopesOrTargetOrForce)) {
|
|
1130
|
+
if (isAuthTokenTarget(scopesTargetOrForce)) {
|
|
1131
|
+
request = {
|
|
1132
|
+
scopes: requestOrScopesOrTargetOrForce,
|
|
1133
|
+
...scopesTargetOrForce,
|
|
1134
|
+
};
|
|
1135
|
+
force = typeof targetOrForce === 'boolean' ? targetOrForce : false;
|
|
1136
|
+
}
|
|
1137
|
+
else if (isAuthTokenTarget(targetOrForce)) {
|
|
1138
|
+
request = {
|
|
1139
|
+
scopes: requestOrScopesOrTargetOrForce,
|
|
1140
|
+
...targetOrForce,
|
|
1141
|
+
};
|
|
1142
|
+
force = typeof scopesTargetOrForce === 'boolean' ? scopesTargetOrForce : false;
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1145
|
+
request = { scopes: requestOrScopesOrTargetOrForce };
|
|
1146
|
+
force = typeof scopesTargetOrForce === 'boolean' ? scopesTargetOrForce : false;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
else if (isAuthTokenRequest(requestOrScopesOrTargetOrForce)) {
|
|
1150
|
+
request = requestOrScopesOrTargetOrForce;
|
|
1151
|
+
force = typeof scopesTargetOrForce === 'boolean' ? scopesTargetOrForce : false;
|
|
1152
|
+
}
|
|
1153
|
+
else if (isAuthTokenTarget(requestOrScopesOrTargetOrForce)) {
|
|
1154
|
+
const requiredScopes = Array.isArray(scopesTargetOrForce)
|
|
1155
|
+
? scopesTargetOrForce
|
|
1156
|
+
: ['read'];
|
|
1157
|
+
request = {
|
|
1158
|
+
scopes: requiredScopes,
|
|
1159
|
+
...requestOrScopesOrTargetOrForce,
|
|
1160
|
+
};
|
|
1161
|
+
force = typeof scopesTargetOrForce === 'boolean'
|
|
1162
|
+
? scopesTargetOrForce
|
|
1163
|
+
: typeof targetOrForce === 'boolean' ? targetOrForce : false;
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
throw new AreteError('Invalid HTTP authentication token request', 'INVALID_CONFIG');
|
|
1167
|
+
}
|
|
1168
|
+
const normalizedRequest = normalizeAuthTokenRequest(request);
|
|
1169
|
+
return hasAuthTokenTarget(normalizedRequest)
|
|
1170
|
+
? this.getTargetedHttpAuthToken(normalizedRequest, force)
|
|
1171
|
+
: this.getOrRefreshToken(force, normalizedRequest.scopes);
|
|
963
1172
|
}
|
|
964
|
-
clearHttpAuthToken() {
|
|
965
|
-
|
|
1173
|
+
clearHttpAuthToken(identity, requiredScopes = ['read']) {
|
|
1174
|
+
if (identity === undefined) {
|
|
1175
|
+
this.clearTokenState();
|
|
1176
|
+
this.httpAuthTokenStates.clear();
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const request = normalizeAuthTokenRequest(isAuthTokenRequest(identity)
|
|
1180
|
+
? identity
|
|
1181
|
+
: {
|
|
1182
|
+
scopes: requiredScopes,
|
|
1183
|
+
...identity,
|
|
1184
|
+
});
|
|
1185
|
+
if (hasAuthTokenTarget(request)) {
|
|
1186
|
+
this.httpAuthTokenStates.delete(createTargetedHttpTokenIdentity(request));
|
|
1187
|
+
}
|
|
1188
|
+
else {
|
|
1189
|
+
this.clearTokenState();
|
|
1190
|
+
}
|
|
966
1191
|
}
|
|
967
1192
|
onFrame(handler) {
|
|
968
1193
|
this.frameHandlers.add(handler);
|
|
@@ -2930,7 +3155,6 @@ async function parseReadResponse(response, path) {
|
|
|
2930
3155
|
function programAccountRead(input) {
|
|
2931
3156
|
return {
|
|
2932
3157
|
account: input.account,
|
|
2933
|
-
path: input.path,
|
|
2934
3158
|
schema: input.schema,
|
|
2935
3159
|
};
|
|
2936
3160
|
}
|
|
@@ -5029,7 +5253,7 @@ function mergeOperations(base, extension) {
|
|
|
5029
5253
|
function extendProgram(program, extensions) {
|
|
5030
5254
|
const base = program;
|
|
5031
5255
|
const extended = { ...program };
|
|
5032
|
-
delete extended.
|
|
5256
|
+
delete extended.sdkDefinitionHash;
|
|
5033
5257
|
for (const key of ['pdas', 'accounts', 'queries', 'addresses', 'constants', 'defaults', 'math']) {
|
|
5034
5258
|
const extensionValue = extensions[key];
|
|
5035
5259
|
if (extensionValue !== undefined) {
|
|
@@ -5594,6 +5818,131 @@ function formatPreparedOperation(operation) {
|
|
|
5594
5818
|
return lines.join('\n');
|
|
5595
5819
|
}
|
|
5596
5820
|
|
|
5821
|
+
function appendUrl(baseUrl, path) {
|
|
5822
|
+
return `${baseUrl.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
|
|
5823
|
+
}
|
|
5824
|
+
function responseErrorCode(response, body) {
|
|
5825
|
+
const headerCode = response.headers.get('X-Error-Code');
|
|
5826
|
+
if (headerCode)
|
|
5827
|
+
return headerCode;
|
|
5828
|
+
try {
|
|
5829
|
+
const parsed = JSON.parse(body);
|
|
5830
|
+
if (typeof parsed.error?.code === 'string')
|
|
5831
|
+
return parsed.error.code;
|
|
5832
|
+
return typeof parsed.code === 'string' ? parsed.code : undefined;
|
|
5833
|
+
}
|
|
5834
|
+
catch {
|
|
5835
|
+
return undefined;
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5838
|
+
function isRefreshableErrorCode(code) {
|
|
5839
|
+
return shouldRefreshToken(parseErrorCode(code.trim().replace(/_/g, '-')));
|
|
5840
|
+
}
|
|
5841
|
+
function parseProgramReadResponse(response, path, body) {
|
|
5842
|
+
if (!response.ok) {
|
|
5843
|
+
throw new ReadRequestError({
|
|
5844
|
+
status: response.status,
|
|
5845
|
+
path,
|
|
5846
|
+
body,
|
|
5847
|
+
serverErrorCode: responseErrorCode(response, body),
|
|
5848
|
+
});
|
|
5849
|
+
}
|
|
5850
|
+
try {
|
|
5851
|
+
return JSON.parse(body);
|
|
5852
|
+
}
|
|
5853
|
+
catch (error) {
|
|
5854
|
+
throw new AreteError(`Program read '${path}' returned invalid JSON`, 'INVALID_RESPONSE', error);
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
function hostedAuthConfig(binding, runtimeAuth) {
|
|
5858
|
+
const metadata = binding.auth;
|
|
5859
|
+
const runtimeStrategyConfigured = runtimeAuth?.token !== undefined
|
|
5860
|
+
|| runtimeAuth?.getToken !== undefined
|
|
5861
|
+
|| runtimeAuth?.tokenEndpoint !== undefined;
|
|
5862
|
+
if (runtimeStrategyConfigured)
|
|
5863
|
+
return runtimeAuth;
|
|
5864
|
+
if (metadata.required === false)
|
|
5865
|
+
return runtimeAuth;
|
|
5866
|
+
return { ...runtimeAuth, tokenEndpoint: metadata.sessionEndpoint };
|
|
5867
|
+
}
|
|
5868
|
+
function hostedAuth(config) {
|
|
5869
|
+
const auth = hostedAuthConfig(config.binding, config.auth);
|
|
5870
|
+
if (auth?.token !== undefined && !auth.token) {
|
|
5871
|
+
throw new AreteError('Authentication token is empty', 'TOKEN_INVALID');
|
|
5872
|
+
}
|
|
5873
|
+
const bindingId = config.binding.programReadBindingId;
|
|
5874
|
+
if (!auth || auth.token !== undefined) {
|
|
5875
|
+
return { staticToken: auth?.token };
|
|
5876
|
+
}
|
|
5877
|
+
return {
|
|
5878
|
+
manager: new ConnectionManager({ websocketUrl: null, auth, fetch: config.fetch }),
|
|
5879
|
+
target: {
|
|
5880
|
+
targetKind: 'program-read-binding',
|
|
5881
|
+
targetId: bindingId,
|
|
5882
|
+
programReleaseHash: config.release.programReleaseHash,
|
|
5883
|
+
},
|
|
5884
|
+
};
|
|
5885
|
+
}
|
|
5886
|
+
function requestPath(release, request) {
|
|
5887
|
+
const releaseHash = encodeURIComponent(release.programReleaseHash).replace(/%3A/g, ':');
|
|
5888
|
+
const root = `/v1/releases/${releaseHash}`
|
|
5889
|
+
+ `/accounts/${encodeURIComponent(request.account)}`;
|
|
5890
|
+
if (request.operation === 'fetchMany')
|
|
5891
|
+
return root;
|
|
5892
|
+
const addressPath = `${root.replace(/\/+$/, '')}/${encodeURIComponent(request.address)}`;
|
|
5893
|
+
return request.operation === 'exists' ? `${addressPath}/exists` : addressPath;
|
|
5894
|
+
}
|
|
5895
|
+
function createProgramReadTransport(config) {
|
|
5896
|
+
if (config.kind === 'unavailable') {
|
|
5897
|
+
return {
|
|
5898
|
+
async read() {
|
|
5899
|
+
throw new AreteError(config.message, 'INVALID_CONFIG');
|
|
5900
|
+
},
|
|
5901
|
+
};
|
|
5902
|
+
}
|
|
5903
|
+
const auth = config.kind === 'hosted-binding' ? hostedAuth(config) : undefined;
|
|
5904
|
+
const endpoint = config.kind === 'hosted-binding'
|
|
5905
|
+
? config.binding.endpoint
|
|
5906
|
+
: config.endpoint;
|
|
5907
|
+
return {
|
|
5908
|
+
async read(request) {
|
|
5909
|
+
const path = requestPath(config.release, request);
|
|
5910
|
+
const input = appendUrl(endpoint, path);
|
|
5911
|
+
const attempt = async (forceRefresh) => {
|
|
5912
|
+
const token = auth?.staticToken
|
|
5913
|
+
?? (auth?.manager && auth.target
|
|
5914
|
+
? await auth.manager.getHttpAuthToken(auth.target, ['read'], forceRefresh)
|
|
5915
|
+
: undefined);
|
|
5916
|
+
const headers = new Headers(request.operation === 'fetchMany'
|
|
5917
|
+
? { 'content-type': 'application/json' }
|
|
5918
|
+
: undefined);
|
|
5919
|
+
if (token)
|
|
5920
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
5921
|
+
return config.fetch(input, {
|
|
5922
|
+
method: request.operation === 'fetchMany' ? 'POST' : 'GET',
|
|
5923
|
+
headers,
|
|
5924
|
+
body: request.operation === 'fetchMany'
|
|
5925
|
+
? JSON.stringify({ addresses: request.addresses })
|
|
5926
|
+
: undefined,
|
|
5927
|
+
});
|
|
5928
|
+
};
|
|
5929
|
+
let response = await attempt(false);
|
|
5930
|
+
let body = await response.text();
|
|
5931
|
+
const wireCode = responseErrorCode(response, body);
|
|
5932
|
+
if (response.status === 401
|
|
5933
|
+
&& wireCode
|
|
5934
|
+
&& isRefreshableErrorCode(wireCode)
|
|
5935
|
+
&& auth?.manager
|
|
5936
|
+
&& auth.target) {
|
|
5937
|
+
auth.manager.clearHttpAuthToken(auth.target);
|
|
5938
|
+
response = await attempt(true);
|
|
5939
|
+
body = await response.text();
|
|
5940
|
+
}
|
|
5941
|
+
return parseProgramReadResponse(response, path, body);
|
|
5942
|
+
},
|
|
5943
|
+
};
|
|
5944
|
+
}
|
|
5945
|
+
|
|
5597
5946
|
class TransactionTransportError extends Error {
|
|
5598
5947
|
constructor(status, body) {
|
|
5599
5948
|
super(body.message);
|
|
@@ -5726,6 +6075,108 @@ function createTransactionTransport(baseUrl, authenticatedFetch) {
|
|
|
5726
6075
|
};
|
|
5727
6076
|
}
|
|
5728
6077
|
|
|
6078
|
+
function effectiveProgramRead(stack, name, override) {
|
|
6079
|
+
return override ?? stack.programReads?.[name];
|
|
6080
|
+
}
|
|
6081
|
+
function validateReleaseIdentity(programName, definition, release) {
|
|
6082
|
+
if (release
|
|
6083
|
+
&& definition.programSpecHash
|
|
6084
|
+
&& release.programSpecHash !== definition.programSpecHash) {
|
|
6085
|
+
throw new AreteError(`Program '${programName}' release programSpecHash '${release.programSpecHash}' does not match definition programSpecHash '${definition.programSpecHash}'`, 'PROGRAM_RELEASE_MISMATCH');
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
function isNonEmptyString(value) {
|
|
6089
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
6090
|
+
}
|
|
6091
|
+
function isSecureOrLoopbackHttpUrl$1(value) {
|
|
6092
|
+
try {
|
|
6093
|
+
const url = new URL(value);
|
|
6094
|
+
return url.protocol === 'https:'
|
|
6095
|
+
|| (url.protocol === 'http:'
|
|
6096
|
+
&& ['localhost', '127.0.0.1', '::1'].includes(url.hostname));
|
|
6097
|
+
}
|
|
6098
|
+
catch {
|
|
6099
|
+
return false;
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
6102
|
+
function validateProgramReadDescriptor(programName, descriptor) {
|
|
6103
|
+
if (!descriptor.release
|
|
6104
|
+
|| !isNonEmptyString(descriptor.release.programReleaseHash)
|
|
6105
|
+
|| !isNonEmptyString(descriptor.release.programSpecHash)) {
|
|
6106
|
+
throw new AreteError(`Program '${programName}' read descriptor requires a complete release`, 'INVALID_CONFIG');
|
|
6107
|
+
}
|
|
6108
|
+
if (!descriptor.transport || typeof descriptor.transport !== 'object') {
|
|
6109
|
+
throw new AreteError(`Program '${programName}' read descriptor requires a transport`, 'INVALID_CONFIG');
|
|
6110
|
+
}
|
|
6111
|
+
if (descriptor.transport.kind === 'local-http') {
|
|
6112
|
+
if (descriptor.transport.endpointSource !== 'connect-http-url') {
|
|
6113
|
+
throw new AreteError(`Program '${programName}' local HTTP transport must use endpointSource 'connect-http-url'`, 'INVALID_CONFIG');
|
|
6114
|
+
}
|
|
6115
|
+
return;
|
|
6116
|
+
}
|
|
6117
|
+
if (descriptor.transport.kind !== 'hosted-binding') {
|
|
6118
|
+
throw new AreteError(`Program '${programName}' read descriptor has an unsupported transport`, 'INVALID_CONFIG');
|
|
6119
|
+
}
|
|
6120
|
+
const { binding } = descriptor.transport;
|
|
6121
|
+
if (!binding
|
|
6122
|
+
|| !isSecureOrLoopbackHttpUrl$1(binding.endpoint)
|
|
6123
|
+
|| !/^prb_[A-Za-z0-9_-]{32}$/.test(binding.programReadBindingId)
|
|
6124
|
+
|| !binding.auth
|
|
6125
|
+
|| binding.auth.targetKind !== 'program-read-binding'
|
|
6126
|
+
|| binding.auth.targetId !== binding.programReadBindingId
|
|
6127
|
+
|| !isSecureOrLoopbackHttpUrl$1(binding.auth.sessionEndpoint)) {
|
|
6128
|
+
throw new AreteError(`Program '${programName}' hosted binding requires secure endpoints, a canonical binding ID, and matching program-read-binding auth metadata`, 'INVALID_CONFIG');
|
|
6129
|
+
}
|
|
6130
|
+
}
|
|
6131
|
+
function validateProgramReads(stack, overrides, validateDescriptorKeys = true) {
|
|
6132
|
+
const programKeys = Object.keys(stack.programs ?? {});
|
|
6133
|
+
if (validateDescriptorKeys && stack.programReads) {
|
|
6134
|
+
const readKeys = Object.keys(stack.programReads);
|
|
6135
|
+
const missing = programKeys.filter((key) => !readKeys.includes(key));
|
|
6136
|
+
const extra = readKeys.filter((key) => !programKeys.includes(key));
|
|
6137
|
+
if (missing.length > 0 || extra.length > 0) {
|
|
6138
|
+
throw new AreteError(`Stack '${stack.name}' programReads keys must exactly match programs`
|
|
6139
|
+
+ `${missing.length > 0 ? `; missing: ${missing.join(', ')}` : ''}`
|
|
6140
|
+
+ `${extra.length > 0 ? `; unknown: ${extra.join(', ')}` : ''}`, 'INVALID_CONFIG');
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
for (const [name, descriptor] of Object.entries(stack.programReads ?? {})) {
|
|
6144
|
+
validateProgramReadDescriptor(name, descriptor);
|
|
6145
|
+
}
|
|
6146
|
+
for (const key of Object.keys(overrides ?? {})) {
|
|
6147
|
+
if (!programKeys.includes(key)) {
|
|
6148
|
+
throw new AreteError(`Program read override '${key}' does not match a program in stack '${stack.name}'`, 'INVALID_CONFIG');
|
|
6149
|
+
}
|
|
6150
|
+
validateProgramReadDescriptor(key, overrides[key]);
|
|
6151
|
+
}
|
|
6152
|
+
for (const [name, definition] of Object.entries(stack.programs ?? {})) {
|
|
6153
|
+
validateReleaseIdentity(name, definition, stack.programReads?.[name]?.release);
|
|
6154
|
+
validateReleaseIdentity(name, definition, overrides?.[name]?.release);
|
|
6155
|
+
}
|
|
6156
|
+
}
|
|
6157
|
+
function hasCompleteIndependentProgramReads(stack, overrides, connectHttpUrl) {
|
|
6158
|
+
const programs = Object.entries(stack.programs ?? {});
|
|
6159
|
+
const readablePrograms = programs
|
|
6160
|
+
.filter(([, definition]) => hasProgramAccountReads(definition));
|
|
6161
|
+
return programs.length > 0 && readablePrograms.every(([name]) => {
|
|
6162
|
+
const read = effectiveProgramRead(stack, name, overrides?.[name]);
|
|
6163
|
+
return read?.transport.kind === 'hosted-binding'
|
|
6164
|
+
|| (read?.transport.kind === 'local-http' && isNonEmptyString(connectHttpUrl));
|
|
6165
|
+
});
|
|
6166
|
+
}
|
|
6167
|
+
function validateLocalProgramReadEndpoints(stack, overrides, connectHttpUrl) {
|
|
6168
|
+
for (const [name, definition] of Object.entries(stack.programs ?? {})) {
|
|
6169
|
+
if (!hasProgramAccountReads(definition))
|
|
6170
|
+
continue;
|
|
6171
|
+
const descriptor = effectiveProgramRead(stack, name, overrides?.[name]);
|
|
6172
|
+
if (descriptor?.transport.kind === 'local-http' && !isNonEmptyString(connectHttpUrl)) {
|
|
6173
|
+
throw new AreteError(`Program '${name}' local HTTP transport requires ConnectOptions.httpUrl`, 'INVALID_CONFIG');
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
function hasProgramAccountReads(definition) {
|
|
6178
|
+
return Object.keys(definition.accounts ?? {}).length > 0;
|
|
6179
|
+
}
|
|
5729
6180
|
function mergeAttachedPrograms(stack, attachedPrograms) {
|
|
5730
6181
|
const merged = { ...(attachedPrograms ?? {}) };
|
|
5731
6182
|
for (const [name, definition] of Object.entries(stack.programs ?? {})) {
|
|
@@ -5795,11 +6246,14 @@ function composeExecutionCallbacks(defaultCallback, callCallback) {
|
|
|
5795
6246
|
};
|
|
5796
6247
|
}
|
|
5797
6248
|
let Arete$1 = class Arete {
|
|
5798
|
-
constructor(url, httpBaseUrl, options) {
|
|
6249
|
+
constructor(url, httpBaseUrl, connectHttpUrl, programReadOverrides, options) {
|
|
5799
6250
|
this.stack = options.stack;
|
|
5800
6251
|
this._wallet = options.wallet;
|
|
5801
6252
|
this.executionDefaults = options.execution;
|
|
5802
6253
|
this.httpBaseUrl = httpBaseUrl;
|
|
6254
|
+
this.connectHttpUrl = connectHttpUrl;
|
|
6255
|
+
this.programReadOverrides = programReadOverrides;
|
|
6256
|
+
this.auth = options.auth;
|
|
5803
6257
|
this.fetchImpl = options.fetch ?? this.resolveFetchImpl();
|
|
5804
6258
|
this.storage = new SortedStorageDecorator(options.storage ?? new MemoryAdapter());
|
|
5805
6259
|
this.queryStore = new QueryStore(this.storage);
|
|
@@ -5818,6 +6272,7 @@ let Arete$1 = class Arete {
|
|
|
5818
6272
|
reconnectIntervals: options.reconnectIntervals,
|
|
5819
6273
|
maxReconnectAttempts: options.maxReconnectAttempts,
|
|
5820
6274
|
auth: options.auth,
|
|
6275
|
+
fetch: this.fetchImpl,
|
|
5821
6276
|
});
|
|
5822
6277
|
this.subscriptionRegistry = new SubscriptionRegistry(this.connection, this.queryStore);
|
|
5823
6278
|
this.connection.onFrame((frame) => {
|
|
@@ -5828,12 +6283,12 @@ let Arete$1 = class Arete {
|
|
|
5828
6283
|
});
|
|
5829
6284
|
this._views = createTypedViews(this.stack, this.storage, this.subscriptionRegistry);
|
|
5830
6285
|
this._queries = this.buildQueries();
|
|
5831
|
-
this._chain = createChainClient(this.httpBaseUrl, ((input, init) => this.
|
|
6286
|
+
this._chain = options.chain ?? createChainClient(this.httpBaseUrl ?? '', ((input, init) => this.authenticatedStackFetch(typeof input === 'string'
|
|
5832
6287
|
? input
|
|
5833
6288
|
: input instanceof URL
|
|
5834
6289
|
? input.toString()
|
|
5835
6290
|
: input.url, init, ['read'])));
|
|
5836
|
-
this._transactions = createTransactionTransport(this.httpBaseUrl, (input, init, scope, requirePreDispatchMarker) => this.
|
|
6291
|
+
this._transactions = options.transactions ?? createTransactionTransport(this.httpBaseUrl ?? '', (input, init, scope, requirePreDispatchMarker) => this.authenticatedStackFetch(input, init, [scope], requirePreDispatchMarker));
|
|
5837
6292
|
this._programs = this.buildPrograms();
|
|
5838
6293
|
}
|
|
5839
6294
|
resolveFetchImpl() {
|
|
@@ -5852,13 +6307,14 @@ let Arete$1 = class Arete {
|
|
|
5852
6307
|
buildPrograms() {
|
|
5853
6308
|
const bases = {};
|
|
5854
6309
|
for (const [name, definition] of Object.entries(this.stack.programs ?? {})) {
|
|
6310
|
+
const transport = this.createProgramTransport(name, definition);
|
|
5855
6311
|
const instructions = {};
|
|
5856
6312
|
for (const [instructionName, handler] of Object.entries(definition.rawInstructions ?? {})) {
|
|
5857
6313
|
instructions[instructionName] = this.createTypedInstruction(handler);
|
|
5858
6314
|
}
|
|
5859
6315
|
const accounts = {};
|
|
5860
6316
|
for (const [accountName, accountDefinition] of Object.entries(definition.accounts ?? {})) {
|
|
5861
|
-
accounts[accountName] = this.createAccountReader(accountDefinition);
|
|
6317
|
+
accounts[accountName] = this.createAccountReader(accountDefinition, transport);
|
|
5862
6318
|
}
|
|
5863
6319
|
const queries = {};
|
|
5864
6320
|
for (const [queryName, queryDefinition] of Object.entries(definition.queries ?? {})) {
|
|
@@ -5908,21 +6364,65 @@ let Arete$1 = class Arete {
|
|
|
5908
6364
|
build: (params, options) => buildInstruction(handler, params, this.withWallet(options)),
|
|
5909
6365
|
};
|
|
5910
6366
|
}
|
|
5911
|
-
|
|
6367
|
+
createProgramTransport(name, definition) {
|
|
6368
|
+
if (!hasProgramAccountReads(definition)) {
|
|
6369
|
+
return createProgramReadTransport({
|
|
6370
|
+
kind: 'unavailable',
|
|
6371
|
+
message: `Program '${name}' has no generated account readers`,
|
|
6372
|
+
});
|
|
6373
|
+
}
|
|
6374
|
+
const descriptor = effectiveProgramRead(this.stack, name, this.programReadOverrides[name]);
|
|
6375
|
+
validateReleaseIdentity(name, definition, descriptor?.release);
|
|
6376
|
+
if (descriptor?.transport.kind === 'local-http') {
|
|
6377
|
+
return createProgramReadTransport({
|
|
6378
|
+
kind: 'local-http',
|
|
6379
|
+
endpoint: this.connectHttpUrl,
|
|
6380
|
+
release: descriptor.release,
|
|
6381
|
+
fetch: this.fetchImpl,
|
|
6382
|
+
});
|
|
6383
|
+
}
|
|
6384
|
+
if (descriptor?.transport.kind === 'hosted-binding') {
|
|
6385
|
+
return createProgramReadTransport({
|
|
6386
|
+
kind: 'hosted-binding',
|
|
6387
|
+
release: descriptor.release,
|
|
6388
|
+
binding: descriptor.transport.binding,
|
|
6389
|
+
auth: this.auth,
|
|
6390
|
+
fetch: this.fetchImpl,
|
|
6391
|
+
});
|
|
6392
|
+
}
|
|
6393
|
+
return createProgramReadTransport({
|
|
6394
|
+
kind: 'unavailable',
|
|
6395
|
+
message: `Program '${name}' has no release-aware read descriptor`,
|
|
6396
|
+
});
|
|
6397
|
+
}
|
|
6398
|
+
createAccountReader(definition, transport) {
|
|
5912
6399
|
return {
|
|
5913
6400
|
fetch: async (address) => {
|
|
5914
|
-
const result = await
|
|
6401
|
+
const result = await transport.read({
|
|
6402
|
+
operation: 'fetch',
|
|
6403
|
+
account: definition.account,
|
|
6404
|
+
address,
|
|
6405
|
+
});
|
|
5915
6406
|
return result === null ? null : parseProgramAccountValue(definition, result);
|
|
5916
6407
|
},
|
|
5917
6408
|
fetchMany: async (addresses) => {
|
|
5918
|
-
const result = await
|
|
5919
|
-
|
|
5920
|
-
|
|
6409
|
+
const result = await transport.read({
|
|
6410
|
+
operation: 'fetchMany',
|
|
6411
|
+
account: definition.account,
|
|
6412
|
+
addresses,
|
|
5921
6413
|
});
|
|
5922
|
-
return
|
|
6414
|
+
return {
|
|
6415
|
+
items: result.items.map((item) => item.status === 'ok'
|
|
6416
|
+
? { ...item, value: parseProgramAccountValue(definition, item.value) }
|
|
6417
|
+
: item),
|
|
6418
|
+
};
|
|
5923
6419
|
},
|
|
5924
6420
|
exists: async (address) => {
|
|
5925
|
-
const result = await
|
|
6421
|
+
const result = await transport.read({
|
|
6422
|
+
operation: 'exists',
|
|
6423
|
+
account: definition.account,
|
|
6424
|
+
address,
|
|
6425
|
+
});
|
|
5926
6426
|
return result.exists;
|
|
5927
6427
|
},
|
|
5928
6428
|
};
|
|
@@ -5977,7 +6477,16 @@ let Arete$1 = class Arete {
|
|
|
5977
6477
|
}
|
|
5978
6478
|
return response;
|
|
5979
6479
|
}
|
|
6480
|
+
authenticatedStackFetch(input, init, requiredScopes = ['read'], requirePreDispatchMarker = false) {
|
|
6481
|
+
if (!this.httpBaseUrl) {
|
|
6482
|
+
throw new AreteError(`Stack '${this.stack.name}' has no HTTP endpoint; this operation is not a program account read`, 'INVALID_CONFIG');
|
|
6483
|
+
}
|
|
6484
|
+
return this.authenticatedFetch(input, init, requiredScopes, requirePreDispatchMarker);
|
|
6485
|
+
}
|
|
5980
6486
|
resolveReadUrl(path) {
|
|
6487
|
+
if (!this.httpBaseUrl) {
|
|
6488
|
+
throw new AreteError(`Stack '${this.stack.name}' has no HTTP endpoint; stack queries require httpUrl or endpoints.http`, 'INVALID_CONFIG');
|
|
6489
|
+
}
|
|
5981
6490
|
return `${this.httpBaseUrl.replace(/\/$/, '')}${path.startsWith('/') ? path : `/${path}`}`;
|
|
5982
6491
|
}
|
|
5983
6492
|
/** Merge the client's default wallet into call options (call options win). */
|
|
@@ -5989,21 +6498,35 @@ let Arete$1 = class Arete {
|
|
|
5989
6498
|
return merged;
|
|
5990
6499
|
}
|
|
5991
6500
|
static async connect(stack, options) {
|
|
6501
|
+
validateProgramReads(stack, undefined);
|
|
6502
|
+
const attachedPrograms = options?.programs;
|
|
6503
|
+
const effectiveStack = withPrograms(stack, attachedPrograms);
|
|
6504
|
+
const programReadOverrides = options?.programReads ?? {};
|
|
6505
|
+
validateProgramReads(effectiveStack, programReadOverrides, false);
|
|
5992
6506
|
const requestedUrl = options?.url ?? stack.endpoints.ws;
|
|
6507
|
+
const connectHttpUrl = options?.httpUrl;
|
|
6508
|
+
validateLocalProgramReadEndpoints(effectiveStack, programReadOverrides, connectHttpUrl);
|
|
5993
6509
|
const autoConnect = options?.autoConnect !== false;
|
|
5994
|
-
const
|
|
6510
|
+
const independentlyReadable = hasCompleteIndependentProgramReads(effectiveStack, programReadOverrides, connectHttpUrl);
|
|
6511
|
+
const implicitProgramOnlyHttp = options?.transport === undefined
|
|
6512
|
+
&& !requestedUrl
|
|
6513
|
+
&& independentlyReadable
|
|
6514
|
+
&& Object.keys(stack.views).length === 0;
|
|
6515
|
+
const httpOnly = options?.transport === 'http' || implicitProgramOnlyHttp;
|
|
5995
6516
|
const url = httpOnly ? null : requestedUrl;
|
|
5996
|
-
const
|
|
5997
|
-
|
|
5998
|
-
|
|
6517
|
+
const hasGeneratedHttp = Object.prototype.hasOwnProperty.call(stack.endpoints, 'http');
|
|
6518
|
+
let httpUrl = options?.httpUrl;
|
|
6519
|
+
if (httpUrl === undefined) {
|
|
6520
|
+
if (hasGeneratedHttp) {
|
|
6521
|
+
httpUrl = stack.endpoints.http;
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
5999
6524
|
if (!httpOnly && !url) {
|
|
6000
6525
|
throw new AreteError('WebSocket URL is required (provide url option or define endpoints.ws in stack)', 'INVALID_CONFIG');
|
|
6001
6526
|
}
|
|
6002
|
-
if (!httpUrl) {
|
|
6527
|
+
if (!httpUrl && !independentlyReadable) {
|
|
6003
6528
|
throw new AreteError('HTTP endpoint is required for transport: "http" (provide httpUrl option or define endpoints.http in stack)', 'INVALID_CONFIG');
|
|
6004
6529
|
}
|
|
6005
|
-
const attachedPrograms = options?.programs;
|
|
6006
|
-
const effectiveStack = withPrograms(stack, attachedPrograms);
|
|
6007
6530
|
const internalOptions = {
|
|
6008
6531
|
stack: effectiveStack,
|
|
6009
6532
|
httpUrl,
|
|
@@ -6020,8 +6543,10 @@ let Arete$1 = class Arete {
|
|
|
6020
6543
|
wallet: options?.wallet,
|
|
6021
6544
|
fetch: options?.fetch,
|
|
6022
6545
|
execution: options?.execution,
|
|
6546
|
+
chain: options?.chain,
|
|
6547
|
+
transactions: options?.transactions,
|
|
6023
6548
|
};
|
|
6024
|
-
const client = new Arete(url, httpUrl, internalOptions);
|
|
6549
|
+
const client = new Arete(url, httpUrl || undefined, connectHttpUrl, programReadOverrides, internalOptions);
|
|
6025
6550
|
if (!httpOnly && autoConnect) {
|
|
6026
6551
|
await client.connection.connect();
|
|
6027
6552
|
}
|
|
@@ -6232,12 +6757,22 @@ function createSignerRegistry(entries = []) {
|
|
|
6232
6757
|
};
|
|
6233
6758
|
}
|
|
6234
6759
|
|
|
6760
|
+
function mergeProgramReadOverrides(...layers) {
|
|
6761
|
+
return layers.reduce((selected, layer) => layer ?? selected, undefined);
|
|
6762
|
+
}
|
|
6763
|
+
function resolveSessionProgramReads(stack, member, options) {
|
|
6764
|
+
const reads = Object.fromEntries(Object.keys(stack.programs ?? {}).flatMap((name) => {
|
|
6765
|
+
const override = mergeProgramReadOverrides(options?.programRead, options?.programReads?.[name], member?.programRead, member?.programReads?.[name]);
|
|
6766
|
+
return override ? [[name, override]] : [];
|
|
6767
|
+
}));
|
|
6768
|
+
return Object.keys(reads).length > 0 ? reads : undefined;
|
|
6769
|
+
}
|
|
6235
6770
|
function resolveMemberConnectOptions(stack, member, options, forceHttpOnly) {
|
|
6236
6771
|
const transport = member?.transport ?? options?.transport ?? (forceHttpOnly ? 'http' : 'ws');
|
|
6237
|
-
//
|
|
6238
|
-
//
|
|
6772
|
+
// Stack endpoints remain on the stack definition. Only explicit session/member
|
|
6773
|
+
// HTTP options are forwarded as ConnectOptions.httpUrl for local Program Reads.
|
|
6239
6774
|
const url = member?.url ?? (stack.endpoints.ws || options?.endpoints?.ws);
|
|
6240
|
-
const httpUrl = member?.httpUrl ??
|
|
6775
|
+
const httpUrl = member?.httpUrl ?? options?.endpoints?.http;
|
|
6241
6776
|
return {
|
|
6242
6777
|
url,
|
|
6243
6778
|
httpUrl,
|
|
@@ -6248,33 +6783,87 @@ function resolveMemberConnectOptions(stack, member, options, forceHttpOnly) {
|
|
|
6248
6783
|
autoReconnect: member?.autoReconnect,
|
|
6249
6784
|
wallet: options?.wallet,
|
|
6250
6785
|
fetch: options?.fetch,
|
|
6786
|
+
programReads: resolveSessionProgramReads(stack, member, options),
|
|
6787
|
+
chain: options?.chain,
|
|
6788
|
+
transactions: options?.transactions,
|
|
6251
6789
|
};
|
|
6252
6790
|
}
|
|
6253
|
-
function
|
|
6791
|
+
function validateCompositionProgramReads(stack, member, options) {
|
|
6792
|
+
const overrides = resolveSessionProgramReads(stack, member, options);
|
|
6793
|
+
for (const [name, program] of Object.entries(stack.programs ?? {})) {
|
|
6794
|
+
const descriptor = overrides?.[name] ?? stack.programReads?.[name];
|
|
6795
|
+
if (descriptor)
|
|
6796
|
+
validateProgramReadDescriptor(name, descriptor);
|
|
6797
|
+
if (Object.keys(program.accounts ?? {}).length === 0)
|
|
6798
|
+
continue;
|
|
6799
|
+
const binding = descriptor?.transport.kind === 'hosted-binding'
|
|
6800
|
+
? descriptor.transport.binding
|
|
6801
|
+
: undefined;
|
|
6802
|
+
if (!descriptor?.release
|
|
6803
|
+
|| !binding
|
|
6804
|
+
|| !binding.endpoint?.trim()
|
|
6805
|
+
|| !binding.programReadBindingId?.trim()
|
|
6806
|
+
|| binding.auth?.targetKind !== 'program-read-binding'
|
|
6807
|
+
|| binding.auth.targetId !== binding.programReadBindingId
|
|
6808
|
+
|| !binding.auth.sessionEndpoint?.trim()) {
|
|
6809
|
+
throw new AreteError(`Composition session program '${name}' requires a complete hosted-binding descriptor or override`, 'INVALID_CONFIG');
|
|
6810
|
+
}
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6813
|
+
function programAsStack(name, program, descriptor) {
|
|
6254
6814
|
return {
|
|
6255
6815
|
name,
|
|
6256
6816
|
endpoints: { ws: '' },
|
|
6257
6817
|
views: {},
|
|
6258
6818
|
programs: { [name]: program },
|
|
6819
|
+
...(descriptor ? { programReads: { [name]: descriptor } } : {}),
|
|
6259
6820
|
};
|
|
6260
6821
|
}
|
|
6261
|
-
async function createSession(definition,
|
|
6822
|
+
async function createSession(definition, ...args) {
|
|
6823
|
+
const options = args[0];
|
|
6262
6824
|
const stackEntries = Object.entries(definition.stacks ?? {});
|
|
6263
6825
|
const programEntries = Object.entries(definition.programs ?? {});
|
|
6264
6826
|
if (stackEntries.length === 0 && programEntries.length === 0) {
|
|
6265
6827
|
throw new AreteError('createSession requires at least one stack or program member', 'INVALID_CONFIG');
|
|
6266
6828
|
}
|
|
6829
|
+
if (definition.mode === 'composition' && (!options?.chain || !options?.transactions)) {
|
|
6830
|
+
throw new AreteError('composition sessions require explicit chain and transaction transports', 'INVALID_CONFIG');
|
|
6831
|
+
}
|
|
6832
|
+
if (definition.mode === 'composition' && options?.endpoints !== undefined) {
|
|
6833
|
+
throw new AreteError('composition sessions require per-member live endpoints, not shared fallback endpoints', 'INVALID_CONFIG');
|
|
6834
|
+
}
|
|
6835
|
+
if (definition.programReads) {
|
|
6836
|
+
const programKeys = programEntries.map(([key]) => key);
|
|
6837
|
+
const descriptorKeys = Object.keys(definition.programReads);
|
|
6838
|
+
if (programKeys.some((key) => !descriptorKeys.includes(key))
|
|
6839
|
+
|| descriptorKeys.some((key) => !programKeys.includes(key))) {
|
|
6840
|
+
throw new AreteError('Session definition programReads keys must exactly match standalone programs', 'INVALID_CONFIG');
|
|
6841
|
+
}
|
|
6842
|
+
}
|
|
6843
|
+
if (definition.mode === 'composition') {
|
|
6844
|
+
for (const [key, stack] of stackEntries) {
|
|
6845
|
+
const member = options?.stacks?.[key];
|
|
6846
|
+
validateCompositionProgramReads(withPrograms(stack, member?.programs), member, options);
|
|
6847
|
+
}
|
|
6848
|
+
for (const [key, program] of programEntries) {
|
|
6849
|
+
validateCompositionProgramReads(programAsStack(key, program, definition.programReads?.[key]), options?.programs?.[key], options);
|
|
6850
|
+
}
|
|
6851
|
+
}
|
|
6267
6852
|
let wallet = options?.wallet;
|
|
6268
6853
|
const signerRegistry = options?.signerRegistry ?? createSignerRegistry();
|
|
6269
6854
|
const connectedStacks = await Promise.all(stackEntries.map(async ([key, stack]) => {
|
|
6270
6855
|
const memberOptions = options?.stacks?.[key];
|
|
6271
6856
|
const effectiveStack = withPrograms(stack, memberOptions?.programs);
|
|
6272
6857
|
const connectOptions = resolveMemberConnectOptions(effectiveStack, memberOptions, options, false);
|
|
6273
|
-
const client = await Arete$1.connect(
|
|
6858
|
+
const client = await Arete$1.connect(stack, {
|
|
6859
|
+
...connectOptions,
|
|
6860
|
+
programs: memberOptions?.programs,
|
|
6861
|
+
programReads: connectOptions.programReads,
|
|
6862
|
+
});
|
|
6274
6863
|
return [key, client];
|
|
6275
6864
|
}));
|
|
6276
6865
|
const connectedPrograms = await Promise.all(programEntries.map(async ([key, program]) => {
|
|
6277
|
-
const syntheticStack = programAsStack(key, program);
|
|
6866
|
+
const syntheticStack = programAsStack(key, program, definition.programReads?.[key]);
|
|
6278
6867
|
const connectOptions = resolveMemberConnectOptions(syntheticStack, options?.programs?.[key], options, true);
|
|
6279
6868
|
const client = await Arete$1.connect(syntheticStack, connectOptions);
|
|
6280
6869
|
return [key, client];
|
|
@@ -6293,21 +6882,23 @@ async function createSession(definition, options) {
|
|
|
6293
6882
|
client.programs[key],
|
|
6294
6883
|
]);
|
|
6295
6884
|
const promotedPrograms = Object.fromEntries(connectedProgramEntries);
|
|
6296
|
-
|
|
6297
|
-
for (const [
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
`
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6885
|
+
if (definition.mode !== 'composition') {
|
|
6886
|
+
for (const [stackKey, client] of connectedStacks) {
|
|
6887
|
+
for (const [programKey, program] of Object.entries(client.programs)) {
|
|
6888
|
+
if (explicitProgramKeys.has(programKey)) {
|
|
6889
|
+
continue;
|
|
6890
|
+
}
|
|
6891
|
+
const existingOwner = programOwners.get(programKey);
|
|
6892
|
+
if (existingOwner) {
|
|
6893
|
+
console.warn(`Program '${programKey}' is bundled by stacks '${existingOwner.stack}'` +
|
|
6894
|
+
` (${existingOwner.programId ?? 'unknown program ID'}) and '${stackKey}'` +
|
|
6895
|
+
` (${program.programId ?? 'unknown program ID'}); session.programs.${programKey}` +
|
|
6896
|
+
` uses '${existingOwner.stack}' because it was connected first`);
|
|
6897
|
+
continue;
|
|
6898
|
+
}
|
|
6899
|
+
promotedPrograms[programKey] = program;
|
|
6900
|
+
programOwners.set(programKey, { stack: stackKey, programId: program.programId });
|
|
6308
6901
|
}
|
|
6309
|
-
promotedPrograms[programKey] = program;
|
|
6310
|
-
programOwners.set(programKey, { stack: stackKey, programId: program.programId });
|
|
6311
6902
|
}
|
|
6312
6903
|
}
|
|
6313
6904
|
const programs = promotedPrograms;
|
|
@@ -6388,6 +6979,142 @@ function flowOperation(prepare) {
|
|
|
6388
6979
|
};
|
|
6389
6980
|
}
|
|
6390
6981
|
|
|
6982
|
+
function isSecureOrLoopbackHttpUrl(value) {
|
|
6983
|
+
try {
|
|
6984
|
+
const url = new URL(value);
|
|
6985
|
+
return url.protocol === 'https:'
|
|
6986
|
+
|| (url.protocol === 'http:'
|
|
6987
|
+
&& ['localhost', '127.0.0.1', '::1'].includes(url.hostname));
|
|
6988
|
+
}
|
|
6989
|
+
catch {
|
|
6990
|
+
return false;
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
function validateBinding(capability, binding, requiredScopes) {
|
|
6994
|
+
const auth = binding?.auth;
|
|
6995
|
+
const complete = binding
|
|
6996
|
+
&& isSecureOrLoopbackHttpUrl(binding.endpoint)
|
|
6997
|
+
&& /^sgb_[A-Za-z0-9_-]{32}$/.test(binding.solanaGatewayBindingId)
|
|
6998
|
+
&& binding.cluster.trim().length > 0
|
|
6999
|
+
&& binding.region.trim().length > 0
|
|
7000
|
+
&& auth
|
|
7001
|
+
&& typeof auth.required === 'boolean'
|
|
7002
|
+
&& auth.mode === binding.authPolicy
|
|
7003
|
+
&& isSecureOrLoopbackHttpUrl(auth.sessionEndpoint)
|
|
7004
|
+
&& isSecureOrLoopbackHttpUrl(auth.jwksUrl)
|
|
7005
|
+
&& auth.tokenTransport === 'bearer'
|
|
7006
|
+
&& auth.audience === 'arete:solana-gateway'
|
|
7007
|
+
&& auth.targetKind === 'solana-gateway-binding'
|
|
7008
|
+
&& auth.targetId === binding.solanaGatewayBindingId
|
|
7009
|
+
&& Array.isArray(auth.scopes)
|
|
7010
|
+
&& requiredScopes.every((scope) => auth.scopes.includes(scope))
|
|
7011
|
+
&& Array.isArray(auth.acceptedKeyClasses)
|
|
7012
|
+
&& typeof auth.transactionEntitlementRequired === 'boolean';
|
|
7013
|
+
if (!complete) {
|
|
7014
|
+
throw new AreteError(`Hosted Solana gateway ${capability} binding is incomplete or inconsistent`, 'INVALID_CONFIG');
|
|
7015
|
+
}
|
|
7016
|
+
}
|
|
7017
|
+
function resolveFetch(fetchImpl) {
|
|
7018
|
+
if (fetchImpl)
|
|
7019
|
+
return fetchImpl;
|
|
7020
|
+
if (typeof globalThis.fetch === 'function')
|
|
7021
|
+
return globalThis.fetch.bind(globalThis);
|
|
7022
|
+
throw new AreteError('A fetch implementation is required for hosted Solana gateway transports', 'INVALID_CONFIG');
|
|
7023
|
+
}
|
|
7024
|
+
function hasRuntimeAuthStrategy(auth) {
|
|
7025
|
+
return auth?.token !== undefined
|
|
7026
|
+
|| auth?.getToken !== undefined
|
|
7027
|
+
|| auth?.tokenEndpoint !== undefined;
|
|
7028
|
+
}
|
|
7029
|
+
function bindingAuthConfig(binding, runtimeAuth) {
|
|
7030
|
+
if (hasRuntimeAuthStrategy(runtimeAuth))
|
|
7031
|
+
return runtimeAuth;
|
|
7032
|
+
if (!binding.auth.required)
|
|
7033
|
+
return runtimeAuth;
|
|
7034
|
+
return { ...runtimeAuth, tokenEndpoint: binding.auth.sessionEndpoint };
|
|
7035
|
+
}
|
|
7036
|
+
function refreshableErrorCode(response) {
|
|
7037
|
+
const wireCode = response.headers.get('X-Error-Code');
|
|
7038
|
+
return wireCode !== null
|
|
7039
|
+
&& shouldRefreshToken(parseErrorCode(wireCode.trim().replace(/_/g, '-')));
|
|
7040
|
+
}
|
|
7041
|
+
/**
|
|
7042
|
+
* Construct explicit hosted chain and transaction transports from generated
|
|
7043
|
+
* gateway descriptors. Tokens are isolated by exact binding target and scope.
|
|
7044
|
+
*/
|
|
7045
|
+
function createHostedSolanaGatewayTransports(bindings, options = {}) {
|
|
7046
|
+
validateBinding('chain', bindings.chain, ['read']);
|
|
7047
|
+
validateBinding('transactions', bindings.transactions, ['transaction:inspect', 'transaction:send']);
|
|
7048
|
+
const fetchImpl = resolveFetch(options.fetch);
|
|
7049
|
+
const managers = new Map();
|
|
7050
|
+
const bindingAuthentication = (binding) => {
|
|
7051
|
+
const auth = bindingAuthConfig(binding, options.auth);
|
|
7052
|
+
if (auth?.token !== undefined) {
|
|
7053
|
+
if (!auth.token) {
|
|
7054
|
+
throw new AreteError('Authentication token is empty', 'TOKEN_INVALID');
|
|
7055
|
+
}
|
|
7056
|
+
return {
|
|
7057
|
+
staticToken: auth.token,
|
|
7058
|
+
target: {
|
|
7059
|
+
targetKind: 'solana-gateway-binding',
|
|
7060
|
+
targetId: binding.solanaGatewayBindingId,
|
|
7061
|
+
},
|
|
7062
|
+
};
|
|
7063
|
+
}
|
|
7064
|
+
let manager;
|
|
7065
|
+
if (auth && (auth.getToken || auth.tokenEndpoint)) {
|
|
7066
|
+
const identity = hasRuntimeAuthStrategy(options.auth)
|
|
7067
|
+
? 'runtime-auth-strategy'
|
|
7068
|
+
: `session-endpoint:${binding.auth.sessionEndpoint}`;
|
|
7069
|
+
manager = managers.get(identity);
|
|
7070
|
+
if (!manager) {
|
|
7071
|
+
manager = new ConnectionManager({ websocketUrl: null, auth, fetch: fetchImpl });
|
|
7072
|
+
managers.set(identity, manager);
|
|
7073
|
+
}
|
|
7074
|
+
}
|
|
7075
|
+
return {
|
|
7076
|
+
manager,
|
|
7077
|
+
target: {
|
|
7078
|
+
targetKind: 'solana-gateway-binding',
|
|
7079
|
+
targetId: binding.solanaGatewayBindingId,
|
|
7080
|
+
},
|
|
7081
|
+
};
|
|
7082
|
+
};
|
|
7083
|
+
const authenticatedFetch = (authentication) => async (input, init, scope, requirePreDispatchMarker) => {
|
|
7084
|
+
const request = { ...authentication.target, scopes: [scope] };
|
|
7085
|
+
const attempt = async (forceRefresh) => {
|
|
7086
|
+
const token = authentication.staticToken
|
|
7087
|
+
?? (authentication.manager
|
|
7088
|
+
? await authentication.manager.getHttpAuthToken(request, forceRefresh)
|
|
7089
|
+
: undefined);
|
|
7090
|
+
const headers = new Headers(init?.headers);
|
|
7091
|
+
if (token)
|
|
7092
|
+
headers.set('authorization', `Bearer ${token}`);
|
|
7093
|
+
return fetchImpl(input, { ...init, headers });
|
|
7094
|
+
};
|
|
7095
|
+
let response = await attempt(false);
|
|
7096
|
+
const explicitlyNotDispatched = response.headers.get('X-Arete-Upstream-Attempted') === 'false';
|
|
7097
|
+
if (!response.ok
|
|
7098
|
+
&& authentication.manager
|
|
7099
|
+
&& refreshableErrorCode(response)
|
|
7100
|
+
&& (!requirePreDispatchMarker || explicitlyNotDispatched)) {
|
|
7101
|
+
authentication.manager.clearHttpAuthToken(request);
|
|
7102
|
+
response = await attempt(true);
|
|
7103
|
+
}
|
|
7104
|
+
return response;
|
|
7105
|
+
};
|
|
7106
|
+
const chainAuthentication = bindingAuthentication(bindings.chain);
|
|
7107
|
+
const transactionAuthentication = bindingAuthentication(bindings.transactions);
|
|
7108
|
+
const chainFetch = authenticatedFetch(chainAuthentication);
|
|
7109
|
+
const transactionFetch = authenticatedFetch(transactionAuthentication);
|
|
7110
|
+
return {
|
|
7111
|
+
chain: createChainClient(bindings.chain.endpoint, ((input, init) => chainFetch(typeof input === 'string'
|
|
7112
|
+
? input
|
|
7113
|
+
: input instanceof URL ? input.toString() : input.url, init, 'read', false))),
|
|
7114
|
+
transactions: createTransactionTransport(bindings.transactions.endpoint, (input, init, scope, requirePreDispatchMarker) => transactionFetch(input, init, scope, requirePreDispatchMarker)),
|
|
7115
|
+
};
|
|
7116
|
+
}
|
|
7117
|
+
|
|
6391
7118
|
function chainAccountLoader(chain) {
|
|
6392
7119
|
return {
|
|
6393
7120
|
async getAccount(address) {
|
|
@@ -6580,6 +7307,7 @@ exports.canonicalQueryKey = canonicalQueryKey;
|
|
|
6580
7307
|
exports.chainAccountLoader = chainAccountLoader;
|
|
6581
7308
|
exports.createChainClient = createChainClient;
|
|
6582
7309
|
exports.createEntityStream = createEntityStream;
|
|
7310
|
+
exports.createHostedSolanaGatewayTransports = createHostedSolanaGatewayTransports;
|
|
6583
7311
|
exports.createInstructionExecutor = createInstructionExecutor;
|
|
6584
7312
|
exports.createInstructionHandler = createInstructionHandler;
|
|
6585
7313
|
exports.createPreparedFlow = createPreparedFlow;
|
|
@@ -6659,6 +7387,7 @@ exports.toRawAmount = toRawAmount;
|
|
|
6659
7387
|
exports.transactionOperation = transactionOperation;
|
|
6660
7388
|
exports.unwrapOperationExecutionError = unwrapOperationExecutionError;
|
|
6661
7389
|
exports.validateAccountResolution = validateAccountResolution;
|
|
7390
|
+
exports.validateProgramReadDescriptor = validateProgramReadDescriptor;
|
|
6662
7391
|
exports.validateSubscriptionId = validateSubscriptionId;
|
|
6663
7392
|
exports.withPrograms = withPrograms;
|
|
6664
7393
|
//# sourceMappingURL=index.cjs.map
|