149 lines
5.0 KiB
TypeScript
149 lines
5.0 KiB
TypeScript
/*
|
|
* Copyright (C) 2022 - 2023 Partisia Blockchain Foundation
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
import BN from "bn.js";
|
|
import { BlockchainAddress } from "@partisiablockchain/abi-client";
|
|
import { TransactionApi } from "../../client/TransactionApi";
|
|
import { AvlClient } from "../../client/AvlClient";
|
|
import { LittleEndianByteInput } from "@secata-public/bitmanipulation-ts";
|
|
import { Buffer } from "buffer";
|
|
import { transfer, deserializeTokenState } from "../../abi/TokenV2";
|
|
import {
|
|
TokenContract,
|
|
TokenContractBasicState,
|
|
TokenBalancesResult,
|
|
} from "../../shared/TokenContract";
|
|
import { ShardedClient } from "../../client/ShardedClient";
|
|
import { PutTransactionWasSuccessful } from "../../client/TransactionData";
|
|
import { NETWORK } from "../../constant";
|
|
|
|
const AVL_CLIENT = new AvlClient(NETWORK .node_base_url, NETWORK.network_shards);
|
|
|
|
/**
|
|
* Structure of the raw data from a WASM contract.
|
|
*/
|
|
interface RawContractData {
|
|
state: { data: string };
|
|
}
|
|
|
|
/**
|
|
* API for the token contract.
|
|
* This minimal implementation only allows for transferring tokens to a single address.
|
|
*
|
|
* The implementation uses the TransactionApi to send transactions, and ABI for the contract to be
|
|
* able to build the RPC for the transfer transaction.
|
|
*/
|
|
export class TokenV2Contract implements TokenContract {
|
|
private readonly transactionApi: TransactionApi | undefined;
|
|
private readonly shardedClient: ShardedClient;
|
|
|
|
constructor(shardedClient: ShardedClient, transactionApi: TransactionApi | undefined) {
|
|
this.transactionApi = transactionApi;
|
|
this.shardedClient = shardedClient;
|
|
}
|
|
|
|
/**
|
|
* Build and send transfer transaction.
|
|
* @param to receiver of tokens
|
|
* @param amount number of tokens to send
|
|
*/
|
|
public transfer(
|
|
contractAddress: BlockchainAddress,
|
|
to: BlockchainAddress,
|
|
amount: BN,
|
|
memo: string,
|
|
): Promise<PutTransactionWasSuccessful> {
|
|
if (this.transactionApi === undefined) {
|
|
throw new Error("No account logged in");
|
|
}
|
|
|
|
// First build the RPC buffer that is the payload of the transaction.
|
|
const rpc = transfer(to, amount);
|
|
// Then send the payload via the transaction API.
|
|
// We are sending the transaction to the configured address of the token address, and use the
|
|
// GasCost utility to estimate how much the transaction costs.
|
|
return this.transactionApi.sendTransactionAndWait(contractAddress.asString(), rpc, 10_000);
|
|
}
|
|
|
|
public basicState(contractAddress: BlockchainAddress): Promise<TokenContractBasicState> {
|
|
return this.shardedClient
|
|
.getContractData<RawContractData>(contractAddress.asString())
|
|
.then((contract) => {
|
|
if (contract == null) {
|
|
throw new Error("Could not find data for contract");
|
|
}
|
|
|
|
// Reads the state of the contract
|
|
const stateBuffer = Buffer.from(contract.serializedContract.state.data, "base64");
|
|
|
|
return deserializeTokenState({ state: stateBuffer });
|
|
});
|
|
}
|
|
|
|
public async tokenBalances(
|
|
contractAddress: BlockchainAddress,
|
|
keyAddress: string | undefined
|
|
): Promise<TokenBalancesResult> {
|
|
const keyBytes = keyAddress === undefined ? undefined : Buffer.from(keyAddress, "hex");
|
|
const values = await AVL_CLIENT.getContractStateAvlNextN(
|
|
contractAddress.asString(),
|
|
0,
|
|
keyBytes,
|
|
10
|
|
);
|
|
if (values === undefined) {
|
|
throw new Error("Could not fetch balances");
|
|
}
|
|
|
|
const balances: Map<BlockchainAddress, BN> = new Map();
|
|
let lastKey: string | undefined = undefined;
|
|
|
|
values.forEach((val) => {
|
|
const tuple = Object.entries(val)[0];
|
|
const blockchainAddressRaw = Buffer.from(tuple[0], "base64").toString("hex");
|
|
const blockchainAddress = BlockchainAddress.fromString(blockchainAddressRaw);
|
|
const value = new LittleEndianByteInput(
|
|
Buffer.from(tuple[1], "base64")
|
|
).readUnsignedBigInteger(16);
|
|
balances.set(blockchainAddress, value);
|
|
lastKey = blockchainAddressRaw;
|
|
});
|
|
|
|
return {
|
|
balances,
|
|
next_cursor: lastKey,
|
|
};
|
|
}
|
|
|
|
public async tokenBalance(
|
|
contractAddress: BlockchainAddress,
|
|
owner: BlockchainAddress
|
|
): Promise<BN> {
|
|
const keyBytes = owner.asBuffer();
|
|
const value = await AVL_CLIENT.getContractStateAvlValue(
|
|
contractAddress.asString(),
|
|
0,
|
|
keyBytes
|
|
);
|
|
if (value === undefined) {
|
|
throw new Error("No balance for user: " + owner.asString());
|
|
}
|
|
return new LittleEndianByteInput(value).readUnsignedBigInteger(16);
|
|
}
|
|
}
|