open-libra-sdk
Version:
A minimalist Typescript library for interacting with the Open Libra blockchain.
177 lines (163 loc) • 5.76 kB
text/typescript
import type {
AccountAddress,
AccountAuthenticatorEd25519,
AnyRawTransaction,
CommittedTransactionResponse,
Ed25519Account,
Ed25519PrivateKey,
InputGenerateTransactionOptions,
MoveFunctionId,
Network,
SimpleEntryFunctionArgumentTypes,
SimpleTransaction,
} from "@aptos-labs/ts-sdk";
import { mnemonicToAccountObj, newAccount } from "../crypto/keyFactory";
import { signTransactionWithAuthenticatorDiem } from "../transaction/txSigning";
import { LibraClient } from "../client/client";
/**
* CryptoWallet class provides functionalities for handling a cryptocurrency wallet.
* It allows generating an account from a mnemonic phrase, signing messages and transactions,
* verifying signatures, and submitting transactions to the blockchain.
* It has modes for using online with a client, or offline for account recovery
* and tx signing only (cold wallet).
*/
export class LibraWallet {
/**
* The account associated with this wallet, generated from a mnemonic phrase.
*/
readonly account: Ed25519Account;
onchainAddress?: AccountAddress;
readonly client?: LibraClient;
/**
* Transaction options that can be modified based on user preferences.
*/
txOptions: InputGenerateTransactionOptions;
private constructor(
account: Ed25519Account,
client?: LibraClient,
address?: AccountAddress,
) {
this.account = account;
this.client = client;
this.onchainAddress = address;
this.txOptions = {
maxGasAmount: 40000,
gasUnitPrice: 100,
};
}
/**
* Creates a wallet instance from a mnemonic phrase
* @param mnemonic The mnemonic phrase used to generate the account
* @param network Optional network settings (MAINNET, TESTNET etc)
* @param fullnode Optional URL of upstream node
* @param forceAddress Optional account address if key has rotated
*/
static fromMnemonic(
mnemonic: string,
network?: Network,
fullnode?: string,
forceAddress?: AccountAddress,
): LibraWallet {
const account = mnemonicToAccountObj(mnemonic, forceAddress);
const client =
network && fullnode ? new LibraClient(network, fullnode) : undefined;
return new LibraWallet(account, client);
}
/**
* Creates a wallet instance from an existing account address and private key
* @param address The account address
* @param privateKey The Ed25519 private key
* @param client Pre-configured LibraClient instance
*/
static fromPrivateKey(
address: AccountAddress,
privateKey: Ed25519PrivateKey,
client?: LibraClient,
): LibraWallet {
const account = newAccount(privateKey, address);
return new LibraWallet(account, client, address);
}
/**
* Requires connection to a fullnode, will check the actual address which this
* authKey owns on chain.
*/
async syncOnchain() {
const derived_authkey = this.account.publicKey.authKey();
if (!this.client) throw "Cold wallet can't connect to chain";
this.onchainAddress =
await this.client.getOriginatingAddress(derived_authkey);
if (this.onchainAddress) {
const account_data = await this.client.account.getAccountInfo({
accountAddress: this.onchainAddress,
});
this.txOptions.accountSequenceNumber = Number(
account_data.sequence_number,
);
if (derived_authkey.toString() != account_data.authentication_key) {
throw "Derived authentication key does not match the one on-chain, cannot submit transactions";
}
}
}
getAddress(): AccountAddress {
return this.onchainAddress ?? this.account.accountAddress;
}
/**
* Signs a raw transaction using the account's private key.
* @param transaction - The raw transaction object that needs to be signed.
* @returns A SignerAuthenticator object containing the signed transaction data.
*/
signTransaction(transaction: AnyRawTransaction): AccountAuthenticatorEd25519 {
return signTransactionWithAuthenticatorDiem(this.account, transaction);
}
/**
*
* @param entry_function address of the function e.g. "0x1::ol_account::transfer"
* @param args: list of simple serializable Move values e.g. [marlon_addr, 100]
*/
async buildTransaction(
entry_function: MoveFunctionId,
args: Array<SimpleEntryFunctionArgumentTypes>,
): Promise<SimpleTransaction> {
if (!this.client) throw "Cold wallet can't connect to chain";
return await this.client.transaction.build.simple({
sender: this.onchainAddress ?? this.account.accountAddress,
data: {
function: entry_function,
functionArguments: args,
},
options: this.txOptions,
});
}
/**
* Simple transfer function between ordinary accounts
* @param recipient address of recipient
* @param amount non-decimal coin amount for transfer. Open Libra uses 6 decimal places. e.g. 1 coin = 1,000,000 amount
*/
async buildTransferTx(
recipient: AccountAddress,
amount: number,
): Promise<AnyRawTransaction> {
return this.buildTransaction("0x1::ol_account::transfer", [
recipient.toString(),
amount,
]);
}
/**
* Submits a signed transaction to the blockchain network.
* @param transaction - The raw transaction object that needs to be signed.
*/
async signSubmitWait(
transaction: AnyRawTransaction,
): Promise<CommittedTransactionResponse> {
if (!this.client) throw "Cold wallet can't connect to chain";
const signerAuthenticator = this.signTransaction(transaction);
return this.client
.submitAndWait(transaction, signerAuthenticator)
.then((res) => {
console.log(
`Transaction success: ${res.success}, vm_status: ${res.vm_status}`,
);
return res;
});
}
}