Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added transfer ticket to the wallet API and updated integration tests #3003

Merged
merged 2 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { CONFIGS } from '../../config';
import { DefaultContractType, TezosToolkit } from '@taquito/taquito';
import { ticketsSendTz } from '../../data/code_with_ticket_transfer';
import { RpcClient, TicketTokenParams } from '@taquito/rpc';

CONFIGS().forEach(({ lib, rpc, setup, createAddress }) => {
const Tezos = lib;
const client = new RpcClient(rpc);

let ticketSendContract: DefaultContractType;
let recipient: TezosToolkit;
let sender: TezosToolkit;
let recipientPkh: string;
let senderPkh: string
let ticketToken: TicketTokenParams;

describe(`Transfer tickets between implicit accounts using: ${rpc}`, () => {

beforeAll(async () => {
await setup(true);
try {
recipient = await createAddress();
sender = await createAddress();

recipientPkh = await recipient.signer.publicKeyHash();
senderPkh = await sender.wallet.pkh();

const fundSender = await Tezos.contract.transfer({ to: senderPkh, amount: 5 });
await fundSender.confirmation();

const ticketSendOrigination = await Tezos.contract.originate({ code: ticketsSendTz, storage: null });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing ticketsSendTz originated here the 2nd time made me want to originate it at integration-tests/originate-known-contracts.ts as knownTicketContract. And integration-tests/__tests__/rpc/nodes.spec.ts can benefit from this too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's not a bad idea, we can make a ticket for that.

await ticketSendOrigination.confirmation();

ticketSendContract = await ticketSendOrigination.contract();
ticketToken = { ticketer: ticketSendContract.address, content_type: { prim: 'string' }, content: { string: 'Ticket' } };

// Send 3 tickets from the originated contract to sender
const sendTickets = await ticketSendContract.methods.default(senderPkh, '3').send();
dsawali marked this conversation as resolved.
Show resolved Hide resolved
await sendTickets.confirmation();
} catch (error) {
console.log(error);
}
});

it('should transfer 1 ticket from an implicit account to another implicit account using a Wallet', async () => {
// Check balances before transferring tickets
const balanceBefore = await client.getTicketBalance(recipientPkh, ticketToken);
expect(balanceBefore).toEqual('0');

const senderBalanceBefore = await client.getTicketBalance(senderPkh, ticketToken);
expect(senderBalanceBefore).toEqual('3');

// Transfer 1 ticket from sender to recipient
const transferTicketOp = await sender.wallet.transferTicket({
ticketContents: { string: "Ticket" },
ticketTy: { prim: "string" },
ticketTicketer: ticketSendContract.address,
ticketAmount: 1,
destination: recipientPkh,
entrypoint: 'default',
}).send();

await transferTicketOp.confirmation();

expect(await transferTicketOp.status()).toEqual('applied');
console.log(`opHash: ${transferTicketOp.opHash}`);

// Check balances after transferring tickets
const balanceAfter = await client.getTicketBalance(recipientPkh, ticketToken);
expect(balanceAfter).toEqual('1');

const senderBalanceAfter = await client.getTicketBalance(senderPkh, ticketToken);
expect(senderBalanceAfter).toEqual('2');
});
});
});
17 changes: 17 additions & 0 deletions packages/taquito-beacon-wallet/src/taquito-beacon-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
WalletStakeParams,
WalletUnstakeParams,
WalletFinalizeUnstakeParams,
WalletTransferTicketParams,
createTransferTicketOperation,
} from '@taquito/taquito';
import { buf2hex, hex2buf, mergebuf } from '@taquito/utils';
import { UnsupportedActionError } from '@taquito/core';
Expand Down Expand Up @@ -97,6 +99,21 @@ export class BeaconWallet implements WalletProvider {
);
}

async mapTransferTicketParamsToWalletParams(params: () => Promise<WalletTransferTicketParams>) {
let walletParams: WalletTransferTicketParams;
await this.client.showPrepare();
try {
walletParams = await params();
} catch (err) {
await this.client.hideUI(['alert']);
throw err;
}
return this.removeDefaultParams(
walletParams,
await createTransferTicketOperation(this.formatParameters(walletParams))
);
}

async mapStakeParamsToWalletParams(params: () => Promise<WalletStakeParams>) {
let walletParams: WalletStakeParams;
await this.client.showPrepare();
Expand Down
1 change: 1 addition & 0 deletions packages/taquito/src/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export * from './operation';
export * from './transaction-operation';
export * from './origination-operation';
export * from './delegation-operation';
export * from './transfer-ticket-operation';
export * from './interface';
export * from './legacy';
10 changes: 10 additions & 0 deletions packages/taquito/src/wallet/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
StakeParams,
UnstakeParams,
FinalizeUnstakeParams,
TransferTicketParams,
} from '../operations/types';

export type WalletDefinedFields = 'source';
Expand All @@ -30,6 +31,8 @@ export type WalletFailingNoopParams = Omit<FailingNoopParams, WalletDefinedField

export type WalletIncreasePaidStorageParams = Omit<IncreasePaidStorageParams, WalletDefinedFields>;

export type WalletTransferTicketParams = Omit<TransferTicketParams, WalletDefinedFields>;

