import * as aptos from 'aptos'; import type { TxExtraArgs, OatOffice } from './common'; import { TxInFlight } from './common'; import type { Config } from './config'; import { TableClient } from './table_client'; type Token = aptos.TokenTypes.Token; type TokenId = aptos.TokenTypes.TokenId; type TokenDataId = aptos.TokenTypes.TokenDataId; export interface OptionalFilters { byCreator?: aptos.HexString; byCollection?: string; byName?: string; } interface TypeStore { type: aptos.Types.MoveStructTag; data: T; } interface OatCreatorReceipt { collection_name: string; oat_addr: string; } interface OatCreatorStore { receipts: OatCreatorReceipt[]; } // default: maximum is 0.1 aptos coin. const defaultTxExtraArgs: TxExtraArgs = { maxGasAmount: BigInt(100_000), gasUnitPrice: BigInt(100), }; export class MintChallenge { constructor( public readonly moduleAddr: aptos.TxnBuilderTypes.AccountAddress, public readonly moduleName: string, public readonly structName: string, public readonly sigId: number | bigint, public readonly pubkeyId: number | bigint, public readonly receiverAddr: aptos.TxnBuilderTypes.AccountAddress, public readonly oatAddr: aptos.TxnBuilderTypes.AccountAddress, public readonly tokenName: string, public readonly amount: number | bigint, public readonly expiredAt: number | bigint, ) {} serialize(serializer: aptos.BCS.Serializer): void { this.moduleAddr.serialize(serializer); serializer.serializeStr(this.moduleName); serializer.serializeStr(this.structName); serializer.serializeU64(this.sigId); serializer.serializeU64(this.pubkeyId); this.receiverAddr.serialize(serializer); this.oatAddr.serialize(serializer); serializer.serializeStr(this.tokenName); serializer.serializeU64(this.amount); serializer.serializeU64(this.expiredAt); } } export class OatClient { client: aptos.AptosClient; txBuilder: aptos.TransactionBuilderABI; moduleAddress: string; constructor(nodeUrl: string, config: Config) { this.client = new aptos.AptosClient(nodeUrl); this.txBuilder = new aptos.TransactionBuilderABI(config.abis.map((abi) => new aptos.HexString(abi).toUint8Array())); this.moduleAddress = config.moduleAddress; } private typeName(id: string) { return this.moduleAddress + '::' + 'oat' + '::' + id; } private async broadcast( sender: aptos.AptosAccount, payload: aptos.TxnBuilderTypes.TransactionPayload, ): Promise { const rawTransaction = await this.client.generateRawTransaction(sender.address(), payload, defaultTxExtraArgs); const bcsTxn = aptos.AptosClient.generateBCSTransaction(sender, rawTransaction); const pendingTransaction = await this.client.submitSignedBCSTransaction(bcsTxn); return new TxInFlight(this.client, pendingTransaction.hash); } public async getCreatedOatAddresses(creator: aptos.HexString) { const creatorStore = await this.client.getAccountResource(creator, this.typeName('OatCreatorStore')); return (creatorStore.data as OatCreatorStore).receipts; } public async instantiate( account: aptos.AptosAccount, seed: string, collectionName: string, description: string, uri: string, ) { const payload = this.txBuilder.buildTransactionPayload( this.typeName('instantiate'), [], [seed, collectionName, description, uri], ); return this.broadcast(account, payload); } public async transferAdmin(admin: aptos.AptosAccount, oatAddr: aptos.HexString, newAdmin: aptos.HexString) { const payload = this.txBuilder.buildTransactionPayload(this.typeName('transfer_admin'), [], [oatAddr, newAdmin]); return this.broadcast(admin, payload); } public async setOperatorRole( admin: aptos.AptosAccount, oatAddr: aptos.HexString, operator: aptos.HexString, isDelete: boolean, ) { const payload = this.txBuilder.buildTransactionPayload( this.typeName('set_operator_role'), [], [oatAddr, operator, isDelete], ); return this.broadcast(admin, payload); } public async setMinterRole( operator: aptos.AptosAccount, oatAddr: aptos.HexString, minter: aptos.HexString, isDelete: boolean, ) { const payload = this.txBuilder.buildTransactionPayload( this.typeName('set_minter_role'), [], [oatAddr, minter, isDelete], ); return this.broadcast(operator, payload); } public async upsertPubkey( operator: aptos.AptosAccount, oatAddr: aptos.HexString, pubkeyId: aptos.BCS.AnyNumber, pubkey: aptos.HexString, ) { const payload = this.txBuilder.buildTransactionPayload( this.typeName('upsert_pubkey'), [], [oatAddr, pubkeyId, pubkey.toUint8Array()], ); return this.broadcast(operator, payload); } public async createNewTokendata( minter: aptos.AptosAccount, oatAddr: aptos.HexString, name: string, description: string, uri: string, max: aptos.BCS.AnyNumber, property_keys: Array = [], property_values: Array = [], property_types: Array = [], ) { const payload = this.txBuilder.buildTransactionPayload( this.typeName('create_new_tokendata'), [], [oatAddr, name, description, uri, max, property_keys, property_values, property_types], ); return this.broadcast(minter, payload); } // expired_at is a UNIX timestamp in seconds. public async claim( receiver: aptos.AptosAccount, oatAddr: aptos.HexString, sigId: aptos.BCS.AnyNumber, pubkeyId: aptos.BCS.AnyNumber, tokenName: string, amount: aptos.BCS.AnyNumber, expiredAt: aptos.BCS.AnyNumber, signature: aptos.HexString, ) { const payload = this.txBuilder.buildTransactionPayload( this.typeName('claim'), [], [oatAddr, sigId, pubkeyId, tokenName, amount, expiredAt, signature.toUint8Array()], ); return this.broadcast(receiver, payload); } public makeSignature( signer: aptos.AptosAccount, sigId: aptos.BCS.AnyNumber, pubkeyId: aptos.BCS.AnyNumber, receiverAddr: aptos.HexString, oatAddr: aptos.HexString, tokenName: string, amount: aptos.BCS.AnyNumber, expiredAt: aptos.BCS.AnyNumber, ): aptos.HexString { const challenge = new MintChallenge( aptos.TxnBuilderTypes.AccountAddress.fromHex(this.moduleAddress), 'oat', 'MintChallenge', sigId, pubkeyId, aptos.TxnBuilderTypes.AccountAddress.fromHex(receiverAddr), aptos.TxnBuilderTypes.AccountAddress.fromHex(oatAddr), tokenName, amount, expiredAt, ); const challengeHex = aptos.HexString.fromUint8Array(aptos.BCS.bcsToBytes(challenge)); return signer.signHexString(challengeHex); } public async directMintTo( minter: aptos.AptosAccount, receiver: aptos.HexString, oatAddr: aptos.HexString, tokenName: string, amount: aptos.BCS.AnyNumber, ) { const payload = this.txBuilder.buildTransactionPayload( this.typeName('direct_mint_to'), [], [receiver, oatAddr, tokenName, amount], ); return this.broadcast(minter, payload); } public async getOatOffice(oatAddr: aptos.HexString): Promise { return (await this.client.getAccountResource(oatAddr, this.typeName('OatOffice'))).data as OatOffice; } public async isSignatureIDUsed(sigID: aptos.BCS.AnyNumber, oatAddr: aptos.HexString): Promise { const office = await this.getOatOffice(oatAddr); const table = new TableClient(this.client, office.sig_ids, 'u64', 'bool'); return (await table.get(sigID)) ? true : false; } // listUserNFTs brute force query user's token position. public async listUserNFTs( userAddr: aptos.HexString, start: number | bigint | undefined, limit: number, filters?: OptionalFilters, ): Promise { let events = await this.client.getEventsByEventHandle(userAddr, '0x3::token::TokenStore', 'deposit_events', { start, limit, }); if (filters) { events = events.filter((e) => { const tokenID: TokenId = e.data.id; if (filters.byCreator && tokenID.token_data_id.creator != filters.byCreator.toString()) { return false; } else if (filters.byCollection && tokenID.token_data_id.collection != filters.byCollection) { return false; } else if (filters.byName && tokenID.token_data_id.name != filters.byName) { return false; } return true; }); } const tokenCli = new aptos.TokenClient(this.client); const tokens: (Token | null)[] = await Promise.all( events.map(async (v) => { const tokenID: TokenId = v.data.id; try { return tokenCli.getTokenForAccount(userAddr, tokenID); } catch { return null; } }), ); return tokens.filter((n) => n != null); } }