/*
* 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 .
*
*/
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 {
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 {
return this.shardedClient
.getContractData(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 {
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 = 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 {
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);
}
}