Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically decode XDR values in getTransaction #129

Merged
merged 7 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 10 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ A breaking change should be clearly marked in this log.


### Breaking Changes
* The minimum supported NodeJS version is now Node 16.
* `Server.prepareTransaction` now returns a `TransactionBuilder` instance rather than an immutable `Transaction`, in order to facilitate modifying your transaction after assembling it alongside the simulation response ([https://github.com/stellar/js-soroban-client/pull/127](#127)).
- The intent is to avoid cloning the transaction again (via `TransactionBuilder.cloneFrom`) if you need to modify parameters such as the storage access footprint.
- To migrate your code, just call `.build()` on the return value.
* The RPC response schemas for simulation have been upgraded to parse the base64-encoded XDR automatically. The full interface changes are in the pull request ([https://github.com/stellar/js-soroban-client/pull/127](#127)), but succinctly:
* The RPC response schemas for simulation (see `Server.simulateTransaction()`) have been upgraded to parse the base64-encoded XDR automatically. The full interface changes are in the pull request ([https://github.com/stellar/js-soroban-client/pull/127](#127)), but succinctly:
- `SimulateTransactionResponse` -> `RawSimulateTransactionResponse`
- `SimulateHostFunctionResult` -> `RawSimulateHostFunctionResult`
- Now, `SimulateTransactionResponse` and `SimulateHostFunctionResult` now include the full, decoded XDR structures instead of raw, base64-encoded strings for the relevant fields (e.g. `SimulateTransactionResponse.transactionData` is now an instance of `SorobanDataBuilder`, `events` is now an `xdr.DiagnosticEvent[]` [try out `humanizeEvents` for a friendlier representation of this field])
- The `SimulateTransactionResponse.results[]` field has been moved to `SimulateTransactionResponse.result?`, since it will always be exactly zero or one result.

Not all schemas have been broken in this manner in order to facilitate user feedback on this approach. Please add your :+1: or :-1: to [#128](https://github.com/stellar/js-soroban-client/issues/128) to provide your perspective on whether or not we should do this for the other response schemas.
- Now, `SimulateTransactionResponse` and `SimulateHostFunctionResult` include the full, decoded XDR structures instead of raw, base64-encoded strings for the relevant fields (e.g. `SimulateTransactionResponse.transactionData` is now an instance of `SorobanDataBuilder`, `events` is now an `xdr.DiagnosticEvent[]` [try out `humanizeEvents` for a friendlier representation of this field]).
- The `SimulateTransactionResponse.results[]` field has been moved to `SimulateTransactionResponse.result?`, since there will always be exactly zero or one result.
* The RPC response schemas for retrieving transaction details (`Server.getTransaction()`) have been upgraded to parse the base64-encoded XDR automatically. The full interface changes are in the pull request ([https://github.com/stellar/js-soroban-client/pull/129](#129)), but succinctly:
- `GetTransactionResponse` -> `RawGetTransactionResponse`
- All of the `*Xdr` properties are now full, decoded XDR structures.
- There is a new `returnValue` field which is a decoded `xdr.ScVal`, present iff the transaction was a successful Soroban function invocation.

Not all schemas have been broken in this manner in order to facilitate user feedback on this approach. Please add your :+1: or :-1: to [#128](https://github.com/stellar/js-soroban-client/issues/128) to vote on whether or not we should do this for the other response schemas.


## v0.10.1
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"@types/chai": "^4.3.5",
"@types/detect-node": "^2.0.0",
"@types/eventsource": "^1.1.2",
"@types/lodash": "^4.14.192",
"@types/lodash": "^4.14.197",
"@types/mocha": "^10.0.1",
"@types/node": "^20.4.2",
"@types/randombytes": "^2.0.0",
Expand Down
101 changes: 72 additions & 29 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export interface GetEventsRequest {
* Specifies the durability namespace of contract-related ledger entries.
*/
export enum Durability {
Temporary = 'temporary',
Persistent = 'persistent',
Temporary = "temporary",
Persistent = "persistent",
}

/**
Expand Down Expand Up @@ -182,7 +182,7 @@ export class Server {
): Promise<SorobanRpc.LedgerEntryResult> {
// coalesce `contract` param variants to an ScAddress
let scAddress: xdr.ScAddress;
if (typeof contract === 'string') {
if (typeof contract === "string") {
scAddress = new Contract(contract).address().toScAddress();
} else if (contract instanceof Address) {
scAddress = contract.toScAddress();
Expand Down Expand Up @@ -211,24 +211,30 @@ export class Server {
contract: scAddress,
key,
durability: xdrDurability,
bodyType: xdr.ContractEntryBodyType.dataEntry() // expirationExtension is internal
})
bodyType: xdr.ContractEntryBodyType.dataEntry(), // expirationExtension is internal
}),
).toXDR("base64");

return jsonrpc.post<SorobanRpc.GetLedgerEntriesResponse>(
this.serverURL.toString(),
"getLedgerEntries",
[contractKey],
).then(response => {
return jsonrpc
.post<SorobanRpc.GetLedgerEntriesResponse>(
this.serverURL.toString(),
"getLedgerEntries",
[contractKey],
)
.then((response) => {
const ledgerEntries = response.entries ?? [];
if (ledgerEntries.length !== 1) {
return Promise.reject({
code: 404,
message: `Contract data not found. Contract: ${Address.fromScAddress(scAddress).toString()}, Key: ${key.toXDR("base64")}, Durability: ${durability}`,
});
}
return ledgerEntries[0];
});
if (ledgerEntries.length !== 1) {
return Promise.reject({
code: 404,
message: `Contract data not found. Contract: ${Address.fromScAddress(
scAddress,
).toString()}, Key: ${key.toXDR(
"base64",
)}, Durability: ${durability}`,
});
}
return ledgerEntries[0];
});
}

/**
Expand Down Expand Up @@ -287,18 +293,55 @@ export class Server {
*
* @param {string} hash - The hex-encoded hash of the transaction to check.
*
* @returns {Promise<SorobanRpc.GetTransactionResponse>} Returns a
* promise to the {@link SorobanRpc.GetTransactionResponse} object
* with the status, result, and other details about the transaction.
* @returns {Promise<SorobanRpc.GetTransactionResponse>} Returns a promise to
* the {@link SorobanRpc.GetTransactionResponse} object with the status,
* result, and other details about the transaction. Raw XDR fields are
* parsed into their appropriate structures wherever possible.
*/
public async getTransaction(
hash: string,
): Promise<SorobanRpc.GetTransactionResponse> {
return await jsonrpc.post(
const raw = await jsonrpc.post<SorobanRpc.RawGetTransactionResponse>(
this.serverURL.toString(),
"getTransaction",
hash,
);

let successInfo: Omit<
SorobanRpc.GetSuccessfulTransactionResponse,
keyof SorobanRpc.GetFailedTransactionResponse
> = {} as any;

if (raw.status === SorobanRpc.GetTransactionStatus.SUCCESS) {
const meta = xdr.TransactionMeta.fromXDR(raw.resultMetaXdr!, "base64");
successInfo = {
ledger: raw.ledger!,
createdAt: raw.createdAt!,
applicationOrder: raw.applicationOrder!,
feeBump: raw.feeBump!,
envelopeXdr: xdr.TransactionEnvelope.fromXDR(
raw.envelopeXdr!,
"base64",
),
resultXdr: xdr.TransactionResult.fromXDR(raw.resultXdr!, "base64"),
resultMetaXdr: meta,
...(meta.switch() === 3 &&
meta.v3().sorobanMeta() !== null && {
returnValue: meta.v3().sorobanMeta()?.returnValue(),
}),
};
}

const result: SorobanRpc.GetTransactionResponse = {
status: raw.status,
latestLedger: raw.latestLedger,
latestLedgerCloseTime: raw.latestLedgerCloseTime,
oldestLedger: raw.oldestLedger,
oldestLedgerCloseTime: raw.oldestLedgerCloseTime,
...successInfo,
};

return result;
}

/**
Expand Down Expand Up @@ -347,8 +390,6 @@ export class Server {
// is an ScSymbol and the last is a U32.
//
// The difficulty comes in matching up the correct integer primitives.
//
// It also means this library will rely on the XDR definitions.
return await jsonrpc.postObject(this.serverURL.toString(), "getEvents", {
filters: request.filters ?? [],
pagination: {
Expand Down Expand Up @@ -449,11 +490,13 @@ export class Server {
public async simulateTransaction(
transaction: Transaction | FeeBumpTransaction,
): Promise<SorobanRpc.SimulateTransactionResponse> {
return await jsonrpc.post<SorobanRpc.RawSimulateTransactionResponse>(
this.serverURL.toString(),
"simulateTransaction",
transaction.toXDR(),
).then((raw) => parseRawSimulation(raw));
return await jsonrpc
.post<SorobanRpc.RawSimulateTransactionResponse>(
this.serverURL.toString(),
"simulateTransaction",
transaction.toXDR(),
)
.then((raw) => parseRawSimulation(raw));
}

/**
Expand Down
43 changes: 41 additions & 2 deletions src/soroban_rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,48 @@ export namespace SorobanRpc {
protocolVersion: string;
}

export type GetTransactionStatus = "SUCCESS" | "NOT_FOUND" | "FAILED";
export enum GetTransactionStatus {
SUCCESS = "SUCCESS",
NOT_FOUND = "NOT_FOUND",
FAILED = "FAILED"
}

export type GetTransactionResponse =
| GetSuccessfulTransactionResponse
| GetFailedTransactionResponse
| GetMissingTransactionResponse;

interface GetAnyTransactionResponse {
status: GetTransactionStatus;
latestLedger: number;
latestLedgerCloseTime: number;
oldestLedger: number;
oldestLedgerCloseTime: number;
}

export interface GetMissingTransactionResponse extends GetAnyTransactionResponse {
status: GetTransactionStatus.NOT_FOUND;
}

export interface GetFailedTransactionResponse extends GetAnyTransactionResponse {
status: GetTransactionStatus.FAILED;
}

export interface GetSuccessfulTransactionResponse extends GetAnyTransactionResponse {
status: GetTransactionStatus.SUCCESS;

ledger: number;
createdAt: number;
applicationOrder: number;
feeBump: boolean;
envelopeXdr: xdr.TransactionEnvelope;
resultXdr: xdr.TransactionResult;
resultMetaXdr: xdr.TransactionMeta;

returnValue?: xdr.ScVal; // present iff resultMeta is a v3
}

export interface GetTransactionResponse {
export interface RawGetTransactionResponse {
status: GetTransactionStatus;
latestLedger: number;
latestLedgerCloseTime: number;
Expand Down
Loading