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

add toXDR and fromXDR for multiauth flow #977

Merged
merged 6 commits into from
Jun 11, 2024
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ A breaking change will get clearly marked in this log.

## Unreleased

### Added
- `contract.AssembledTransaction` now has a `toXDR` and `fromXDR` method for serializing the
transaction to and from XDR. Additionally, `contract.Client` now has a `txFromXDR`. These methods
should be used in place of `AssembledTransaction.toJSON`, `AssembledTransaction.fromJSON`, and
`Client.txFromJSON` for multi-auth signing. The JSON methods are now deprecated. **Note you must now
call `simulate` on the transaction before the final `signAndSend` call after all required signatures
are gathered when using the XDR methods.

### Deprecated
- In `contract.AssembledTransaction`, `toJSON` and `fromJSON` should be replaced with `toXDR` and
`fromXDR`. Similarly, in `contract.Client`, `txFromJSON` should be replaced with `txFromXDR`.


## [v12.0.1](https://github.com/stellar/js-stellar-sdk/compare/v11.3.0...v12.0.1)

Expand Down
58 changes: 51 additions & 7 deletions src/contract/assembled_transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
implementsToString,
} from "./utils";
import { SentTransaction } from "./sent_transaction";
import { Spec } from "./spec";

export const NULL_ACCOUNT =
"GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF";
Expand Down Expand Up @@ -364,6 +365,47 @@ export class AssembledTransaction<T> {
return txn;
}

/**
* Serialize the AssembledTransaction to a base64-encoded XDR string.
*/
toXDR(): string {
if(!this.built) throw new Error(
"Transaction has not yet been simulated; " +
"call `AssembledTransaction.simulate` first.",
);
return this.built?.toEnvelope().toXDR('base64');
}

/**
* Deserialize the AssembledTransaction from a base64-encoded XDR string.
*/
static fromXDR<T>(
options: Omit<AssembledTransactionOptions<T>, "args" | "method" | "parseResultXdr">,
encodedXDR: string,
spec: Spec
): AssembledTransaction<T> {
const envelope = xdr.TransactionEnvelope.fromXDR(encodedXDR, "base64");
const built = TransactionBuilder.fromXDR(envelope, options.networkPassphrase) as Tx;
const operation = built.operations[0] as Operation.InvokeHostFunction;
if (!operation?.func?.value || typeof operation.func.value !== 'function') {
throw new Error("Could not extract the method from the transaction envelope.");
}
const invokeContractArgs = operation.func.value() as xdr.InvokeContractArgs;
if (!invokeContractArgs?.functionName) {
throw new Error("Could not extract the method name from the transaction envelope.");
}
const method = invokeContractArgs.functionName().toString('utf-8');
const txn = new AssembledTransaction(
{ ...options,
method,
parseResultXdr: (result: xdr.ScVal) =>
spec.funcResToNative(method, result),
}
);
txn.built = built;
return txn;
}

private constructor(public options: AssembledTransactionOptions<T>) {
this.options.simulate = this.options.simulate ?? true;
this.server = new Server(this.options.rpcUrl, {
Expand Down Expand Up @@ -412,14 +454,16 @@ export class AssembledTransaction<T> {
}

simulate = async (): Promise<this> => {
if (!this.raw) {
throw new Error(
"Transaction has not yet been assembled; " +
"call `AssembledTransaction.build` first.",
);
}
if (!this.built) {
if (!this.raw) {
throw new Error(
"Transaction has not yet been assembled; " +
"call `AssembledTransaction.build` first.",
);
}

this.built = this.raw.build();
this.built = this.raw.build();
}
this.simulation = await this.server.simulateTransaction(this.built);

if (Api.isSimulationSuccess(this.simulation)) {
Expand Down
3 changes: 3 additions & 0 deletions src/contract/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,8 @@ export class Client {
tx,
);
};

txFromXDR = <T>(xdrBase64: string): AssembledTransaction<T> => AssembledTransaction.fromXDR(this.options, xdrBase64, this.spec);

}

13 changes: 7 additions & 6 deletions test/e2e/src/test-swap.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,31 +100,32 @@ describe("Swap Contract Tests", function () {
expect(needsNonInvokerSigningBy.indexOf(this.context.bob.publicKey())).to.equal(1, "needsNonInvokerSigningBy does not have bob's public key!");

// root serializes & sends to alice
const jsonFromRoot = tx.toJSON();
const xdrFromRoot = tx.toXDR();
const { client: clientAlice } = await clientFor("swap", {
keypair: this.context.alice,
contractId: this.context.swapId,
});
const txAlice = clientAlice.txFromJSON(jsonFromRoot);
const txAlice = clientAlice.txFromXDR(xdrFromRoot);
await txAlice.signAuthEntries();

// alice serializes & sends to bob
const jsonFromAlice = txAlice.toJSON();
const xdrFromAlice = txAlice.toXDR();
const { client: clientBob } = await clientFor("swap", {
keypair: this.context.bob,
contractId: this.context.swapId,
});
const txBob = clientBob.txFromJSON(jsonFromAlice);
const txBob = clientBob.txFromXDR(xdrFromAlice);
await txBob.signAuthEntries();

// bob serializes & sends back to root
const jsonFromBob = txBob.toJSON();
const xdrFromBob = txBob.toXDR();
const { client: clientRoot } = await clientFor("swap", {
keypair: this.context.root,
contractId: this.context.swapId,
});
const txRoot = clientRoot.txFromJSON(jsonFromBob);
const txRoot = clientRoot.txFromXDR(xdrFromBob);

await txRoot.simulate();
const result = await txRoot.signAndSend();

expect(result).to.have.property('sendTransactionResponse');
Expand Down
Loading