Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
42f486d
feat(transaction-introspection): add package for inspecting confirmed…
amilz May 8, 2026
e6e54e5
refactor(transaction-introspection): tighten decode return type and t…
amilz May 13, 2026
8a8c2f9
refactor(transaction-introspection): flatten walk API, drop separate …
amilz May 20, 2026
3810c4a
feat(transaction-introspection): add v1 transaction message support
amilz May 20, 2026
943e67b
feat(errors): add dedicated codes for instruction account-index and u…
amilz Jun 11, 2026
c7aa23c
fix(transaction-introspection): decode v1 JSON responses and reject u…
amilz Jun 11, 2026
b7f014d
refactor(transaction-introspection): use rpc-api getTransaction respo…
amilz Jun 11, 2026
67ac9c8
refactor(transaction-introspection): drop getAllAddressesFromCompiled…
amilz Jun 11, 2026
9b6530e
refactor(transaction-introspection): omit empty accounts and data fro…
amilz Jun 11, 2026
20eba80
feat(transaction-introspection): interleave inner instructions in wal…
amilz Jun 11, 2026
9b87001
test(transaction-introspection): decode a real v0 wire transaction
amilz Jun 11, 2026
6626373
chore: expand changeset and drop personal editor entries from .gitignore
amilz Jun 11, 2026
218e46b
fix(transaction-introspection): identify jsonParsed responses by miss…
amilz Jun 12, 2026
5bf1878
fix(transaction-introspection): omit empty addressTableLookups from J…
amilz Jun 12, 2026
cfd573c
docs(transaction-introspection): narrow accounts and data in examples…
amilz Jun 12, 2026
cf9468f
chore(transaction-introspection): align package version, deps, and Ty…
amilz Jun 12, 2026
7e80081
fix(transaction-introspection): harden v1 and inner-instruction edge …
amilz Jun 12, 2026
1703af5
chore: expand null check
amilz Jun 16, 2026
96dc50c
chore: add codama example to readme
amilz Jun 16, 2026
92c66d6
test(transaction-introspection): address post-review nits
amilz Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .changeset/crisp-squids-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
'@solana/transaction-introspection': minor
'@solana/errors': minor
'@solana/rpc-api': minor
'@solana/kit': minor
---

Add `@solana/transaction-introspection`, a new package that bridges a `getTransaction` response and the auto-generated `@solana-program/*` `parseXInstruction` clients. Decodes the transaction (`encoding: 'base64'`, `'base58'`, or `'json'`), resolves account indices against static + ALT-loaded addresses, normalizes inner instructions from `meta.innerInstructions`, and exposes `walkInstructions` to enumerate every instruction in display order — each outer instruction followed by its inner instructions — with a `trace` recording its location. Each returned instruction is a `ResolvedInstruction & { trace }` directly usable with `isInstructionForProgram` from `@solana/instructions` and with the auto-generated `identifyXInstruction` / `parseXInstruction` helpers. Supports `legacy`, `v0`, and `v1` compiled transaction messages. Re-exported from `@solana/kit`.

```ts
import { createSolanaRpc, signature } from '@solana/kit';
import { isInstructionForProgram } from '@solana/instructions';
import { decodeTransactionFromRpcResponse, walkInstructions } from '@solana/transaction-introspection';
import { identifyTokenInstruction, TOKEN_PROGRAM_ADDRESS, TokenInstruction } from '@solana-program/token';

const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const rpcTx = await rpc
.getTransaction(signature(txid), {
commitment: 'confirmed',
encoding: 'base64',
maxSupportedTransactionVersion: 0,
})
.send();
if (!rpcTx) throw new Error(`Transaction ${txid} not found`);

const { compiledMessage, loadedAddresses } = decodeTransactionFromRpcResponse(rpcTx);

for (const ix of walkInstructions({ compiledMessage, loadedAddresses, meta: rpcTx.meta })) {
if (!isInstructionForProgram(ix, TOKEN_PROGRAM_ADDRESS)) continue;
if (identifyTokenInstruction(ix) === TokenInstruction.SyncNative) {
console.log('SyncNative found at', ix.trace);
}
}
```

