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