@usearete/sdk 0.8.1 → 0.9.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.cjs CHANGED
@@ -3324,6 +3324,274 @@ function createChainClient(httpBaseUrl, fetchImpl) {
3324
3324
  };
3325
3325
  }
3326
3326
 
3327
+ class TransactionTransportError extends Error {
3328
+ constructor(status, body) {
3329
+ super(body.message);
3330
+ this.name = 'TransactionTransportError';
3331
+ this.status = status;
3332
+ this.code = body.code;
3333
+ this.retryable = body.retryable;
3334
+ this.requestId = body.request_id;
3335
+ this.submissionState = body.submission_state;
3336
+ this.signature = body.signature;
3337
+ this.details = body.details;
3338
+ }
3339
+ }
3340
+ function decimal(value) {
3341
+ return value === undefined ? undefined : value.toString(10);
3342
+ }
3343
+ function bigintField(value, field) {
3344
+ if (typeof value !== 'string' || !/^\d+$/.test(value)) {
3345
+ throw new Error(`Invalid decimal u64 field '${field}' in transaction response`);
3346
+ }
3347
+ return BigInt(value);
3348
+ }
3349
+ function optionalBigint(value, field) {
3350
+ return value === undefined || value === null ? undefined : bigintField(value, field);
3351
+ }
3352
+ function requestBody(value) {
3353
+ return JSON.stringify(Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)));
3354
+ }
3355
+ async function parseError(response) {
3356
+ let body = {};
3357
+ try {
3358
+ body = await response.json();
3359
+ }
3360
+ catch {
3361
+ // Public errors are deliberately synthesized without reflecting raw bodies.
3362
+ }
3363
+ return new TransactionTransportError(response.status, {
3364
+ code: typeof body.code === 'string' ? body.code : 'transaction_transport_error',
3365
+ message: typeof body.message === 'string' ? body.message : `Transaction request failed (${response.status})`,
3366
+ retryable: body.retryable === true,
3367
+ request_id: body.request_id ?? body.requestId,
3368
+ submission_state: body.submission_state
3369
+ ?? body.submissionState,
3370
+ signature: body.signature,
3371
+ details: body.details,
3372
+ });
3373
+ }
3374
+ function createTransactionTransport(baseUrl, authenticatedFetch) {
3375
+ const root = `${baseUrl.replace(/\/$/, '')}/transactions/v1`;
3376
+ const post = async (route, body, scope) => {
3377
+ const response = await authenticatedFetch(`${root}/${route}`, {
3378
+ method: 'POST',
3379
+ headers: { 'content-type': 'application/json' },
3380
+ body: requestBody(body),
3381
+ }, scope, true);
3382
+ if (!response.ok)
3383
+ throw await parseError(response);
3384
+ return response.json();
3385
+ };
3386
+ return {
3387
+ async getLatestBlockhash(options = {}) {
3388
+ const value = await post('latest-blockhash', {
3389
+ commitment: options.commitment,
3390
+ minContextSlot: decimal(options.minContextSlot),
3391
+ }, 'transaction:inspect');
3392
+ return {
3393
+ blockhash: String(value.blockhash),
3394
+ contextSlot: bigintField(value.contextSlot, 'contextSlot'),
3395
+ lastValidBlockHeight: bigintField(value.lastValidBlockHeight, 'lastValidBlockHeight'),
3396
+ };
3397
+ },
3398
+ async getFeeForMessage(message, options = {}) {
3399
+ const value = await post('fee', {
3400
+ message,
3401
+ commitment: options.commitment,
3402
+ minContextSlot: decimal(options.minContextSlot),
3403
+ }, 'transaction:inspect');
3404
+ return {
3405
+ feeLamports: value.feeLamports === null ? null : bigintField(value.feeLamports, 'feeLamports'),
3406
+ contextSlot: bigintField(value.contextSlot, 'contextSlot'),
3407
+ };
3408
+ },
3409
+ async simulateTransaction(transaction, options = {}) {
3410
+ const value = await post('simulate', {
3411
+ transaction,
3412
+ commitment: options.commitment,
3413
+ minContextSlot: decimal(options.minContextSlot),
3414
+ accounts: options.accounts ? { addresses: options.accounts } : undefined,
3415
+ innerInstructions: options.innerInstructions,
3416
+ replaceRecentBlockhash: options.replaceRecentBlockhash,
3417
+ }, 'transaction:inspect');
3418
+ return {
3419
+ contextSlot: bigintField(value.contextSlot, 'contextSlot'),
3420
+ err: value.err ?? null,
3421
+ logs: value.logs ?? null,
3422
+ unitsConsumed: optionalBigint(value.unitsConsumed, 'unitsConsumed'),
3423
+ accounts: value.accounts,
3424
+ };
3425
+ },
3426
+ sendTransaction(transaction, options = {}) {
3427
+ return post('send', {
3428
+ transaction,
3429
+ skipPreflight: options.skipPreflight,
3430
+ preflightCommitment: options.preflightCommitment,
3431
+ minContextSlot: decimal(options.minContextSlot),
3432
+ }, 'transaction:send');
3433
+ },
3434
+ async getSignatureStatus(signature, options = {}) {
3435
+ const value = await post('signature-status', {
3436
+ signature,
3437
+ searchTransactionHistory: options.searchTransactionHistory,
3438
+ }, 'transaction:inspect');
3439
+ const status = value?.status;
3440
+ if (!status)
3441
+ return null;
3442
+ return {
3443
+ signature,
3444
+ slot: status.slot === null ? null : bigintField(status.slot, 'slot'),
3445
+ confirmationStatus: status.confirmationStatus,
3446
+ err: status.err ?? null,
3447
+ };
3448
+ },
3449
+ async getBlockHeight(options = {}) {
3450
+ const value = await post('block-height', {
3451
+ commitment: options.commitment,
3452
+ minContextSlot: decimal(options.minContextSlot),
3453
+ }, 'transaction:inspect');
3454
+ return bigintField(value.blockHeight, 'blockHeight');
3455
+ },
3456
+ };
3457
+ }
3458
+
3459
+ function isSecureOrLoopbackHttpUrl$1(value) {
3460
+ try {
3461
+ const url = new URL(value);
3462
+ return url.protocol === 'https:'
3463
+ || (url.protocol === 'http:'
3464
+ && ['localhost', '127.0.0.1', '::1'].includes(url.hostname));
3465
+ }
3466
+ catch {
3467
+ return false;
3468
+ }
3469
+ }
3470
+ function validateBinding(capability, binding, requiredScopes) {
3471
+ const auth = binding?.auth;
3472
+ const complete = binding
3473
+ && isSecureOrLoopbackHttpUrl$1(binding.endpoint)
3474
+ && /^sgb_[A-Za-z0-9_-]{32}$/.test(binding.solanaGatewayBindingId)
3475
+ && binding.cluster.trim().length > 0
3476
+ && binding.region.trim().length > 0
3477
+ && auth
3478
+ && typeof auth.required === 'boolean'
3479
+ && auth.mode === binding.authPolicy
3480
+ && isSecureOrLoopbackHttpUrl$1(auth.sessionEndpoint)
3481
+ && isSecureOrLoopbackHttpUrl$1(auth.jwksUrl)
3482
+ && auth.tokenTransport === 'bearer'
3483
+ && auth.audience === 'arete:solana-gateway'
3484
+ && auth.targetKind === 'solana-gateway-binding'
3485
+ && auth.targetId === binding.solanaGatewayBindingId
3486
+ && Array.isArray(auth.scopes)
3487
+ && requiredScopes.every((scope) => auth.scopes.includes(scope))
3488
+ && Array.isArray(auth.acceptedKeyClasses)
3489
+ && typeof auth.transactionEntitlementRequired === 'boolean';
3490
+ if (!complete) {
3491
+ throw new AreteError(`Hosted Solana gateway ${capability} binding is incomplete or inconsistent`, 'INVALID_CONFIG');
3492
+ }
3493
+ }
3494
+ function resolveFetch(fetchImpl) {
3495
+ if (fetchImpl)
3496
+ return fetchImpl;
3497
+ if (typeof globalThis.fetch === 'function')
3498
+ return globalThis.fetch.bind(globalThis);
3499
+ throw new AreteError('A fetch implementation is required for hosted Solana gateway transports', 'INVALID_CONFIG');
3500
+ }
3501
+ function hasRuntimeAuthStrategy(auth) {
3502
+ return auth?.token !== undefined
3503
+ || auth?.getToken !== undefined
3504
+ || auth?.tokenEndpoint !== undefined;
3505
+ }
3506
+ function bindingAuthConfig(binding, runtimeAuth) {
3507
+ if (hasRuntimeAuthStrategy(runtimeAuth))
3508
+ return runtimeAuth;
3509
+ if (!binding.auth.required)
3510
+ return runtimeAuth;
3511
+ return { ...runtimeAuth, tokenEndpoint: binding.auth.sessionEndpoint };
3512
+ }
3513
+ function refreshableErrorCode(response) {
3514
+ const wireCode = response.headers.get('X-Error-Code');
3515
+ return wireCode !== null
3516
+ && shouldRefreshToken(parseErrorCode(wireCode.trim().replace(/_/g, '-')));
3517
+ }
3518
+ /**
3519
+ * Construct explicit hosted chain and transaction transports from generated
3520
+ * gateway descriptors. Tokens are isolated by exact binding target and scope.
3521
+ */
3522
+ function createHostedSolanaGatewayTransports(bindings, options = {}) {
3523
+ validateBinding('chain', bindings.chain, ['read']);
3524
+ validateBinding('transactions', bindings.transactions, ['transaction:inspect', 'transaction:send']);
3525
+ const fetchImpl = resolveFetch(options.fetch);
3526
+ const managers = new Map();
3527
+ const bindingAuthentication = (binding) => {
3528
+ const auth = bindingAuthConfig(binding, options.auth);
3529
+ if (auth?.token !== undefined) {
3530
+ if (!auth.token) {
3531
+ throw new AreteError('Authentication token is empty', 'TOKEN_INVALID');
3532
+ }
3533
+ return {
3534
+ staticToken: auth.token,
3535
+ target: {
3536
+ targetKind: 'solana-gateway-binding',
3537
+ targetId: binding.solanaGatewayBindingId,
3538
+ },
3539
+ };
3540
+ }
3541
+ let manager;
3542
+ if (auth && (auth.getToken || auth.tokenEndpoint)) {
3543
+ const identity = hasRuntimeAuthStrategy(options.auth)
3544
+ ? 'runtime-auth-strategy'
3545
+ : `session-endpoint:${binding.auth.sessionEndpoint}`;
3546
+ manager = managers.get(identity);
3547
+ if (!manager) {
3548
+ manager = new ConnectionManager({ websocketUrl: null, auth, fetch: fetchImpl });
3549
+ managers.set(identity, manager);
3550
+ }
3551
+ }
3552
+ return {
3553
+ manager,
3554
+ target: {
3555
+ targetKind: 'solana-gateway-binding',
3556
+ targetId: binding.solanaGatewayBindingId,
3557
+ },
3558
+ };
3559
+ };
3560
+ const authenticatedFetch = (authentication) => async (input, init, scope, requirePreDispatchMarker) => {
3561
+ const request = { ...authentication.target, scopes: [scope] };
3562
+ const attempt = async (forceRefresh) => {
3563
+ const token = authentication.staticToken
3564
+ ?? (authentication.manager
3565
+ ? await authentication.manager.getHttpAuthToken(request, forceRefresh)
3566
+ : undefined);
3567
+ const headers = new Headers(init?.headers);
3568
+ if (token)
3569
+ headers.set('authorization', `Bearer ${token}`);
3570
+ return fetchImpl(input, { ...init, headers });
3571
+ };
3572
+ let response = await attempt(false);
3573
+ const explicitlyNotDispatched = response.headers.get('X-Arete-Upstream-Attempted') === 'false';
3574
+ if (!response.ok
3575
+ && authentication.manager
3576
+ && refreshableErrorCode(response)
3577
+ && (!requirePreDispatchMarker || explicitlyNotDispatched)) {
3578
+ authentication.manager.clearHttpAuthToken(request);
3579
+ response = await attempt(true);
3580
+ }
3581
+ return response;
3582
+ };
3583
+ const chainAuthentication = bindingAuthentication(bindings.chain);
3584
+ const transactionAuthentication = bindingAuthentication(bindings.transactions);
3585
+ const chainFetch = authenticatedFetch(chainAuthentication);
3586
+ const transactionFetch = authenticatedFetch(transactionAuthentication);
3587
+ return {
3588
+ chain: createChainClient(bindings.chain.endpoint, ((input, init) => chainFetch(typeof input === 'string'
3589
+ ? input
3590
+ : input instanceof URL ? input.toString() : input.url, init, 'read', false))),
3591
+ transactions: createTransactionTransport(bindings.transactions.endpoint, (input, init, scope, requirePreDispatchMarker) => transactionFetch(input, init, scope, requirePreDispatchMarker)),
3592
+ };
3593
+ }
3594
+
3327
3595
  /*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
3328
3596
  /**
3329
3597
  * 4KB JS implementation of ed25519 EdDSA signatures.
@@ -5954,237 +6222,105 @@ function isRefreshableErrorCode(code) {
5954
6222
  }
5955
6223
  function parseProgramReadResponse(response, path, body) {
5956
6224
  if (!response.ok) {
5957
- throw new ReadRequestError({
5958
- status: response.status,
5959
- path,
5960
- body,
5961
- serverErrorCode: responseErrorCode(response, body),
5962
- });
5963
- }
5964
- try {
5965
- return JSON.parse(body);
5966
- }
5967
- catch (error) {
5968
- throw new AreteError(`Program read '${path}' returned invalid JSON`, 'INVALID_RESPONSE', error);
5969
- }
5970
- }
5971
- function hostedAuthConfig(binding, runtimeAuth) {
5972
- const metadata = binding.auth;
5973
- const runtimeStrategyConfigured = runtimeAuth?.token !== undefined
5974
- || runtimeAuth?.getToken !== undefined
5975
- || runtimeAuth?.tokenEndpoint !== undefined;
5976
- if (runtimeStrategyConfigured)
5977
- return runtimeAuth;
5978
- if (metadata.required === false)
5979
- return runtimeAuth;
5980
- return { ...runtimeAuth, tokenEndpoint: metadata.sessionEndpoint };
5981
- }
5982
- function hostedAuth(config) {
5983
- const auth = hostedAuthConfig(config.binding, config.auth);
5984
- if (auth?.token !== undefined && !auth.token) {
5985
- throw new AreteError('Authentication token is empty', 'TOKEN_INVALID');
5986
- }
5987
- const bindingId = config.binding.programReadBindingId;
5988
- if (!auth || auth.token !== undefined) {
5989
- return { staticToken: auth?.token };
5990
- }
5991
- return {
5992
- manager: new ConnectionManager({ websocketUrl: null, auth, fetch: config.fetch }),
5993
- target: {
5994
- targetKind: 'program-read-binding',
5995
- targetId: bindingId,
5996
- programReleaseHash: config.release.programReleaseHash,
5997
- },
5998
- };
5999
- }
6000
- function requestPath(release, request) {
6001
- const releaseHash = encodeURIComponent(release.programReleaseHash).replace(/%3A/g, ':');
6002
- const root = `/v1/releases/${releaseHash}`
6003
- + `/accounts/${encodeURIComponent(request.account)}`;
6004
- if (request.operation === 'fetchMany')
6005
- return root;
6006
- const addressPath = `${root.replace(/\/+$/, '')}/${encodeURIComponent(request.address)}`;
6007
- return request.operation === 'exists' ? `${addressPath}/exists` : addressPath;
6008
- }
6009
- function createProgramReadTransport(config) {
6010
- if (config.kind === 'unavailable') {
6011
- return {
6012
- async read() {
6013
- throw new AreteError(config.message, 'INVALID_CONFIG');
6014
- },
6015
- };
6016
- }
6017
- const auth = config.kind === 'hosted-binding' ? hostedAuth(config) : undefined;
6018
- const endpoint = config.kind === 'hosted-binding'
6019
- ? config.binding.endpoint
6020
- : config.endpoint;
6021
- return {
6022
- async read(request) {
6023
- const path = requestPath(config.release, request);
6024
- const input = appendUrl(endpoint, path);
6025
- const attempt = async (forceRefresh) => {
6026
- const token = auth?.staticToken
6027
- ?? (auth?.manager && auth.target
6028
- ? await auth.manager.getHttpAuthToken(auth.target, ['read'], forceRefresh)
6029
- : undefined);
6030
- const headers = new Headers(request.operation === 'fetchMany'
6031
- ? { 'content-type': 'application/json' }
6032
- : undefined);
6033
- if (token)
6034
- headers.set('authorization', `Bearer ${token}`);
6035
- return config.fetch(input, {
6036
- method: request.operation === 'fetchMany' ? 'POST' : 'GET',
6037
- headers,
6038
- body: request.operation === 'fetchMany'
6039
- ? JSON.stringify({ addresses: request.addresses })
6040
- : undefined,
6041
- });
6042
- };
6043
- let response = await attempt(false);
6044
- let body = await response.text();
6045
- const wireCode = responseErrorCode(response, body);
6046
- if (response.status === 401
6047
- && wireCode
6048
- && isRefreshableErrorCode(wireCode)
6049
- && auth?.manager
6050
- && auth.target) {
6051
- auth.manager.clearHttpAuthToken(auth.target);
6052
- response = await attempt(true);
6053
- body = await response.text();
6054
- }
6055
- return parseProgramReadResponse(response, path, body);
6056
- },
6057
- };
6058
- }
6059
-
6060
- class TransactionTransportError extends Error {
6061
- constructor(status, body) {
6062
- super(body.message);
6063
- this.name = 'TransactionTransportError';
6064
- this.status = status;
6065
- this.code = body.code;
6066
- this.retryable = body.retryable;
6067
- this.requestId = body.request_id;
6068
- this.submissionState = body.submission_state;
6069
- this.signature = body.signature;
6070
- this.details = body.details;
6071
- }
6072
- }
6073
- function decimal(value) {
6074
- return value === undefined ? undefined : value.toString(10);
6075
- }
6076
- function bigintField(value, field) {
6077
- if (typeof value !== 'string' || !/^\d+$/.test(value)) {
6078
- throw new Error(`Invalid decimal u64 field '${field}' in transaction response`);
6079
- }
6080
- return BigInt(value);
6081
- }
6082
- function optionalBigint(value, field) {
6083
- return value === undefined || value === null ? undefined : bigintField(value, field);
6084
- }
6085
- function requestBody(value) {
6086
- return JSON.stringify(Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)));
6087
- }
6088
- async function parseError(response) {
6089
- let body = {};
6090
- try {
6091
- body = await response.json();
6092
- }
6093
- catch {
6094
- // Public errors are deliberately synthesized without reflecting raw bodies.
6095
- }
6096
- return new TransactionTransportError(response.status, {
6097
- code: typeof body.code === 'string' ? body.code : 'transaction_transport_error',
6098
- message: typeof body.message === 'string' ? body.message : `Transaction request failed (${response.status})`,
6099
- retryable: body.retryable === true,
6100
- request_id: body.request_id ?? body.requestId,
6101
- submission_state: body.submission_state
6102
- ?? body.submissionState,
6103
- signature: body.signature,
6104
- details: body.details,
6105
- });
6106
- }
6107
- function createTransactionTransport(baseUrl, authenticatedFetch) {
6108
- const root = `${baseUrl.replace(/\/$/, '')}/transactions/v1`;
6109
- const post = async (route, body, scope) => {
6110
- const response = await authenticatedFetch(`${root}/${route}`, {
6111
- method: 'POST',
6112
- headers: { 'content-type': 'application/json' },
6113
- body: requestBody(body),
6114
- }, scope, true);
6115
- if (!response.ok)
6116
- throw await parseError(response);
6117
- return response.json();
6118
- };
6119
- return {
6120
- async getLatestBlockhash(options = {}) {
6121
- const value = await post('latest-blockhash', {
6122
- commitment: options.commitment,
6123
- minContextSlot: decimal(options.minContextSlot),
6124
- }, 'transaction:inspect');
6125
- return {
6126
- blockhash: String(value.blockhash),
6127
- contextSlot: bigintField(value.contextSlot, 'contextSlot'),
6128
- lastValidBlockHeight: bigintField(value.lastValidBlockHeight, 'lastValidBlockHeight'),
6129
- };
6130
- },
6131
- async getFeeForMessage(message, options = {}) {
6132
- const value = await post('fee', {
6133
- message,
6134
- commitment: options.commitment,
6135
- minContextSlot: decimal(options.minContextSlot),
6136
- }, 'transaction:inspect');
6137
- return {
6138
- feeLamports: value.feeLamports === null ? null : bigintField(value.feeLamports, 'feeLamports'),
6139
- contextSlot: bigintField(value.contextSlot, 'contextSlot'),
6140
- };
6141
- },
6142
- async simulateTransaction(transaction, options = {}) {
6143
- const value = await post('simulate', {
6144
- transaction,
6145
- commitment: options.commitment,
6146
- minContextSlot: decimal(options.minContextSlot),
6147
- accounts: options.accounts ? { addresses: options.accounts } : undefined,
6148
- innerInstructions: options.innerInstructions,
6149
- replaceRecentBlockhash: options.replaceRecentBlockhash,
6150
- }, 'transaction:inspect');
6151
- return {
6152
- contextSlot: bigintField(value.contextSlot, 'contextSlot'),
6153
- err: value.err ?? null,
6154
- logs: value.logs ?? null,
6155
- unitsConsumed: optionalBigint(value.unitsConsumed, 'unitsConsumed'),
6156
- accounts: value.accounts,
6157
- };
6158
- },
6159
- sendTransaction(transaction, options = {}) {
6160
- return post('send', {
6161
- transaction,
6162
- skipPreflight: options.skipPreflight,
6163
- preflightCommitment: options.preflightCommitment,
6164
- minContextSlot: decimal(options.minContextSlot),
6165
- }, 'transaction:send');
6225
+ throw new ReadRequestError({
6226
+ status: response.status,
6227
+ path,
6228
+ body,
6229
+ serverErrorCode: responseErrorCode(response, body),
6230
+ });
6231
+ }
6232
+ try {
6233
+ return JSON.parse(body);
6234
+ }
6235
+ catch (error) {
6236
+ throw new AreteError(`Program read '${path}' returned invalid JSON`, 'INVALID_RESPONSE', error);
6237
+ }
6238
+ }
6239
+ function hostedAuthConfig(binding, runtimeAuth) {
6240
+ const metadata = binding.auth;
6241
+ const runtimeStrategyConfigured = runtimeAuth?.token !== undefined
6242
+ || runtimeAuth?.getToken !== undefined
6243
+ || runtimeAuth?.tokenEndpoint !== undefined;
6244
+ if (runtimeStrategyConfigured)
6245
+ return runtimeAuth;
6246
+ if (metadata.required === false)
6247
+ return runtimeAuth;
6248
+ return { ...runtimeAuth, tokenEndpoint: metadata.sessionEndpoint };
6249
+ }
6250
+ function hostedAuth(config) {
6251
+ const auth = hostedAuthConfig(config.binding, config.auth);
6252
+ if (auth?.token !== undefined && !auth.token) {
6253
+ throw new AreteError('Authentication token is empty', 'TOKEN_INVALID');
6254
+ }
6255
+ const bindingId = config.binding.programReadBindingId;
6256
+ if (!auth || auth.token !== undefined) {
6257
+ return { staticToken: auth?.token };
6258
+ }
6259
+ return {
6260
+ manager: new ConnectionManager({ websocketUrl: null, auth, fetch: config.fetch }),
6261
+ target: {
6262
+ targetKind: 'program-read-binding',
6263
+ targetId: bindingId,
6264
+ programReleaseHash: config.release.programReleaseHash,
6166
6265
  },
6167
- async getSignatureStatus(signature, options = {}) {
6168
- const value = await post('signature-status', {
6169
- signature,
6170
- searchTransactionHistory: options.searchTransactionHistory,
6171
- }, 'transaction:inspect');
6172
- const status = value?.status;
6173
- if (!status)
6174
- return null;
6175
- return {
6176
- signature,
6177
- slot: status.slot === null ? null : bigintField(status.slot, 'slot'),
6178
- confirmationStatus: status.confirmationStatus,
6179
- err: status.err ?? null,
6266
+ };
6267
+ }
6268
+ function requestPath(release, request) {
6269
+ const releaseHash = encodeURIComponent(release.programReleaseHash).replace(/%3A/g, ':');
6270
+ const root = `/v1/releases/${releaseHash}`
6271
+ + `/accounts/${encodeURIComponent(request.account)}`;
6272
+ if (request.operation === 'fetchMany')
6273
+ return root;
6274
+ const addressPath = `${root.replace(/\/+$/, '')}/${encodeURIComponent(request.address)}`;
6275
+ return request.operation === 'exists' ? `${addressPath}/exists` : addressPath;
6276
+ }
6277
+ function createProgramReadTransport(config) {
6278
+ if (config.kind === 'unavailable') {
6279
+ return {
6280
+ async read() {
6281
+ throw new AreteError(config.message, 'INVALID_CONFIG');
6282
+ },
6283
+ };
6284
+ }
6285
+ const auth = config.kind === 'hosted-binding' ? hostedAuth(config) : undefined;
6286
+ const endpoint = config.kind === 'hosted-binding'
6287
+ ? config.binding.endpoint
6288
+ : config.endpoint;
6289
+ return {
6290
+ async read(request) {
6291
+ const path = requestPath(config.release, request);
6292
+ const input = appendUrl(endpoint, path);
6293
+ const attempt = async (forceRefresh) => {
6294
+ const token = auth?.staticToken
6295
+ ?? (auth?.manager && auth.target
6296
+ ? await auth.manager.getHttpAuthToken(auth.target, ['read'], forceRefresh)
6297
+ : undefined);
6298
+ const headers = new Headers(request.operation === 'fetchMany'
6299
+ ? { 'content-type': 'application/json' }
6300
+ : undefined);
6301
+ if (token)
6302
+ headers.set('authorization', `Bearer ${token}`);
6303
+ return config.fetch(input, {
6304
+ method: request.operation === 'fetchMany' ? 'POST' : 'GET',
6305
+ headers,
6306
+ body: request.operation === 'fetchMany'
6307
+ ? JSON.stringify({ addresses: request.addresses })
6308
+ : undefined,
6309
+ });
6180
6310
  };
6181
- },
6182
- async getBlockHeight(options = {}) {
6183
- const value = await post('block-height', {
6184
- commitment: options.commitment,
6185
- minContextSlot: decimal(options.minContextSlot),
6186
- }, 'transaction:inspect');
6187
- return bigintField(value.blockHeight, 'blockHeight');
6311
+ let response = await attempt(false);
6312
+ let body = await response.text();
6313
+ const wireCode = responseErrorCode(response, body);
6314
+ if (response.status === 401
6315
+ && wireCode
6316
+ && isRefreshableErrorCode(wireCode)
6317
+ && auth?.manager
6318
+ && auth.target) {
6319
+ auth.manager.clearHttpAuthToken(auth.target);
6320
+ response = await attempt(true);
6321
+ body = await response.text();
6322
+ }
6323
+ return parseProgramReadResponse(response, path, body);
6188
6324
  },
6189
6325
  };
6190
6326
  }
@@ -6204,7 +6340,7 @@ function validateReleaseIdentity(programName, definition, release) {
6204
6340
  function isNonEmptyString(value) {
6205
6341
  return typeof value === 'string' && value.trim().length > 0;
6206
6342
  }
6207
- function isSecureOrLoopbackHttpUrl$1(value) {
6343
+ function isSecureOrLoopbackHttpUrl(value) {
6208
6344
  try {
6209
6345
  const url = new URL(value);
6210
6346
  return url.protocol === 'https:'
@@ -6235,12 +6371,12 @@ function validateProgramReadDescriptor(programName, descriptor) {
6235
6371
  }
6236
6372
  const { binding } = descriptor.transport;
6237
6373
  if (!binding
6238
- || !isSecureOrLoopbackHttpUrl$1(binding.endpoint)
6374
+ || !isSecureOrLoopbackHttpUrl(binding.endpoint)
6239
6375
  || !/^prb_[A-Za-z0-9_-]{32}$/.test(binding.programReadBindingId)
6240
6376
  || !binding.auth
6241
6377
  || binding.auth.targetKind !== 'program-read-binding'
6242
6378
  || binding.auth.targetId !== binding.programReadBindingId
6243
- || !isSecureOrLoopbackHttpUrl$1(binding.auth.sessionEndpoint)) {
6379
+ || !isSecureOrLoopbackHttpUrl(binding.auth.sessionEndpoint)) {
6244
6380
  throw new AreteError(`Program '${programName}' hosted binding requires secure endpoints, a canonical binding ID, and matching program-read-binding auth metadata`, 'INVALID_CONFIG');
6245
6381
  }
6246
6382
  }
@@ -6402,12 +6538,19 @@ let Arete$1 = class Arete {
6402
6538
  });
6403
6539
  this._views = createTypedViews(this.stack, this.storage, this.subscriptionRegistry);
6404
6540
  this._queries = this.buildQueries();
6405
- this._chain = options.chain ?? createChainClient(this.httpBaseUrl ?? '', ((input, init) => this.authenticatedStackFetch(typeof input === 'string'
6541
+ const hostedGateway = this.stack.gateway
6542
+ && (options.chain === undefined || options.transactions === undefined)
6543
+ ? createHostedSolanaGatewayTransports(this.stack.gateway, {
6544
+ auth: options.auth,
6545
+ fetch: this.fetchImpl,
6546
+ })
6547
+ : undefined;
6548
+ this._chain = options.chain ?? hostedGateway?.chain ?? createChainClient(this.httpBaseUrl ?? '', ((input, init) => this.authenticatedStackFetch(typeof input === 'string'
6406
6549
  ? input
6407
6550
  : input instanceof URL
6408
6551
  ? input.toString()
6409
6552
  : input.url, init, ['read'])));
6410
- this._transactions = options.transactions ?? createTransactionTransport(this.httpBaseUrl ?? '', (input, init, scope, requirePreDispatchMarker) => this.authenticatedStackFetch(input, init, [scope], requirePreDispatchMarker));
6553
+ this._transactions = options.transactions ?? hostedGateway?.transactions ?? createTransactionTransport(this.httpBaseUrl ?? '', (input, init, scope, requirePreDispatchMarker) => this.authenticatedStackFetch(input, init, [scope], requirePreDispatchMarker));
6411
6554
  this._programs = this.buildPrograms();
6412
6555
  }
6413
6556
  resolveFetchImpl() {
@@ -6941,6 +7084,7 @@ function programAsStack(name, program, descriptor) {
6941
7084
  views: {},
6942
7085
  programs: { [name]: program },
6943
7086
  ...(descriptor ? { programReads: { [name]: descriptor } } : {}),
7087
+ ...(program.gateway ? { gateway: program.gateway } : {}),
6944
7088
  };
6945
7089
  }
6946
7090
  async function createSession(definition, ...args) {
@@ -6950,8 +7094,16 @@ async function createSession(definition, ...args) {
6950
7094
  if (stackEntries.length === 0 && programEntries.length === 0) {
6951
7095
  throw new AreteError('createSession requires at least one stack or program member', 'INVALID_CONFIG');
6952
7096
  }
6953
- if (definition.mode === 'composition' && (!options?.chain || !options?.transactions)) {
6954
- throw new AreteError('composition sessions require explicit chain and transaction transports', 'INVALID_CONFIG');
7097
+ const generatedGateway = definition.gateway && (!options?.chain || !options?.transactions)
7098
+ ? createHostedSolanaGatewayTransports(definition.gateway, {
7099
+ auth: options?.auth,
7100
+ fetch: options?.fetch,
7101
+ })
7102
+ : undefined;
7103
+ const sharedChain = options?.chain ?? generatedGateway?.chain;
7104
+ const sharedTransactions = options?.transactions ?? generatedGateway?.transactions;
7105
+ if (definition.mode === 'composition' && (!sharedChain || !sharedTransactions)) {
7106
+ throw new AreteError('composition sessions require generated or explicit chain and transaction transports', 'INVALID_CONFIG');
6955
7107
  }
6956
7108
  if (definition.mode === 'composition' && options?.endpoints !== undefined) {
6957
7109
  throw new AreteError('composition sessions require per-member live endpoints, not shared fallback endpoints', 'INVALID_CONFIG');
@@ -6978,7 +7130,11 @@ async function createSession(definition, ...args) {
6978
7130
  const connectedStacks = await Promise.all(stackEntries.map(async ([key, stack]) => {
6979
7131
  const memberOptions = options?.stacks?.[key];
6980
7132
  const effectiveStack = withPrograms(stack, memberOptions?.programs);
6981
- const connectOptions = resolveMemberConnectOptions(effectiveStack, memberOptions, options, false);
7133
+ const connectOptions = resolveMemberConnectOptions(effectiveStack, memberOptions, {
7134
+ ...options,
7135
+ chain: sharedChain,
7136
+ transactions: sharedTransactions,
7137
+ }, false);
6982
7138
  const client = await Arete$1.connect(stack, {
6983
7139
  ...connectOptions,
6984
7140
  programs: memberOptions?.programs,
@@ -6988,7 +7144,11 @@ async function createSession(definition, ...args) {
6988
7144
  }));
6989
7145
  const connectedPrograms = await Promise.all(programEntries.map(async ([key, program]) => {
6990
7146
  const syntheticStack = programAsStack(key, program, definition.programReads?.[key]);
6991
- const connectOptions = resolveMemberConnectOptions(syntheticStack, options?.programs?.[key], options, true);
7147
+ const connectOptions = resolveMemberConnectOptions(syntheticStack, options?.programs?.[key], {
7148
+ ...options,
7149
+ chain: sharedChain,
7150
+ transactions: sharedTransactions,
7151
+ }, true);
6992
7152
  const client = await Arete$1.connect(syntheticStack, connectOptions);
6993
7153
  return [key, client];
6994
7154
  }));
@@ -6997,7 +7157,7 @@ async function createSession(definition, ...args) {
6997
7157
  ...connectedPrograms.map(([, client]) => client),
6998
7158
  ];
6999
7159
  const executionHost = memberClients[0];
7000
- const transactions = options?.transactions ?? executionHost.transactions;
7160
+ const transactions = sharedTransactions ?? executionHost.transactions;
7001
7161
  const stacks = Object.fromEntries(connectedStacks);
7002
7162
  const explicitProgramKeys = new Set(connectedPrograms.map(([key]) => key));
7003
7163
  const programOwners = new Map();
@@ -7026,7 +7186,7 @@ async function createSession(definition, ...args) {
7026
7186
  }
7027
7187
  }
7028
7188
  const programs = promotedPrograms;
7029
- const chain = options?.chain ?? (options?.endpoints?.http !== undefined
7189
+ const chain = sharedChain ?? (options?.endpoints?.http !== undefined
7030
7190
  ? createChainClient(options.endpoints.http, (options?.fetch ?? globalThis.fetch?.bind(globalThis)))
7031
7191
  : executionHost.chain);
7032
7192
  const session = {
@@ -7103,142 +7263,6 @@ function flowOperation(prepare) {
7103
7263
  };
7104
7264
  }
7105
7265
 
7106
- function isSecureOrLoopbackHttpUrl(value) {
7107
- try {
7108
- const url = new URL(value);
7109
- return url.protocol === 'https:'
7110
- || (url.protocol === 'http:'
7111
- && ['localhost', '127.0.0.1', '::1'].includes(url.hostname));
7112
- }
7113
- catch {
7114
- return false;
7115
- }
7116
- }
7117
- function validateBinding(capability, binding, requiredScopes) {
7118
- const auth = binding?.auth;
7119
- const complete = binding
7120
- && isSecureOrLoopbackHttpUrl(binding.endpoint)
7121
- && /^sgb_[A-Za-z0-9_-]{32}$/.test(binding.solanaGatewayBindingId)
7122
- && binding.cluster.trim().length > 0
7123
- && binding.region.trim().length > 0
7124
- && auth
7125
- && typeof auth.required === 'boolean'
7126
- && auth.mode === binding.authPolicy
7127
- && isSecureOrLoopbackHttpUrl(auth.sessionEndpoint)
7128
- && isSecureOrLoopbackHttpUrl(auth.jwksUrl)
7129
- && auth.tokenTransport === 'bearer'
7130
- && auth.audience === 'arete:solana-gateway'
7131
- && auth.targetKind === 'solana-gateway-binding'
7132
- && auth.targetId === binding.solanaGatewayBindingId
7133
- && Array.isArray(auth.scopes)
7134
- && requiredScopes.every((scope) => auth.scopes.includes(scope))
7135
- && Array.isArray(auth.acceptedKeyClasses)
7136
- && typeof auth.transactionEntitlementRequired === 'boolean';
7137
- if (!complete) {
7138
- throw new AreteError(`Hosted Solana gateway ${capability} binding is incomplete or inconsistent`, 'INVALID_CONFIG');
7139
- }
7140
- }
7141
- function resolveFetch(fetchImpl) {
7142
- if (fetchImpl)
7143
- return fetchImpl;
7144
- if (typeof globalThis.fetch === 'function')
7145
- return globalThis.fetch.bind(globalThis);
7146
- throw new AreteError('A fetch implementation is required for hosted Solana gateway transports', 'INVALID_CONFIG');
7147
- }
7148
- function hasRuntimeAuthStrategy(auth) {
7149
- return auth?.token !== undefined
7150
- || auth?.getToken !== undefined
7151
- || auth?.tokenEndpoint !== undefined;
7152
- }
7153
- function bindingAuthConfig(binding, runtimeAuth) {
7154
- if (hasRuntimeAuthStrategy(runtimeAuth))
7155
- return runtimeAuth;
7156
- if (!binding.auth.required)
7157
- return runtimeAuth;
7158
- return { ...runtimeAuth, tokenEndpoint: binding.auth.sessionEndpoint };
7159
- }
7160
- function refreshableErrorCode(response) {
7161
- const wireCode = response.headers.get('X-Error-Code');
7162
- return wireCode !== null
7163
- && shouldRefreshToken(parseErrorCode(wireCode.trim().replace(/_/g, '-')));
7164
- }
7165
- /**
7166
- * Construct explicit hosted chain and transaction transports from generated
7167
- * gateway descriptors. Tokens are isolated by exact binding target and scope.
7168
- */
7169
- function createHostedSolanaGatewayTransports(bindings, options = {}) {
7170
- validateBinding('chain', bindings.chain, ['read']);
7171
- validateBinding('transactions', bindings.transactions, ['transaction:inspect', 'transaction:send']);
7172
- const fetchImpl = resolveFetch(options.fetch);
7173
- const managers = new Map();
7174
- const bindingAuthentication = (binding) => {
7175
- const auth = bindingAuthConfig(binding, options.auth);
7176
- if (auth?.token !== undefined) {
7177
- if (!auth.token) {
7178
- throw new AreteError('Authentication token is empty', 'TOKEN_INVALID');
7179
- }
7180
- return {
7181
- staticToken: auth.token,
7182
- target: {
7183
- targetKind: 'solana-gateway-binding',
7184
- targetId: binding.solanaGatewayBindingId,
7185
- },
7186
- };
7187
- }
7188
- let manager;
7189
- if (auth && (auth.getToken || auth.tokenEndpoint)) {
7190
- const identity = hasRuntimeAuthStrategy(options.auth)
7191
- ? 'runtime-auth-strategy'
7192
- : `session-endpoint:${binding.auth.sessionEndpoint}`;
7193
- manager = managers.get(identity);
7194
- if (!manager) {
7195
- manager = new ConnectionManager({ websocketUrl: null, auth, fetch: fetchImpl });
7196
- managers.set(identity, manager);
7197
- }
7198
- }
7199
- return {
7200
- manager,
7201
- target: {
7202
- targetKind: 'solana-gateway-binding',
7203
- targetId: binding.solanaGatewayBindingId,
7204
- },
7205
- };
7206
- };
7207
- const authenticatedFetch = (authentication) => async (input, init, scope, requirePreDispatchMarker) => {
7208
- const request = { ...authentication.target, scopes: [scope] };
7209
- const attempt = async (forceRefresh) => {
7210
- const token = authentication.staticToken
7211
- ?? (authentication.manager
7212
- ? await authentication.manager.getHttpAuthToken(request, forceRefresh)
7213
- : undefined);
7214
- const headers = new Headers(init?.headers);
7215
- if (token)
7216
- headers.set('authorization', `Bearer ${token}`);
7217
- return fetchImpl(input, { ...init, headers });
7218
- };
7219
- let response = await attempt(false);
7220
- const explicitlyNotDispatched = response.headers.get('X-Arete-Upstream-Attempted') === 'false';
7221
- if (!response.ok
7222
- && authentication.manager
7223
- && refreshableErrorCode(response)
7224
- && (!requirePreDispatchMarker || explicitlyNotDispatched)) {
7225
- authentication.manager.clearHttpAuthToken(request);
7226
- response = await attempt(true);
7227
- }
7228
- return response;
7229
- };
7230
- const chainAuthentication = bindingAuthentication(bindings.chain);
7231
- const transactionAuthentication = bindingAuthentication(bindings.transactions);
7232
- const chainFetch = authenticatedFetch(chainAuthentication);
7233
- const transactionFetch = authenticatedFetch(transactionAuthentication);
7234
- return {
7235
- chain: createChainClient(bindings.chain.endpoint, ((input, init) => chainFetch(typeof input === 'string'
7236
- ? input
7237
- : input instanceof URL ? input.toString() : input.url, init, 'read', false))),
7238
- transactions: createTransactionTransport(bindings.transactions.endpoint, (input, init, scope, requirePreDispatchMarker) => transactionFetch(input, init, scope, requirePreDispatchMarker)),
7239
- };
7240
- }
7241
-
7242
7266
  function chainAccountLoader(chain) {
7243
7267
  return {
7244
7268
  async getAccount(address) {