`@solana/rpc-api` now exports the non-null `getTransaction` response shapes as named types (`GetTransactionApiResponseBase58`, `GetTransactionApiResponseBase64`, `GetTransactionApiResponseJson`, `GetTransactionApiResponseJsonParsed`), which `decodeTransactionFromRpcResponse` accepts as inputs. `@solana/errors` gains `SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE` plus a new `TRANSACTION_INTROSPECTION` domain (`SOLANA_ERROR__TRANSACTION_INTROSPECTION__CANNOT_DECODE_JSON_PARSED_TRANSACTION`, `SOLANA_ERROR__TRANSACTION_INTROSPECTION__UNRECOGNIZED_GET_TRANSACTION_RESPONSE`).
9 changes: 9 additions & 0 deletions packages/errors/src/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ export const SOLANA_ERROR__TRANSACTION__TOO_MANY_INSTRUCTIONS = 5663034;
export const SOLANA_ERROR__TRANSACTION__TOO_MANY_ACCOUNTS_IN_INSTRUCTION = 5663035;
export const SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_LOADED_ACCOUNTS_DATA_SIZE_LIMIT = 5663036;
export const SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_RESOURCE_LIMITS = 5663037;
export const SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE = 5663038;

// Transaction-introspection-related errors.
// Reserve error codes in the range [5664000-5664999].
export const SOLANA_ERROR__TRANSACTION_INTROSPECTION__CANNOT_DECODE_JSON_PARSED_TRANSACTION = 5664000;
export const SOLANA_ERROR__TRANSACTION_INTROSPECTION__UNRECOGNIZED_GET_TRANSACTION_RESPONSE = 5664001;

