/* * 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); } }