export interface WalletProvider {
/**
* @description Request the public key hash from the wallet
Expand All @@ -46,6 +49,13 @@ export interface WalletProvider {
*/
mapTransferParamsToWalletParams: (params: () => Promise<WalletTransferParams>) => Promise<any>;

/**
* @description Transform WalletTransferTicketParams into a format compliant with the underlying wallet
*/
mapTransferTicketParamsToWalletParams: (
params: () => Promise<WalletTransferTicketParams>
) => Promise<any>;

/**
* @description Transform WalletStakeParams into a format compliant with the underlying wallet
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/taquito/src/wallet/legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
WalletStakeParams,
WalletUnstakeParams,
WalletFinalizeUnstakeParams,
WalletTransferTicketParams,
} from './interface';
import { WalletParamsWithKind } from './wallet';

Expand Down Expand Up @@ -51,6 +52,10 @@ export class LegacyWalletProvider implements WalletProvider {
return attachKind(await params(), OpKind.INCREASE_PAID_STORAGE);
}

async mapTransferTicketParamsToWalletParams(params: () => Promise<WalletTransferTicketParams>) {
return attachKind(await params(), OpKind.TRANSFER_TICKET);
}

async sendOperations(params: WalletParamsWithKind[]) {
const op = await this.context.batch.batch(params as any).send();
return op.hash;
Expand Down
12 changes: 12 additions & 0 deletions packages/taquito/src/wallet/operation-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { IncreasePaidStorageWalletOperation } from './increase-paid-storage-oper
import { WalletOperation } from './operation';
import { OriginationWalletOperation } from './origination-operation';
import { TransactionWalletOperation } from './transaction-operation';
import { TransferTicketWalletOperation } from './transfer-ticket-operation';
import { ConfirmationTimeoutError } from '../errors';

export function timeoutAfter<T>(timeoutMillisec: number): (source: Observable<T>) => Observable<T> {
Expand Down Expand Up @@ -132,6 +133,17 @@ export class OperationFactory {
);
}

async createTransferTicketOperation(
hash: string,
config: OperationFactoryConfig = {}
): Promise<TransferTicketWalletOperation> {
return new TransferTicketWalletOperation(
hash,
this.context.clone(),
await this.createHeadObservableFromConfig(config)
);
}

async createDelegationOperation(
hash: string,
config: OperationFactoryConfig = {}
Expand Down
54 changes: 54 additions & 0 deletions packages/taquito/src/wallet/transfer-ticket-operation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { WalletOperation, OperationStatus } from './operation';
import { Context } from '../context';
import { Observable } from 'rxjs';
import {
BlockResponse,
OpKind,
OperationContentsAndResultReveal,
OperationContentsAndResultTransferTicket,
} from '@taquito/rpc';
import { ObservableError } from './errors';

export class TransferTicketWalletOperation extends WalletOperation {
constructor(
public readonly opHash: string,
protected readonly context: Context,
newHead$: Observable<BlockResponse>
) {
super(opHash, context, newHead$);
}

public async revealOperation() {
const operationResult = await this.operationResults();
if (!operationResult) {
throw new ObservableError('operationResult returned undefined');
}

return operationResult.find((x) => x.kind === OpKind.REVEAL) as
| OperationContentsAndResultReveal
| undefined;
}

public async transferTicketOperation() {
const operationResult = await this.operationResults();
if (!operationResult) {
throw new ObservableError('operationResult returned undefined');
}
return operationResult.find((x) => x.kind === OpKind.TRANSFER_TICKET) as
| OperationContentsAndResultTransferTicket
| undefined;
}

public async status(): Promise<OperationStatus> {
if (!this._included) {
return 'pending';
}

const op = await this.transferTicketOperation();
if (!op) {
return 'unknown';
}

return op.metadata.operation_result.status;
}
}
37 changes: 36 additions & 1 deletion packages/taquito/src/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
WalletStakeParams,
WalletUnstakeParams,
WalletFinalizeUnstakeParams,
WalletTransferTicketParams,
} from './interface';
import {
InvalidAddressError,
Expand All @@ -43,7 +44,8 @@ export type WalletParamsWithKind =
| withKind<WalletTransferParams, OpKind.TRANSACTION>
| withKind<WalletOriginateParams, OpKind.ORIGINATION>
| withKind<WalletDelegateParams, OpKind.DELEGATION>
| withKind<WalletIncreasePaidStorageParams, OpKind.INCREASE_PAID_STORAGE>;
| withKind<WalletIncreasePaidStorageParams, OpKind.INCREASE_PAID_STORAGE>
| withKind<WalletTransferTicketParams, OpKind.TRANSFER_TICKET>;

export class WalletOperationBatch {
private operations: WalletParamsWithKind[] = [];
Expand Down Expand Up @@ -115,6 +117,19 @@ export class WalletOperationBatch {
return this;
}

/**
* @description Add an TransferTicket operation to the batch
* @param param TransferTicket operation parameter
*/
withTransferTicket(params: WalletTransferTicketParams) {
const destinationValidation = validateAddress(params.destination);
if (destinationValidation !== ValidationResult.VALID) {
throw new InvalidAddressError(params.destination, invalidDetail(destinationValidation));
}
this.operations.push({ kind: OpKind.TRANSFER_TICKET, ...params });
return this;
}

private async mapOperation(param: WalletParamsWithKind) {
switch (param.kind) {
case OpKind.TRANSACTION:
Expand Down Expand Up @@ -323,6 +338,26 @@ export class Wallet {
});
}

/**
* @description Transfer tezos tickets from current address to a specific address or a smart contract
* @returns a TransferTicketWalletOperation promise object when followed by .send()
* @param params operation parameter
*/
transferTicket(params: WalletTransferTicketParams) {
const toValidation = validateAddress(params.destination);
if (toValidation !== ValidationResult.VALID) {
throw new InvalidAddressError(params.destination, invalidDetail(toValidation));
}
return this.walletCommand(async () => {
const mappedParams = await this.walletProvider.mapTransferTicketParamsToWalletParams(
async () => params
);

const opHash = await this.walletProvider.sendOperations([mappedParams]);
return this.context.operationFactory.createTransferTicketOperation(opHash);
});
}

/**
* @description Stake a given amount for the source address
* @returns a TransactionWalletOperation promise object when followed by .send()
Expand Down
Loading