Skip to content

Commit

Permalink
Merge branch 'main' into v0.11.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Shaptic committed Aug 21, 2023
2 parents 5a012bb + 4eedb93 commit 020843f
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 54 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ A breaking change should be clearly marked in this log.

### Breaking Changes
* The minimum supported NodeJS version is now Node 16.
<<<<<<< HEAD
* `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 ([#127](https://github.com/stellar/js-soroban-client/pull/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.
Expand Down
101 changes: 72 additions & 29 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,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 @@ -178,7 +178,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 @@ -207,24 +207,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 @@ -283,18 +289,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 @@ -343,8 +386,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 @@ -445,11 +486,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
143 changes: 120 additions & 23 deletions test/unit/server/get_transaction_test.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
const {
xdr,
Keypair,
Account,
Server,
TransactionBuilder,
nativeToScVal,
XdrLargeInt
} = SorobanClient;

describe('Server#getTransaction', function () {
let keypair = SorobanClient.Keypair.random();
let keypair = Keypair.random();
let account = new SorobanClient.Account(
keypair.publicKey(),
'56199647068161'
);

beforeEach(function () {
this.server = new SorobanClient.Server(serverUrl);
this.server = new Server(serverUrl);
this.axiosMock = sinon.mock(AxiosClient);
let transaction = new SorobanClient.TransactionBuilder(account, {
let transaction = new TransactionBuilder(account, {
fee: 100,
networkPassphrase: SorobanClient.Networks.TESTNET,
v1: true
Expand All @@ -28,6 +38,17 @@ describe('Server#getTransaction', function () {
this.transaction = transaction;
this.hash = this.transaction.hash().toString('hex');
this.blob = transaction.toEnvelope().toXDR().toString('base64');
this.prepareAxios = (result) => {
this.axiosMock
.expects('post')
.withArgs(serverUrl, {
jsonrpc: '2.0',
id: 1,
method: 'getTransaction',
params: [this.hash]
})
.returns(Promise.resolve({ data: { id: 1, result } }));
};
});

afterEach(function () {
Expand All @@ -36,32 +57,108 @@ describe('Server#getTransaction', function () {
});

it('transaction not found', function (done) {
const result = {
status: 'NOT_FOUND',
latestLedger: 100,
latestLedgerCloseTime: 12345,
oldestLedger: 50,
oldestLedgerCloseTime: 500
};
this.axiosMock
.expects('post')
.withArgs(serverUrl, {
jsonrpc: '2.0',
id: 1,
method: 'getTransaction',
params: [this.hash]
const result = makeTxResult('NOT_FOUND');
this.prepareAxios(result);

this.server
.getTransaction(this.hash)
.then(function (response) {
expect(response).to.be.deep.equal(result);
done();
})
.returns(Promise.resolve({ data: { id: 1, result } }));
.catch((err) => done(err));
});

it('transaction success', function (done) {
const result = makeTxResult('SUCCESS', true);
this.prepareAxios(result);

this.server.getTransaction(this.hash).then(function (response) {
expect(response).to.be.deep.equal(result);
done();
let expected = JSON.parse(JSON.stringify(result));
[
['envelopeXdr', xdr.TransactionEnvelope],
['resultXdr', xdr.TransactionResult],
['resultMetaXdr', xdr.TransactionMeta]
].forEach(([field, struct]) => {
expected[field] = struct.fromXDR(result[field], 'base64');
});
expected.returnValue = expected.resultMetaXdr
.v3()
.sorobanMeta()
.returnValue();

this.server
.getTransaction(this.hash)
.then((resp) => {
expect(Object.keys(resp)).to.eql(Object.keys(expected));
expect(resp).to.eql(expected);
expect(resp.returnValue).to.eql(new XdrLargeInt('u64', 1234).toScVal());
done();
})
.catch((err) => done(err));
});

xit('transaction pending', function (done) {});
xit('non-Soroban transaction success', function (done) {
const result = makeTxResult('SUCCESS', false);
this.prepareAxios(result);

xit('transaction success', function (done) {});
this.server
.getTransaction(this.hash)
.then((resp) => {
expect(resp).to.be.deep.equal(result);
done();
})
.catch((err) => done(err));
});

xit('transaction pending', function (done) {});
xit('transaction error', function (done) {});
});

function makeTxResult(status, addSoroban = true) {
const metaV3 = new xdr.TransactionMeta(
3,
new xdr.TransactionMetaV3({
ext: new xdr.ExtensionPoint(0),
txChangesBefore: [],
operations: [],
txChangesAfter: [],
sorobanMeta: new xdr.SorobanTransactionMeta({
ext: new xdr.ExtensionPoint(0),
events: [],
diagnosticEvents: [],
returnValue: nativeToScVal(1234)
})
})
);

// only injected in the success case
//
// this data was picked from a random transaction in horizon:
// aa6a8e198abe53c7e852e4870413b29fe9ef04da1415a97a5de1a4ae489e11e2
const successInfo = {
ledger: 1234,
createdAt: 123456789010,
applicationOrder: 2,
feeBump: false,
envelopeXdr:
'AAAAAgAAAAAT/LQZdYz0FcQ4Xwyg8IM17rkUx3pPCCWLu+SowQ/T+gBLB24poiQa9iwAngAAAAEAAAAAAAAAAAAAAABkwdeeAAAAAAAAAAEAAAABAAAAAC/9E8hDhnktyufVBS5tqA734Yz5XrLX2XNgBgH/YEkiAAAADQAAAAAAAAAAAAA1/gAAAAAv/RPIQ4Z5Lcrn1QUubagO9+GM+V6y19lzYAYB/2BJIgAAAAAAAAAAAAA1/gAAAAQAAAACU0lMVkVSAAAAAAAAAAAAAFDutWuu6S6UPJBrotNSgfmXa27M++63OT7TYn1qjgy+AAAAAVNHWAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AAAACUEFMTEFESVVNAAAAAAAAAFDutWuu6S6UPJBrotNSgfmXa27M++63OT7TYn1qjgy+AAAAAlNJTFZFUgAAAAAAAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAAAAAAAACwQ/T+gAAAEA+ztVEKWlqHXNnqy6FXJeHr7TltHzZE6YZm5yZfzPIfLaqpp+5cyKotVkj3d89uZCQNsKsZI48uoyERLne+VwL/2BJIgAAAEA7323gPSaezVSa7Vi0J4PqsnklDH1oHLqNBLwi5EWo5W7ohLGObRVQZ0K0+ufnm4hcm9J4Cuj64gEtpjq5j5cM',
resultXdr:
'AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAANAAAAAAAAAAUAAAACZ4W6fmN63uhVqYRcHET+D2NEtJvhCIYflFh9GqtY+AwAAAACU0lMVkVSAAAAAAAAAAAAAFDutWuu6S6UPJBrotNSgfmXa27M++63OT7TYn1qjgy+AAAYW0toL2gAAAAAAAAAAAAANf4AAAACcgyAkXD5kObNTeRYciLh7R6ES/zzKp0n+cIK3Y6TjBkAAAABU0dYAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAGlGnIJrXAAAAAlNJTFZFUgAAAAAAAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAGFtLaC9oAAAAApmc7UgUBInrDvij8HMSridx2n1w3I8TVEn4sLr1LSpmAAAAAlBBTExBRElVTQAAAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAIUz88EqYAAAAAVNHWAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AABpRpyCa1wAAAAKYUsaaCZ233xB1p+lG7YksShJWfrjsmItbokiR3ifa0gAAAAJTSUxWRVIAAAAAAAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AABv52PPa5wAAAAJQQUxMQURJVU0AAAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AACFM/PBKmAAAAAJnhbp+Y3re6FWphFwcRP4PY0S0m+EIhh+UWH0aq1j4DAAAAAAAAAAAAAA9pAAAAAJTSUxWRVIAAAAAAAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AABv52PPa5wAAAAAv/RPIQ4Z5Lcrn1QUubagO9+GM+V6y19lzYAYB/2BJIgAAAAAAAAAAAAA9pAAAAAA=',
resultMetaXdr: metaV3.toXDR('base64')
};

if (!addSoroban) {
// replace the V3 Soroban meta with a "classic" V2 version
successInfo.resultMetaXdr =
'AAAAAgAAAAIAAAADAtL5awAAAAAAAAAAS0CFMhOtWUKJWerx66zxkxORaiH6/3RUq7L8zspD5RoAAAAAAcm9QAKVkpMAAHpMAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAC0vi5AAAAAGTB02oAAAAAAAAAAQLS+WsAAAAAAAAAAEtAhTITrVlCiVnq8eus8ZMTkWoh+v90VKuy/M7KQ+UaAAAAAAHJvUAClZKTAAB6TQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAtL5awAAAABkwdd1AAAAAAAAAAEAAAAGAAAAAwLS+VQAAAACAAAAAG4cwu71zHNXx3jHCzRGOIthcnfwRgfN2f/AoHFLLMclAAAAAEySDkgAAAAAAAAAAkJVU0lORVNTAAAAAAAAAAC3JfDeo9vreItKNPoe74EkFIqWybeUQNFvLvURhHtskAAAAAAeQtHTL5f6TAAAXH0AAAAAAAAAAAAAAAAAAAABAtL5awAAAAIAAAAAbhzC7vXMc1fHeMcLNEY4i2Fyd/BGB83Z/8CgcUssxyUAAAAATJIOSAAAAAAAAAACQlVTSU5FU1MAAAAAAAAAALcl8N6j2+t4i0o0+h7vgSQUipbJt5RA0W8u9RGEe2yQAAAAAB5C0dNHf4CAAACLCQAAAAAAAAAAAAAAAAAAAAMC0vlUAAAAAQAAAABuHMLu9cxzV8d4xws0RjiLYXJ38EYHzdn/wKBxSyzHJQAAAAJCVVNJTkVTUwAAAAAAAAAAtyXw3qPb63iLSjT6Hu+BJBSKlsm3lEDRby71EYR7bJAAAAAAAABAL3//////////AAAAAQAAAAEAE3H3TnhnuQAAAAAAAAAAAAAAAAAAAAAAAAABAtL5awAAAAEAAAAAbhzC7vXMc1fHeMcLNEY4i2Fyd/BGB83Z/8CgcUssxyUAAAACQlVTSU5FU1MAAAAAAAAAALcl8N6j2+t4i0o0+h7vgSQUipbJt5RA0W8u9RGEe2yQAAAAAAAAQC9//////////wAAAAEAAAABABNx9J6Z4RkAAAAAAAAAAAAAAAAAAAAAAAAAAwLS+WsAAAAAAAAAAG4cwu71zHNXx3jHCzRGOIthcnfwRgfN2f/AoHFLLMclAAAAH37+zXQCXdRTAAASZAAAApIAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAABbBXKIigAAABhZWyiOAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAtL0awAAAABkwbqrAAAAAAAAAAEC0vlrAAAAAAAAAABuHMLu9cxzV8d4xws0RjiLYXJ38EYHzdn/wKBxSyzHJQAAAB9+/s10Al3UUwAAEmQAAAKSAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAWwVyiIoAAAAYWVsojgAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAALS9GsAAAAAZMG6qwAAAAAAAAAA';
}

return {
status,
latestLedger: 100,
latestLedgerCloseTime: 12345,
oldestLedger: 50,
oldestLedgerCloseTime: 500,
...(status === 'SUCCESS' && successInfo)
};
}

0 comments on commit 020843f

Please sign in to comment.