@zlattice/lattice-js
Version:
Lattice blockchain TypeScript SDK with dual module support (CJS + ESM)
269 lines • 12.7 kB
JavaScript
import { HEX_PREFIX, TransactionTypes, ZERO_ADDRESS } from "../common/constants.js";
import { LatestBlock } from "../common/types/index.js";
import { createCrypto } from "../crypto/index.js";
import { BlockCacheImpl, newAccountLock } from "../lattice/index.js";
import { log } from "../logger.js";
import { HttpClientImpl, HttpProvider } from "../providers/http_provider.js";
import { FixedDelayStrategy, RetryHandler } from "../utils/index.js";
import { decryptFileKey } from "../wallet/index.js";
import { ResultAsync, errAsync, ok } from "neverthrow";
import { TransactionBuilder } from "./tx/index.js";
class Credentials {
constructor(accountAddress, fileKey, passphrase, privateKey) {
this.accountAddress = accountAddress;
this.passphrase = passphrase;
this.fileKey = fileKey;
this.privateKey = privateKey;
}
static fromFileKey(accountAddress, fileKey, passphrase) {
const result = decryptFileKey(fileKey, passphrase);
if (result.isErr()) {
throw result.error;
}
const privateKey = result.value;
return new Credentials(accountAddress, fileKey, passphrase, privateKey);
}
static fromPrivateKey(accountAddress, privateKey) {
return new Credentials(accountAddress, undefined, undefined, privateKey);
}
getAccountAddress() {
return this.accountAddress;
}
getPrivateKey() {
// TODO: check if private key is set, if not, throw an error
if (!this.privateKey) {
throw new Error("Private key is not set");
}
return this.privateKey;
}
}
class ChainConfig {
constructor(curve, tokenLess) {
this.curve = curve;
this.tokenLess = tokenLess;
}
}
class NodeConnectionConfig {
constructor(ip, httpPort, wsPort, ginHttpPort, jwtSecret, jwtTokenExpirationDuration = 6 * 60 * 60, // 6 hours
insecure = true) {
this.insecure = insecure;
this.ip = ip;
this.httpPort = httpPort;
this.wsPort = wsPort;
this.ginHttpPort = ginHttpPort;
this.jwtSecret = jwtSecret;
this.jwtTokenExpirationDuration = jwtTokenExpirationDuration;
}
}
class Options {
constructor(options) {
this.options = options;
}
get(key) {
return this.options[key];
}
}
class LatticeClient {
constructor(chainConfig, nodeConnectionConfig, options, accountLock, blockCache) {
this.httpClient = new HttpClientImpl(new HttpProvider(`${nodeConnectionConfig.insecure ? "http" : "https"}://${nodeConnectionConfig.ip}:${nodeConnectionConfig.httpPort}`));
this.chainConfig = chainConfig;
this.nodeConnectionConfig = nodeConnectionConfig;
this.options = options ?? new Options({});
this.accountLock = accountLock ?? newAccountLock();
this.blockCache =
blockCache ??
new BlockCacheImpl({
backend: "memory"
}, 10);
}
/**
* Wait for receipt
* @param chainId The chain id
* @param hash The hash
* @param retryStrategy The retry strategy, default is FixedDelayStrategy.default
* @param retries The number of retries, default is 10
* @returns E.Either<Receipt, Error>, left is the receipt, right is the error
*/
waitReceipt(chainId, hash, retryStrategy = FixedDelayStrategy.default, retries = 10) {
const retryHandler = new RetryHandler(retryStrategy, retries);
return ResultAsync.fromPromise(retryHandler.execute(async () => {
return await this.httpClient.getReceipt(chainId, hash);
}), (error) => {
log.error(`Failed to get receipt: ${error}`);
return error instanceof Error ? error : new Error(String(error));
});
}
handleTransaction(chainId, credentials, transaction, block) {
const signResult = transaction.signTx(chainId, this.chainConfig.curve, credentials.getPrivateKey());
if (signResult.isErr()) {
return errAsync(signResult.error);
}
return ResultAsync.fromPromise(this.httpClient.sendTransaction(chainId, transaction), (error) => {
log.error(`Failed to send transaction: ${error}`);
return error instanceof Error ? error : new Error(String(error));
}).andThen((hash) => {
block.currentTBlockNumber = block.currentTBlockNumber + 1;
block.currentTBlockHash = hash;
this.blockCache.putBlock(chainId, credentials.getAccountAddress(), block);
return ok(hash);
});
}
/**
* Transfer
* @param credentials Your credentials
* @param chainId The chain id
* @param linker The linker address
* @param payload The payload, should be a hex string
* @param amount The amount, default is 0
* @param joule The joule, default is 0
* @returns ResultAsync<string, Error>, left is the hash, right is the error
*/
transfer(credentials, chainId, linker, payload, amount = 0, joule = 0) {
return this.accountLock.withLock(chainId, credentials.getAccountAddress(), async () => {
const block = await this.blockCache.getBlock(chainId, credentials.getAccountAddress(), async (chainId, address) => {
return await this.httpClient.getLatestBlock(chainId, address);
});
const tx = TransactionBuilder.builder(TransactionTypes.Send)
.setBlock(block)
.setOwner(credentials.getAccountAddress())
.setLinker(linker)
.setPayload(payload)
.setAmount(amount)
.setJoule(joule)
.build();
return await this.handleTransaction(chainId, credentials, tx, block).match((hash) => hash, (error) => {
throw error;
});
});
}
/**
* Transfer and wait for receipt
* @param credentials Your credentials
* @param chainId The chain id
* @param linker The linker address
* @param payload The payload, should be a hex string
* @param amount The amount, default is 0
* @param joule The joule, default is 0
* @param retryStrategy The retry strategy, default is FixedDelayStrategy.default
* @param retries The number of retries, default is 10
* @returns E.Either<Receipt, Error>, left is the receipt, right is the error
*/
transferWaitReceipt(credentials, chainId, linker, payload, amount = 0, joule = 0, retryStrategy = FixedDelayStrategy.default, retries = 10) {
return this.transfer(credentials, chainId, linker, payload, amount, joule).andThen((hash) => {
return this.waitReceipt(chainId, hash, retryStrategy, retries);
});
}
/**
* Deploy contract
* @param credentials Your credentials
* @param chainId The chain id
* @param code The code, should be a hex string
* @param payload The payload, should be a hex string, default is 0x
* @param amount The amount, default is 0
* @param joule The joule, default is 0
* @returns E.Either<string, Error>, left is the hash, right is the error
*/
deployContract(credentials, chainId, code, payload = HEX_PREFIX, amount = 0, joule = 0) {
return this.accountLock.withLock(chainId, credentials.getAccountAddress(), async () => {
const block = await this.blockCache.getBlock(chainId, credentials.getAccountAddress(), async (chainId, address) => {
return await this.httpClient.getLatestBlock(chainId, address);
});
const tx = TransactionBuilder.builder(TransactionTypes.DeployContract)
.setBlock(block)
.setOwner(credentials.getAccountAddress())
.setLinker(ZERO_ADDRESS)
.setCode(code)
.setPayload(payload)
.setAmount(amount)
.setJoule(joule)
.build();
const codeHash = createCrypto(this.chainConfig.curve).hash(Buffer.from(code.startsWith(HEX_PREFIX) ? code.slice(2) : code, "hex"));
tx.codeHash = `0x${codeHash.toString("hex")}`;
return await this.handleTransaction(chainId, credentials, tx, block).match((hash) => hash, (error) => {
throw error;
});
});
}
/**
* Deploy contract and wait for receipt
* @param credentials Your credentials
* @param chainId The chain id
* @param code The code, should be a hex string
* @param payload The payload, should be a hex string, default is 0x
* @param amount The amount, default is 0
* @param joule The joule, default is 0
* @param retryStrategy The retry strategy, default is FixedDelayStrategy.default
* @param retries The number of retries, default is 10
* @returns E.Either<Receipt, Error>, left is the receipt, right is the error
*/
deployContractWaitReceipt(credentials, chainId, code, payload = HEX_PREFIX, amount = 0, joule = 0, retryStrategy = FixedDelayStrategy.default, retries = 10) {
return this.deployContract(credentials, chainId, code, payload, amount, joule).andThen((hash) => {
return this.waitReceipt(chainId, hash, retryStrategy, retries);
});
}
/**
* Call contract
* @param credentials Your credentials
* @param chainId The chain id
* @param contractAddress The contract address
* @param code The code, should be a hex string
* @param payload The payload, should be a hex string, default is 0x
* @param amount The amount, default is 0
* @param joule The joule, default is 0
* @returns E.Either<string, Error>, left is the hash, right is the error
*/
callContract(credentials, chainId, contractAddress, code, payload = HEX_PREFIX, amount = 0, joule = 0) {
return this.accountLock.withLock(chainId, credentials.getAccountAddress(), async () => {
const block = await this.blockCache.getBlock(chainId, credentials.getAccountAddress(), async (chainId, address) => {
return await this.httpClient.getLatestBlock(chainId, address);
});
const tx = TransactionBuilder.builder(TransactionTypes.CallContract)
.setBlock(block)
.setOwner(credentials.getAccountAddress())
.setLinker(contractAddress)
.setCode(code)
.setPayload(payload)
.setAmount(amount)
.setJoule(joule)
.build();
const codeHash = createCrypto(this.chainConfig.curve).hash(Buffer.from(code.startsWith(HEX_PREFIX) ? code.slice(2) : code, "hex"));
tx.codeHash = `0x${codeHash.toString("hex")}`;
return await this.handleTransaction(chainId, credentials, tx, block).match((hash) => hash, (error) => {
throw error;
});
});
}
/**
* Call contract and wait for receipt
* @param credentials Your credentials
* @param chainId The chain id
* @param contractAddress The contract address
* @param code The code, should be a hex string
* @param payload The payload, should be a hex string, default is 0x
* @param amount The amount, default is 0
* @param joule The joule, default is 0
* @returns E.Either<Receipt, Error>, left is the receipt, right is the error
*/
callContractWaitReceipt(credentials, chainId, contractAddress, code, payload = HEX_PREFIX, amount = 0, joule = 0, retryStrategy = FixedDelayStrategy.default, retries = 10) {
return this.callContract(credentials, chainId, contractAddress, code, payload, amount, joule).andThen((hash) => {
return this.waitReceipt(chainId, hash, retryStrategy, retries);
});
}
preCallContract(credentials, chainId, contractAddress, code, payload = HEX_PREFIX, amount = 0, joule = 0) {
const unsignedTx = TransactionBuilder.builder(TransactionTypes.CallContract)
.setBlock(LatestBlock.emptyBlock())
.setOwner(credentials.getAccountAddress())
.setLinker(contractAddress)
.setCode(code)
.setPayload(payload)
.setAmount(amount)
.setJoule(joule)
.build();
return ResultAsync.fromPromise(this.httpClient.preCallContract(chainId, unsignedTx), (error) => {
log.error(`Failed to pre-call contract: ${error}`);
return error instanceof Error ? error : new Error(String(error));
});
}
}
export { LatticeClient, Credentials, ChainConfig, NodeConnectionConfig, Options };
//# sourceMappingURL=lattice.js.map