eulith-web3js-core
Version:
Eulith core web3js SDK (code to access Eulith services via web3js)
279 lines (278 loc) • 14.1 kB
TypeScript
import { TransactionConfig, TransactionReceipt } from "web3-eth";
import * as Eulith from "../src/index";
interface commitAndSendAndWaitArgs {
timeoutMS?: number;
extraTXParams2Merge?: TransactionConfig;
}
/**
* APIS relating to Atomic Transactions
*/
export declare module AtomicTx {
/**
* @todo CONSIDER if IAtomicTx should have a commit method, and what it should return
* Reasonable answers:
* o no - just do in subclasses
* o Yes, return void|TransactionConfig, depending on if tx is outermost or not.
* o maybe return # or | TransactionConfig - once I understand point of ever returning number
*
* @todo consider adding LEVEL property to be returned.
*/
interface IAtomicTx {
/**
* Accumulate the argument transactionConfig into a server-side object which,
* when this.commit() is called, will produce a single transaction which combines
* the added sub-transactions.
*
* @todo SINCE we don't implement any (much) server side logic to react to partially
* completed transactions (like in a database) - I wonder why this is useful todo
* server side. That's probably worth DOCUMENTING. Otherwise, we could just accumulate
* an array client side and send that.
*
* @param transactionConfig
* @returns
*/
addTransaction(transactionConfig: TransactionConfig): Promise<string>;
/**
* Adds multiple transactions, and returns all their hashed txHash results.
* Equivilent to a series sof calls to addTransaction, but waiting for each in turn.
*
* \note - this means that order is preserved between the transactions added, even
* if the caller fails to wait on this promise.
*/
addTransactions(txs: TransactionConfig[]): Promise<string[]>;
/**
*
*/
readonly provider: Eulith.Provider;
/**
* Not needed, but for debugging purposes maybe handy
*/
readonly atomicTxID: string;
}
/**
* @typedef Eulith.AtomicTx.Transaction - a root transaction
*
* \brief An atomic transaction is a bundle of transactions that all execute at the same time.
*
* Key properties of atomicity:
* o Everything fails or everything succeeds --> If at any point in your series of transactions something goes wrong, everything reverts. This is especially useful if you're making a trade that has upstream dependencies.
* o Everything is executed as a single unit --> It's not possible for someone else to take action in the middle of your execution. The state of the blockchain is frozen at the start of your transaction and is only mutable by your own actions. As far as the blockchain is concerned, your atomic unit is a single transaction.
* o More efficient usage of EVM storage --> Could result in cheaper transactions.
*
* \see <https://docs.eulith.com/v/srG7S9J4U0bx5OMNR41S/implementation-details/atomic-transactions>
*
* Constructing the transaction object starts the transaction.
* Calling commit() or rollback() renders this object unusable again, so
* the caller should create a new one for a second transaction.
*
* Should the caller need to perform additional operations 'in the context' of the
* transaction (such as calls to eulith_swap_quote), use the Eulith.Provider from
* this.provider()
*
* Example Code
* ~~~
* const atomicTransaction = new Eulith.AtomicTx.Transaction({web3: ew3, signer: acct});
* atomicTransaction.addTransaction({ 'from': acct.address, 'to': await one.address, 'value': 12131415 });
* const combinedTransactionAsTxParams = await atomicTransaction.commit()
* const txHash: string = await provider.signAndSendTransaction(combinedTransactionAsTxParams, acct);
* const txReceipt: TransactionReceipt = await ew3.eth.getTransactionReceipt(txHash);
* ~~~
*
* Example Code
* ~~~
* const atomicTransaction = new Eulith.AtomicTx.Transaction({web3: ew3, signer: acct});
* atomicTransaction.addTransaction({ 'from': acct.address, 'to': one.address, 'value': 12131415 });
* const txReceipt = await atomicTx.commitAndSendAndWait({timeoutMS: 10*1000});
* ~~~
*
* An AtomicTx - internally - uses a 'proxy' contract to implement the actual steps of an atomic transaction.
* Since this contract sometimes needs to be setup before the start of any AtomicTx using it, the caller
* may preconstruct it (for use in approves, for example). They then may optionally pass it to the
* AtomicTx constructor to avoid having it recomputed.
*
* if signer provided, its address must match that given by the (then optional) accountAddress
*/
class Transaction implements IAtomicTx {
/**
* Begin an atomic transaction.
*
* Atomic transactions, are implemented via a proxy contract instance which typically must
* be 'approved' for operations.
*
* This class automatically manages construction of that proxy object, if needed, though it can be specifically
* passed into the constructor, for performance reasons.
*
* Example Usage:
* ~~~
* const atomicTx = new Transaction({web3: ew3, accountAddress: acct.address});
* ~~~
*
* Example Usage:
* ~~~
* const agentContractAddress = await Eulith.OnChainAgents.contractAddress({ provider, authorizedSigner: acct });
* await tokenContract
* .approve(agentContractAddress, tokenContract.asTokenValue(borrowAmountA * 1.2), { from: acct.address })
* .signAndSendAndWait(acct, provider);
* const atomicTx = await Eulith.AtomicTx.Transaction({ provider, signer: acct, agentContractAddress });
* ~~~
*
* @param provider - typically Eulith.Provider object OR Eulith.Web3
* @param accountAddress - required - transactions must be signed, and this tells the address of the signer
* @param agentContractAddress - optional - generally omitted, and computed automatically - refers to the contract address of the on-chain agent implementing this atomic transaction
* @param gnosis - tbd ;-)
*/
constructor({ provider, signer, accountAddress, agentContractAddress, gnosis }: {
provider?: Eulith.Provider | Eulith.Web3;
accountAddress?: string;
gnosis?: string;
signer?: Eulith.Signing.ICryptographicSigner | Eulith.Signing.SigningService | Eulith.Web3;
agentContractAddress?: string;
});
/**
* Since atomic transactions are implemented by having an onChain Agent contract do all the operations,
* it often must be 'approved' to do those operations, so the caller will need to know its address.
*/
get agentContractAddress(): Promise<string>;
/**
* Accumulate the argument transactionConfig into a server-side object which,
* when this.commit() is called, will produce a single transaction which combines
* the added sub-transactions.
*
* @todo SINCE we don't implement any (much) server side logic to react to partially
* completed transactions (like in a database) - I wonder why this is useful todo
* server side. That's probably worth DOCUMENTING. Otherwise, we could just accumulate
* an array client side and send that.
*
* @param transactionConfig
* @returns
*/
addTransaction(transactionConfig: TransactionConfig): Promise<string>;
/**
* Adds multiple transactions, and returns all their hashed txHash results.
* Equivilent to a series sof calls to addTransaction, but waiting for each in turn.
*
* \note - this means that order is preserved between the transactions added, even
* if the caller fails to wait on this promise.
*/
addTransactions(txs: TransactionConfig[]): Promise<string[]>;
/**
* Not needed, but for debugging purposes maybe handy, and certainly used internally.
*/
get provider(): Eulith.Provider;
/**
* Not needed, but for debugging purposes maybe handy
*/
get atomicTxID(): string;
/**
* \note This replaces the python ew3.v0.commit_atomic_transaction()
*
* \see also commitAndSendAndWait - often a better choice
*
* Example Usage:
* ~~~
* const atomicTx = new AtomicTx.Transaction({web3: ew3, accountAddress: acct.address});
* // do something with atmoictx - adding transactions or .provider and hand to something else
* const txReceipt: TransactionReceipt = await atomicTx.commitAndSendAndWait({ timeoutMS: 10 * 1000, extraTXParams2Merge: { gas: 1000000 } })
* expect(txReceipt.status).toBeTruthy();
* ~~~
*
* This (automatically) waits for any transactions added (so the caller doesnt need to
* wait on the async results)
*
* Then, combines all the transactions attempted, and produces a new TransactionConfig
* which, if signed and sent to the server, will complete the (combined atomic) transaction.
*/
commit(): Promise<TransactionConfig>;
/**
* \note This replaces the python ew3.v0.commit_atomic_transaction() followed by ew3.eth.send_transaction(txparams) and ew3.eth.wait_for_transaction_receipt(tx)
*
* \brief shorthand for commit(), and signing and sending that tx, and waiting for its txReceipt all in one
*
* \note - requires a signer attached to the provider object used to create the transaction.
*
* \note - this takes a timeoutMS, and repeatedly calls getTransactionReceipt, until the timeout
* expires (or receipt received). This differs from the default behavior of getTransactionReceipt
*
* \todo Discuss if this should throw on failure? or provide overload/variant that does
*/
commitAndSendAndWait(args?: commitAndSendAndWaitArgs): Promise<TransactionReceipt>;
/**
*/
rollback(): Promise<void>;
private web3_;
private inTransaction_;
private TXID_;
private logger_;
private ongoingTransactions_;
private agentContractAddress_;
}
/**
* @typedef Eulith.AtomicTx.NestedTransaction
*
* \brief This acts quite similarly to a AtomicTx (and someday maybe merged with that)
*
* It acts as a nested transaction.
*
* Example Usage:
* ~~~
* const atomicTx = new Eulith.AtomicTx.Transaction({ provider, signer: acct });
*
* const swapAtomicTx: Eulith.AtomicTx.NestedTransaction = await Eulith.Uniswap.startSwap({
* request: quote.swapRequest,
* parentTx: atomicTx
* });
* await swapAtomicTx.commit(); // NOTE: critical to await here, before commiting the parent transaction!
*
* // Commit, sign, and complete the operation - this will only return if/when the entire operation succeeeds (and throws on any part failing)
* await atomicTx.commitAndSendAndWait();
* ~~~
*/
class NestedTransaction {
/**
* NestedTransaction can be nested inside AtomicTx.Transaction or another NestedTransaction
* (so really should consider merging this with AtomicTx)
*/
constructor({ parentTx }: {
parentTx: AtomicTx.Transaction | NestedTransaction;
});
/**
* \see IAtomicTx.provider
*/
get provider(): Eulith.Provider;
/**
* Accumulate the argument transactionConfig into a server-side object which,
* when this.commit() is called, will produce a single transaction which combines
* the added sub-transactions.
*
* @todo SINCE we don't implement any (much) server side logic to react to partially
* completed transactions (like in a database) - I wonder why this is useful todo
* server side. That's probably worth DOCUMENTING. Otherwise, we could just accumulate
* an array client side and send that.
*
* @param transactionConfig
* @returns
*/
addTransaction(transactionConfig: TransactionConfig): Promise<string>;
/**
* Adds multiple transactions, and returns all their hashed txHash results.
* Equivilent to a series sof calls to addTransaction, but waiting for each in turn.
*
* \note - this means that order is preserved between the transactions added, even
* if the caller fails to wait on this promise.
*/
addTransactions(txs: TransactionConfig[]): Promise<string[]>;
/**
* Complete (atomically) this sub-step of the parent atomic transaction.
*
* This doesn't actually do the step, but marks as completed the sequence of operations
* and returns the (1-based) index of this step in the parent atomic transaction.
*
* Note: that returned index is not generally useful, but can be used to help diagnose problems
* in advanced applications.
*/
commit(): Promise<number>;
private web3_;
private ongoingTransactions_;
}
}
export {};