aptos
Version:
734 lines (637 loc) • 23.7 kB
text/typescript
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable class-methods-use-this */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable max-classes-per-file */
import { sha3_256 as sha3Hash } from "@noble/hashes/sha3";
import { HexString } from "../utils";
import {
Deserializer,
Serializer,
Uint64,
Bytes,
Seq,
Uint8,
Uint128,
deserializeVector,
serializeVector,
bcsToBytes,
Uint16,
Uint256,
} from "../bcs";
import { AccountAuthenticator, TransactionAuthenticator, TransactionAuthenticatorMultiAgent } from "./authenticator";
import { Identifier } from "./identifier";
import { TypeTag } from "./type_tag";
import { AccountAddress } from "./account_address";
export class RawTransaction {
/**
* RawTransactions contain the metadata and payloads that can be submitted to Aptos chain for execution.
* RawTransactions must be signed before Aptos chain can execute them.
*
* @param sender Account address of the sender.
* @param sequence_number Sequence number of this transaction. This must match the sequence number stored in
* the sender's account at the time the transaction executes.
* @param payload Instructions for the Aptos Blockchain, including publishing a module,
* execute a entry function or execute a script payload.
* @param max_gas_amount Maximum total gas to spend for this transaction. The account must have more
* than this gas or the transaction will be discarded during validation.
* @param gas_unit_price Price to be paid per gas unit.
* @param expiration_timestamp_secs The blockchain timestamp at which the blockchain would discard this transaction.
* @param chain_id The chain ID of the blockchain that this transaction is intended to be run on.
*/
constructor(
public readonly sender: AccountAddress,
public readonly sequence_number: Uint64,
public readonly payload: TransactionPayload,
public readonly max_gas_amount: Uint64,
public readonly gas_unit_price: Uint64,
public readonly expiration_timestamp_secs: Uint64,
public readonly chain_id: ChainId,
) {}
serialize(serializer: Serializer): void {
this.sender.serialize(serializer);
serializer.serializeU64(this.sequence_number);
this.payload.serialize(serializer);
serializer.serializeU64(this.max_gas_amount);
serializer.serializeU64(this.gas_unit_price);
serializer.serializeU64(this.expiration_timestamp_secs);
this.chain_id.serialize(serializer);
}
static deserialize(deserializer: Deserializer): RawTransaction {
const sender = AccountAddress.deserialize(deserializer);
const sequence_number = deserializer.deserializeU64();
const payload = TransactionPayload.deserialize(deserializer);
const max_gas_amount = deserializer.deserializeU64();
const gas_unit_price = deserializer.deserializeU64();
const expiration_timestamp_secs = deserializer.deserializeU64();
const chain_id = ChainId.deserialize(deserializer);
return new RawTransaction(
sender,
sequence_number,
payload,
max_gas_amount,
gas_unit_price,
expiration_timestamp_secs,
chain_id,
);
}
}
export class Script {
/**
* Scripts contain the Move bytecodes payload that can be submitted to Aptos chain for execution.
* @param code Move bytecode
* @param ty_args Type arguments that bytecode requires.
*
* @example
* A coin transfer function has one type argument "CoinType".
* ```
* public(script) fun transfer<CoinType>(from: &signer, to: address, amount: u64,)
* ```
* @param args Arugments to bytecode function.
*
* @example
* A coin transfer function has three arugments "from", "to" and "amount".
* ```
* public(script) fun transfer<CoinType>(from: &signer, to: address, amount: u64,)
* ```
*/
constructor(
public readonly code: Bytes,
public readonly ty_args: Seq<TypeTag>,
public readonly args: Seq<TransactionArgument>,
) {}
serialize(serializer: Serializer): void {
serializer.serializeBytes(this.code);
serializeVector<TypeTag>(this.ty_args, serializer);
serializeVector<TransactionArgument>(this.args, serializer);
}
static deserialize(deserializer: Deserializer): Script {
const code = deserializer.deserializeBytes();
const ty_args = deserializeVector(deserializer, TypeTag);
const args = deserializeVector(deserializer, TransactionArgument);
return new Script(code, ty_args, args);
}
}
export class EntryFunction {
/**
* Contains the payload to run a function within a module.
* @param module_name Fully qualified module name. ModuleId consists of account address and module name.
* @param function_name The function to run.
* @param ty_args Type arguments that move function requires.
*
* @example
* A coin transfer function has one type argument "CoinType".
* ```
* public(script) fun transfer<CoinType>(from: &signer, to: address, amount: u64,)
* ```
* @param args Arugments to the move function.
*
* @example
* A coin transfer function has three arugments "from", "to" and "amount".
* ```
* public(script) fun transfer<CoinType>(from: &signer, to: address, amount: u64,)
* ```
*/
constructor(
public readonly module_name: ModuleId,
public readonly function_name: Identifier,
public readonly ty_args: Seq<TypeTag>,
public readonly args: Seq<Bytes>,
) {}
/**
*
* @param module Fully qualified module name in format "AccountAddress::module_name" e.g. "0x1::coin"
* @param func Function name
* @param ty_args Type arguments that move function requires.
*
* @example
* A coin transfer function has one type argument "CoinType".
* ```
* public(script) fun transfer<CoinType>(from: &signer, to: address, amount: u64,)
* ```
* @param args Arugments to the move function.
*
* @example
* A coin transfer function has three arugments "from", "to" and "amount".
* ```
* public(script) fun transfer<CoinType>(from: &signer, to: address, amount: u64,)
* ```
* @returns
*/
static natural(module: string, func: string, ty_args: Seq<TypeTag>, args: Seq<Bytes>): EntryFunction {
return new EntryFunction(ModuleId.fromStr(module), new Identifier(func), ty_args, args);
}
/**
* `natual` is deprecated, please use `natural`
*
* @deprecated.
*/
static natual(module: string, func: string, ty_args: Seq<TypeTag>, args: Seq<Bytes>): EntryFunction {
return EntryFunction.natural(module, func, ty_args, args);
}
serialize(serializer: Serializer): void {
this.module_name.serialize(serializer);
this.function_name.serialize(serializer);
serializeVector<TypeTag>(this.ty_args, serializer);
serializer.serializeU32AsUleb128(this.args.length);
this.args.forEach((item: Bytes) => {
serializer.serializeBytes(item);
});
}
static deserialize(deserializer: Deserializer): EntryFunction {
const module_name = ModuleId.deserialize(deserializer);
const function_name = Identifier.deserialize(deserializer);
const ty_args = deserializeVector(deserializer, TypeTag);
const length = deserializer.deserializeUleb128AsU32();
const list: Seq<Bytes> = [];
for (let i = 0; i < length; i += 1) {
list.push(deserializer.deserializeBytes());
}
const args = list;
return new EntryFunction(module_name, function_name, ty_args, args);
}
}
export class MultiSigTransactionPayload {
/**
* Contains the payload to run a multisig account transaction.
* @param transaction_payload The payload of the multisig transaction. This can only be EntryFunction for now but
* Script might be supported in the future.
*/
constructor(public readonly transaction_payload: EntryFunction) {}
serialize(serializer: Serializer): void {
// We can support multiple types of inner transaction payload in the future.
// For now it's only EntryFunction but if we support more types, we need to serialize with the right enum values
// here
serializer.serializeU32AsUleb128(0);
this.transaction_payload.serialize(serializer);
}
static deserialize(deserializer: Deserializer): MultiSigTransactionPayload {
// TODO: Support other types of payload beside EntryFunction.
// This is the enum value indicating which type of payload the multisig tx contains.
deserializer.deserializeUleb128AsU32();
return new MultiSigTransactionPayload(EntryFunction.deserialize(deserializer));
}
}
export class MultiSig {
/**
* Contains the payload to run a multisig account transaction.
* @param multisig_address The multisig account address the transaction will be executed as.
* @param transaction_payload The payload of the multisig transaction. This is optional when executing a multisig
* transaction whose payload is already stored on chain.
*/
constructor(
public readonly multisig_address: AccountAddress,
public readonly transaction_payload?: MultiSigTransactionPayload,
) {}
serialize(serializer: Serializer): void {
this.multisig_address.serialize(serializer);
// Options are encoded with an extra u8 field before the value - 0x0 is none and 0x1 is present.
// We use serializeBool below to create this prefix value.
if (this.transaction_payload === undefined) {
serializer.serializeBool(false);
} else {
serializer.serializeBool(true);
this.transaction_payload.serialize(serializer);
}
}
static deserialize(deserializer: Deserializer): MultiSig {
const multisig_address = AccountAddress.deserialize(deserializer);
const payloadPresent = deserializer.deserializeBool();
let transaction_payload;
if (payloadPresent) {
transaction_payload = MultiSigTransactionPayload.deserialize(deserializer);
}
return new MultiSig(multisig_address, transaction_payload);
}
}
export class Module {
/**
* Contains the bytecode of a Move module that can be published to the Aptos chain.
* @param code Move bytecode of a module.
*/
constructor(public readonly code: Bytes) {}
serialize(serializer: Serializer): void {
serializer.serializeBytes(this.code);
}
static deserialize(deserializer: Deserializer): Module {
const code = deserializer.deserializeBytes();
return new Module(code);
}
}
export class ModuleId {
/**
* Full name of a module.
* @param address The account address.
* @param name The name of the module under the account at "address".
*/
constructor(
public readonly address: AccountAddress,
public readonly name: Identifier,
) {}
/**
* Converts a string literal to a ModuleId
* @param moduleId String literal in format "AccountAddress::module_name", e.g. "0x1::coin"
* @returns
*/
static fromStr(moduleId: string): ModuleId {
const parts = moduleId.split("::");
if (parts.length !== 2) {
throw new Error("Invalid module id.");
}
return new ModuleId(AccountAddress.fromHex(new HexString(parts[0])), new Identifier(parts[1]));
}
serialize(serializer: Serializer): void {
this.address.serialize(serializer);
this.name.serialize(serializer);
}
static deserialize(deserializer: Deserializer): ModuleId {
const address = AccountAddress.deserialize(deserializer);
const name = Identifier.deserialize(deserializer);
return new ModuleId(address, name);
}
}
export class ChangeSet {
serialize(serializer: Serializer): void {
throw new Error("Not implemented.");
}
static deserialize(deserializer: Deserializer): ChangeSet {
throw new Error("Not implemented.");
}
}
export class WriteSet {
serialize(serializer: Serializer): void {
throw new Error("Not implmented.");
}
static deserialize(deserializer: Deserializer): WriteSet {
throw new Error("Not implmented.");
}
}
export class SignedTransaction {
/**
* A SignedTransaction consists of a raw transaction and an authenticator. The authenticator
* contains a client's public key and the signature of the raw transaction.
*
* @see {@link https://aptos.dev/guides/creating-a-signed-transaction/ | Creating a Signed Transaction}
*
* @param raw_txn
* @param authenticator Contains a client's public key and the signature of the raw transaction.
* Authenticator has 3 flavors: single signature, multi-signature and multi-agent.
* @see authenticator.ts for details.
*/
constructor(
public readonly raw_txn: RawTransaction,
public readonly authenticator: TransactionAuthenticator,
) {}
serialize(serializer: Serializer): void {
this.raw_txn.serialize(serializer);
this.authenticator.serialize(serializer);
}
static deserialize(deserializer: Deserializer): SignedTransaction {
const raw_txn = RawTransaction.deserialize(deserializer);
const authenticator = TransactionAuthenticator.deserialize(deserializer);
return new SignedTransaction(raw_txn, authenticator);
}
}
export abstract class RawTransactionWithData {
abstract serialize(serializer: Serializer): void;
static deserialize(deserializer: Deserializer): RawTransactionWithData {
const index = deserializer.deserializeUleb128AsU32();
switch (index) {
case 0:
return MultiAgentRawTransaction.load(deserializer);
case 1:
return FeePayerRawTransaction.load(deserializer);
default:
throw new Error(`Unknown variant index for RawTransactionWithData: ${index}`);
}
}
}
export class MultiAgentRawTransaction extends RawTransactionWithData {
constructor(
public readonly raw_txn: RawTransaction,
public readonly secondary_signer_addresses: Seq<AccountAddress>,
) {
super();
}
serialize(serializer: Serializer): void {
// enum variant index
serializer.serializeU32AsUleb128(0);
this.raw_txn.serialize(serializer);
serializeVector<TransactionArgument>(this.secondary_signer_addresses, serializer);
}
static load(deserializer: Deserializer): MultiAgentRawTransaction {
const rawTxn = RawTransaction.deserialize(deserializer);
const secondarySignerAddresses = deserializeVector(deserializer, AccountAddress);
return new MultiAgentRawTransaction(rawTxn, secondarySignerAddresses);
}
}
export class FeePayerRawTransaction extends RawTransactionWithData {
constructor(
public readonly raw_txn: RawTransaction,
public readonly secondary_signer_addresses: Seq<AccountAddress>,
public readonly fee_payer_address: AccountAddress,
) {
super();
}
serialize(serializer: Serializer): void {
// enum variant index
serializer.serializeU32AsUleb128(1);
this.raw_txn.serialize(serializer);
serializeVector<TransactionArgument>(this.secondary_signer_addresses, serializer);
this.fee_payer_address.serialize(serializer);
}
static load(deserializer: Deserializer): FeePayerRawTransaction {
const rawTxn = RawTransaction.deserialize(deserializer);
const secondarySignerAddresses = deserializeVector(deserializer, AccountAddress);
const feePayerAddress = AccountAddress.deserialize(deserializer);
return new FeePayerRawTransaction(rawTxn, secondarySignerAddresses, feePayerAddress);
}
}
export abstract class TransactionPayload {
abstract serialize(serializer: Serializer): void;
static deserialize(deserializer: Deserializer): TransactionPayload {
const index = deserializer.deserializeUleb128AsU32();
switch (index) {
case 0:
return TransactionPayloadScript.load(deserializer);
// TODO: change to 1 once ModuleBundle has been removed from rust
case 2:
return TransactionPayloadEntryFunction.load(deserializer);
case 3:
return TransactionPayloadMultisig.load(deserializer);
default:
throw new Error(`Unknown variant index for TransactionPayload: ${index}`);
}
}
}
export class TransactionPayloadScript extends TransactionPayload {
constructor(public readonly value: Script) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(0);
this.value.serialize(serializer);
}
static load(deserializer: Deserializer): TransactionPayloadScript {
const value = Script.deserialize(deserializer);
return new TransactionPayloadScript(value);
}
}
export class TransactionPayloadEntryFunction extends TransactionPayload {
constructor(public readonly value: EntryFunction) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(2);
this.value.serialize(serializer);
}
static load(deserializer: Deserializer): TransactionPayloadEntryFunction {
const value = EntryFunction.deserialize(deserializer);
return new TransactionPayloadEntryFunction(value);
}
}
export class TransactionPayloadMultisig extends TransactionPayload {
constructor(public readonly value: MultiSig) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(3);
this.value.serialize(serializer);
}
static load(deserializer: Deserializer): TransactionPayloadMultisig {
const value = MultiSig.deserialize(deserializer);
return new TransactionPayloadMultisig(value);
}
}
export class ChainId {
constructor(public readonly value: Uint8) {}
serialize(serializer: Serializer): void {
serializer.serializeU8(this.value);
}
static deserialize(deserializer: Deserializer): ChainId {
const value = deserializer.deserializeU8();
return new ChainId(value);
}
}
export abstract class TransactionArgument {
abstract serialize(serializer: Serializer): void;
static deserialize(deserializer: Deserializer): TransactionArgument {
const index = deserializer.deserializeUleb128AsU32();
switch (index) {
case 0:
return TransactionArgumentU8.load(deserializer);
case 1:
return TransactionArgumentU64.load(deserializer);
case 2:
return TransactionArgumentU128.load(deserializer);
case 3:
return TransactionArgumentAddress.load(deserializer);
case 4:
return TransactionArgumentU8Vector.load(deserializer);
case 5:
return TransactionArgumentBool.load(deserializer);
case 6:
return TransactionArgumentU16.load(deserializer);
case 7:
return TransactionArgumentU32.load(deserializer);
case 8:
return TransactionArgumentU256.load(deserializer);
default:
throw new Error(`Unknown variant index for TransactionArgument: ${index}`);
}
}
}
export class TransactionArgumentU8 extends TransactionArgument {
constructor(public readonly value: Uint8) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(0);
serializer.serializeU8(this.value);
}
static load(deserializer: Deserializer): TransactionArgumentU8 {
const value = deserializer.deserializeU8();
return new TransactionArgumentU8(value);
}
}
export class TransactionArgumentU16 extends TransactionArgument {
constructor(public readonly value: Uint16) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(6);
serializer.serializeU16(this.value);
}
static load(deserializer: Deserializer): TransactionArgumentU16 {
const value = deserializer.deserializeU16();
return new TransactionArgumentU16(value);
}
}
export class TransactionArgumentU32 extends TransactionArgument {
constructor(public readonly value: Uint16) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(7);
serializer.serializeU32(this.value);
}
static load(deserializer: Deserializer): TransactionArgumentU32 {
const value = deserializer.deserializeU32();
return new TransactionArgumentU32(value);
}
}
export class TransactionArgumentU64 extends TransactionArgument {
constructor(public readonly value: Uint64) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(1);
serializer.serializeU64(this.value);
}
static load(deserializer: Deserializer): TransactionArgumentU64 {
const value = deserializer.deserializeU64();
return new TransactionArgumentU64(value);
}
}
export class TransactionArgumentU128 extends TransactionArgument {
constructor(public readonly value: Uint128) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(2);
serializer.serializeU128(this.value);
}
static load(deserializer: Deserializer): TransactionArgumentU128 {
const value = deserializer.deserializeU128();
return new TransactionArgumentU128(value);
}
}
export class TransactionArgumentU256 extends TransactionArgument {
constructor(public readonly value: Uint256) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(8);
serializer.serializeU256(this.value);
}
static load(deserializer: Deserializer): TransactionArgumentU256 {
const value = deserializer.deserializeU256();
return new TransactionArgumentU256(value);
}
}
export class TransactionArgumentAddress extends TransactionArgument {
constructor(public readonly value: AccountAddress) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(3);
this.value.serialize(serializer);
}
static load(deserializer: Deserializer): TransactionArgumentAddress {
const value = AccountAddress.deserialize(deserializer);
return new TransactionArgumentAddress(value);
}
}
export class TransactionArgumentU8Vector extends TransactionArgument {
constructor(public readonly value: Bytes) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(4);
serializer.serializeBytes(this.value);
}
static load(deserializer: Deserializer): TransactionArgumentU8Vector {
const value = deserializer.deserializeBytes();
return new TransactionArgumentU8Vector(value);
}
}
export class TransactionArgumentBool extends TransactionArgument {
constructor(public readonly value: boolean) {
super();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(5);
serializer.serializeBool(this.value);
}
static load(deserializer: Deserializer): TransactionArgumentBool {
const value = deserializer.deserializeBool();
return new TransactionArgumentBool(value);
}
}
export abstract class Transaction {
abstract serialize(serializer: Serializer): void;
abstract hash(): Bytes;
getHashSalt(): Bytes {
const hash = sha3Hash.create();
hash.update("APTOS::Transaction");
return hash.digest();
}
static deserialize(deserializer: Deserializer): Transaction {
const index = deserializer.deserializeUleb128AsU32();
switch (index) {
case 0:
return UserTransaction.load(deserializer);
default:
throw new Error(`Unknown variant index for Transaction: ${index}`);
}
}
}
export class UserTransaction extends Transaction {
constructor(public readonly value: SignedTransaction) {
super();
}
hash(): Bytes {
const hash = sha3Hash.create();
hash.update(this.getHashSalt());
hash.update(bcsToBytes(this));
return hash.digest();
}
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(0);
this.value.serialize(serializer);
}
static load(deserializer: Deserializer): UserTransaction {
return new UserTransaction(SignedTransaction.deserialize(deserializer));
}
}