// Transaction errors.
// Reserve error codes starting with [7050000-7050999] for the Rust enum `TransactionError`.
Expand Down Expand Up @@ -673,6 +679,7 @@ export type SolanaErrorCode =
| typeof SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_CONTENTS_MISSING
| typeof SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_INDEX_OUT_OF_RANGE
| typeof SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_FEE_PAYER_MISSING
| typeof SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE
| typeof SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND
| typeof SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_COMPUTE_LIMIT
| typeof SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_LOADED_ACCOUNTS_DATA_SIZE_LIMIT
Expand Down Expand Up @@ -737,6 +744,8 @@ export type SolanaErrorCode =
| typeof SOLANA_ERROR__TRANSACTION_ERROR__WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT
| typeof SOLANA_ERROR__TRANSACTION_ERROR__WOULD_EXCEED_MAX_BLOCK_COST_LIMIT
| typeof SOLANA_ERROR__TRANSACTION_ERROR__WOULD_EXCEED_MAX_VOTE_COST_LIMIT
| typeof SOLANA_ERROR__TRANSACTION_INTROSPECTION__CANNOT_DECODE_JSON_PARSED_TRANSACTION
| typeof SOLANA_ERROR__TRANSACTION_INTROSPECTION__UNRECOGNIZED_GET_TRANSACTION_RESPONSE
| typeof SOLANA_ERROR__WALLET__ACCOUNT_NOT_AVAILABLE
| typeof SOLANA_ERROR__WALLET__NO_SIGNER_CONNECTED
| typeof SOLANA_ERROR__WALLET__NOT_CONNECTED
Expand Down
4 changes: 4 additions & 0 deletions packages/errors/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ import {
SOLANA_ERROR__TRANSACTION__EXCEEDS_SIZE_LIMIT,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_CONTENTS_MISSING,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_INDEX_OUT_OF_RANGE,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND,
SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT,
SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_RESOURCE_LIMITS,
Expand Down Expand Up @@ -885,6 +886,9 @@ export type SolanaErrorContext = ReadonlyContextValue<
highestRequestedIndex: number;
lookupTableAddress: string;
};
[SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE]: {
index: number;
};
[SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND]: {
index: number;
};
Expand Down
9 changes: 9 additions & 0 deletions packages/errors/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ import {
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_CONTENTS_MISSING,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_INDEX_OUT_OF_RANGE,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_FEE_PAYER_MISSING,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND,
SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_COMPUTE_LIMIT,
SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
Expand Down Expand Up @@ -316,6 +317,8 @@ import {
SOLANA_ERROR__TRANSACTION_ERROR__WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT,
SOLANA_ERROR__TRANSACTION_ERROR__WOULD_EXCEED_MAX_BLOCK_COST_LIMIT,
SOLANA_ERROR__TRANSACTION_ERROR__WOULD_EXCEED_MAX_VOTE_COST_LIMIT,
SOLANA_ERROR__TRANSACTION_INTROSPECTION__CANNOT_DECODE_JSON_PARSED_TRANSACTION,
SOLANA_ERROR__TRANSACTION_INTROSPECTION__UNRECOGNIZED_GET_TRANSACTION_RESPONSE,
SOLANA_ERROR__WALLET__ACCOUNT_NOT_AVAILABLE,
SOLANA_ERROR__WALLET__NO_SIGNER_CONNECTED,
SOLANA_ERROR__WALLET__NOT_CONNECTED,
Expand Down Expand Up @@ -882,6 +885,12 @@ export const SolanaErrorMessages: Readonly<{
'Transaction has $actualCount instructions but the maximum allowed is $maxAllowed',
[SOLANA_ERROR__TRANSACTION__TOO_MANY_ACCOUNTS_IN_INSTRUCTION]:
'The instruction at index $instructionIndex has $actualCount account references but the maximum allowed is $maxAllowed',
[SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE]:
'Could not find an account address at index $index while decompiling an instruction',
[SOLANA_ERROR__TRANSACTION_INTROSPECTION__CANNOT_DECODE_JSON_PARSED_TRANSACTION]:
"`getTransaction` responses fetched with `encoding: 'jsonParsed'` cannot be decoded. Re-fetch the transaction with `encoding: 'base64'`, `'base58'`, or `'json'`",
[SOLANA_ERROR__TRANSACTION_INTROSPECTION__UNRECOGNIZED_GET_TRANSACTION_RESPONSE]:
"Could not recognize the shape of this `getTransaction` response. Expected a response fetched with `encoding: 'base64'`, `'base58'`, or `'json'`",
[SOLANA_ERROR__WALLET__NOT_CONNECTED]: 'Cannot $operation: no wallet connected',
[SOLANA_ERROR__WALLET__NO_SIGNER_CONNECTED]: 'No signing wallet connected (status: $status)',
[SOLANA_ERROR__WALLET__SIGNER_NOT_AVAILABLE]: 'Connected wallet does not support signing',
Expand Down
1 change: 1 addition & 0 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
"@solana/signers": "workspace:*",
"@solana/sysvars": "workspace:*",
"@solana/transaction-confirmation": "workspace:*",
"@solana/transaction-introspection": "workspace:*",
"@solana/transaction-messages": "workspace:*",
"@solana/transactions": "workspace:*"
},
Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * from '@solana/rpc-parsed-types';
export * from '@solana/rpc-subscriptions';
export * from '@solana/rpc-types';
export * from '@solana/signers';
export * from '@solana/transaction-introspection';
export * from '@solana/transaction-messages';
export * from '@solana/transactions';
export * from './create-async-generator-with-initial-value-and-slot-tracking';
Expand Down
129 changes: 69 additions & 60 deletions packages/rpc-api/src/getTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,71 @@ type TransactionAddressTableLookups = Readonly<{
}>;
}>;

/**
* Common envelope for every non-null `getTransaction` response, regardless of
* encoding. The conditional shape mirrors the API: when
* `TMaxSupportedTransactionVersion` is unset (`void`), the response carries no
* `version` field; when set, the resolved `TransactionVersion` is included.
*/
type GetTransactionApiResponseEnvelope<TMaxSupportedTransactionVersion extends TransactionVersion | void = void> =
GetTransactionApiResponseBase &
(TMaxSupportedTransactionVersion extends void ? Record<string, never> : { version: TransactionVersion });

/**
* The non-parsed `meta` shape returned with `encoding: 'base64'`, `'base58'`, or
* `'json'`. `loadedAddresses` is only populated when
* `TMaxSupportedTransactionVersion` is set on the request.
*/
type TransactionMetaNotParsed<TMaxSupportedTransactionVersion extends TransactionVersion | void = void> =
| (TransactionMetaBase &
TransactionMetaInnerInstructionsNotParsed &
(TMaxSupportedTransactionVersion extends void ? Record<string, never> : TransactionMetaLoadedAddresses))
| null;

/**
* The shape of a non-null `getTransaction` response when called with
* `encoding: 'base64'`.
*/
export type GetTransactionApiResponseBase64<TMaxSupportedTransactionVersion extends TransactionVersion | void = void> =
GetTransactionApiResponseEnvelope<TMaxSupportedTransactionVersion> & {
meta: TransactionMetaNotParsed<TMaxSupportedTransactionVersion>;
transaction: Base64EncodedDataResponse;
};

/**
* The shape of a non-null `getTransaction` response when called with
* `encoding: 'base58'`.
*/
export type GetTransactionApiResponseBase58<TMaxSupportedTransactionVersion extends TransactionVersion | void = void> =
GetTransactionApiResponseEnvelope<TMaxSupportedTransactionVersion> & {
meta: TransactionMetaNotParsed<TMaxSupportedTransactionVersion>;
transaction: Base58EncodedDataResponse;
};

/**
* The shape of a non-null `getTransaction` response when called with
* `encoding: 'json'` (the default).
*/
export type GetTransactionApiResponseJson<TMaxSupportedTransactionVersion extends TransactionVersion | void = void> =
GetTransactionApiResponseEnvelope<TMaxSupportedTransactionVersion> & {
meta: TransactionMetaNotParsed<TMaxSupportedTransactionVersion>;
transaction: TransactionJson &
(TMaxSupportedTransactionVersion extends void ? Record<string, never> : TransactionAddressTableLookups);
};

/**
* The shape of a non-null `getTransaction` response when called with
* `encoding: 'jsonParsed'`. Inner instructions and instruction data are
* parsed by the server when a parser is registered for the program.
*/
export type GetTransactionApiResponseJsonParsed<
TMaxSupportedTransactionVersion extends TransactionVersion | void = void,
> = GetTransactionApiResponseEnvelope<TMaxSupportedTransactionVersion> & {
meta: (TransactionMetaBase & TransactionMetaInnerInstructionsParsed) | null;
transaction: TransactionJsonParsed &
(TMaxSupportedTransactionVersion extends void ? Record<string, never> : TransactionAddressTableLookups);
};

export type GetTransactionApi = {
/**
* Returns details of the confirmed transaction identified by the given signature.
Expand All @@ -318,18 +383,7 @@ export type GetTransactionApi = {
Readonly<{
encoding: 'jsonParsed';
}>,
):
| (GetTransactionApiResponseBase &
(TMaxSupportedTransactionVersion extends void
? Record<string, never>
: { version: TransactionVersion }) & {
meta: (TransactionMetaBase & TransactionMetaInnerInstructionsParsed) | null;
transaction: TransactionJsonParsed &
(TMaxSupportedTransactionVersion extends void
? Record<string, never>
: TransactionAddressTableLookups);
})
| null;
): GetTransactionApiResponseJsonParsed<TMaxSupportedTransactionVersion> | null;
/**
* Returns details of the confirmed transaction identified by the given signature.
*
Expand All @@ -349,21 +403,7 @@ export type GetTransactionApi = {
Readonly<{
encoding: 'base64';
}>,
):
| (GetTransactionApiResponseBase &
(TMaxSupportedTransactionVersion extends void
? Record<string, never>
: { version: TransactionVersion }) & {
meta:
| (TransactionMetaBase &
TransactionMetaInnerInstructionsNotParsed &
(TMaxSupportedTransactionVersion extends void
? Record<string, never>
: TransactionMetaLoadedAddresses))
| null;
transaction: Base64EncodedDataResponse;
})
| null;
): GetTransactionApiResponseBase64<TMaxSupportedTransactionVersion> | null;
/**
* Returns details of the confirmed transaction identified by the given signature.
*
Expand All @@ -383,21 +423,7 @@ export type GetTransactionApi = {
Readonly<{
encoding: 'base58';
}>,
):
| (GetTransactionApiResponseBase &
(TMaxSupportedTransactionVersion extends void
? Record<string, never>
: { version: TransactionVersion }) & {
meta:
| (TransactionMetaBase &
TransactionMetaInnerInstructionsNotParsed &
(TMaxSupportedTransactionVersion extends void
? Record<string, never>
: TransactionMetaLoadedAddresses))
| null;
transaction: Base58EncodedDataResponse;
})
| null;
): GetTransactionApiResponseBase58<TMaxSupportedTransactionVersion> | null;
/**
* Returns details of the confirmed transaction identified by the given signature.
*
Expand All @@ -416,22 +442,5 @@ export type GetTransactionApi = {
Readonly<{
encoding?: 'json';
}>,
):
| (GetTransactionApiResponseBase &
(TMaxSupportedTransactionVersion extends void
? Record<string, never>
: { version: TransactionVersion }) & {
meta:
| (TransactionMetaBase &
TransactionMetaInnerInstructionsNotParsed &
(TMaxSupportedTransactionVersion extends void
? Record<string, never>
: TransactionMetaLoadedAddresses))
| null;
transaction: TransactionJson &
(TMaxSupportedTransactionVersion extends void
? Record<string, never>
: TransactionAddressTableLookups);
})
| null;
): GetTransactionApiResponseJson<TMaxSupportedTransactionVersion> | null;
};
12 changes: 11 additions & 1 deletion packages/rpc-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ import { GetTokenAccountsByDelegateApi } from './getTokenAccountsByDelegate';
import { GetTokenAccountsByOwnerApi } from './getTokenAccountsByOwner';
import { GetTokenLargestAccountsApi } from './getTokenLargestAccounts';
import { GetTokenSupplyApi } from './getTokenSupply';
import { GetTransactionApi } from './getTransaction';
import {
GetTransactionApi,
GetTransactionApiResponseBase58,
GetTransactionApiResponseBase64,
GetTransactionApiResponseJson,
GetTransactionApiResponseJsonParsed,
} from './getTransaction';
import { GetTransactionCountApi } from './getTransactionCount';
import { GetVersionApi } from './getVersion';
import { GetVoteAccountsApi } from './getVoteAccounts';
Expand Down Expand Up @@ -212,6 +218,10 @@ export type {
GetTokenLargestAccountsApi,
GetTokenSupplyApi,
GetTransactionApi,
GetTransactionApiResponseBase58,
GetTransactionApiResponseBase64,
GetTransactionApiResponseJson,
GetTransactionApiResponseJsonParsed,
GetTransactionCountApi,
GetVersionApi,
GetVoteAccountsApi,
Expand Down
2 changes: 2 additions & 0 deletions packages/transaction-introspection/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.docs/
dist/
20 changes: 20 additions & 0 deletions packages/transaction-introspection/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) 2023 Solana Labs, Inc

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Loading
Loading