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

2921 staking wallet api #2935

Merged
merged 5 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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,62 @@
import { CONFIGS } from '../../config';
import { Protocols } from '@taquito/taquito';
import { ProtoGreaterOrEqual } from '@taquito/michel-codec';
import { InvalidStakingAddressError, InvalidFinalizeUnstakeAmountError } from '@taquito/core';

CONFIGS().forEach(({ lib, rpc, setup, protocol }) => {
const Tezos = lib;
const parisAndAlpha = ProtoGreaterOrEqual(protocol, Protocols.PtParisBQ) ? test : test.skip;
describe(`Test staking pseudo operations using: ${rpc}`, () => {
beforeAll(async () => {
await setup(true);
try {
const delegateOp = await Tezos.contract.setDelegate({
delegate: 'tz1PZY3tEWmXGasYeehXYqwXuw2Z3iZ6QDnA', // can use knownBaker in future
source: await Tezos.signer.publicKeyHash()
});
await delegateOp.confirmation();
} catch (e) {
console.log(JSON.stringify(e));
}
});

parisAndAlpha(`should be able to stake successfully: ${rpc}`, async () => {
const op = await Tezos.wallet.stake({ amount: 3000000, mutez: true }).send()
await op.confirmation();
expect(op.status).toBeTruthy();

const stakedBalance = await Tezos.rpc.getStakedBalance(await Tezos.signer.publicKeyHash());
expect(stakedBalance.toNumber()).toEqual(3000000);
});

parisAndAlpha(`should be able to unstake successfully: ${rpc}`, async () => {
const op = await Tezos.wallet.unstake({ amount: 1 }).send()
await op.confirmation();
expect(op.status).toBeTruthy();

const UnstakedBalance = await Tezos.rpc.getUnstakedFrozenBalance(await Tezos.signer.publicKeyHash());
expect(UnstakedBalance.toNumber()).toEqual(1000000); // 1000000 mutez = 1 tez
});

parisAndAlpha(`should be able to finalizeUnstake successfully: ${rpc}`, async () => {
const op = await Tezos.wallet.finalizeUnstake({ }).send()
await op.confirmation();
expect(op.status).toBeTruthy();
});

parisAndAlpha('should throw error when param is against pseudo operation', async () => {
expect(async () => {
const op = await Tezos.wallet.stake({ amount: 1, to: 'tz1PZY3tEWmXGasYeehXYqwXuw2Z3iZ6QDnA' }).send();
}).rejects.toThrow(InvalidStakingAddressError);
expect(async () => {
const op = await Tezos.wallet.unstake({ amount: 1, to: 'tz1PZY3tEWmXGasYeehXYqwXuw2Z3iZ6QDnA' }).send();
}).rejects.toThrow(InvalidStakingAddressError);
expect(async () => {
const op = await Tezos.wallet.finalizeUnstake({ to: 'tz1PZY3tEWmXGasYeehXYqwXuw2Z3iZ6QDnA' }).send();
}).rejects.toThrow(InvalidStakingAddressError);
expect(async () => {
const op = await Tezos.wallet.finalizeUnstake({ amount: 1 }).send();
}).rejects.toThrow(InvalidFinalizeUnstakeAmountError);
});
});
});
56 changes: 55 additions & 1 deletion packages/taquito-beacon-wallet/src/taquito-beacon-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import {
WalletOriginateParams,
WalletProvider,
WalletTransferParams,
WalletStakeParams,
WalletUnstakeParams,
WalletFinalizeUnstakeParams,
} from '@taquito/taquito';
import { buf2hex, hex2buf, mergebuf } from '@taquito/utils';
import { UnsupportedActionError } from '@taquito/core';
Expand Down Expand Up @@ -89,6 +92,51 @@ export class BeaconWallet implements WalletProvider {
);
}

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

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

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

async mapIncreasePaidStorageWalletParams(params: () => Promise<WalletIncreasePaidStorageParams>) {
let walletParams: WalletIncreasePaidStorageParams;
await this.client.showPrepare();
Expand Down Expand Up @@ -148,7 +196,13 @@ export class BeaconWallet implements WalletProvider {
}

removeDefaultParams(
params: WalletTransferParams | WalletOriginateParams | WalletDelegateParams,
params:
| WalletTransferParams
| WalletStakeParams
| WalletUnstakeParams
| WalletFinalizeUnstakeParams
| WalletOriginateParams
| WalletDelegateParams,
operatedParams: any
) {
// If fee, storageLimit or gasLimit is undefined by user
Expand Down
26 changes: 26 additions & 0 deletions packages/taquito/src/wallet/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ import {
IncreasePaidStorageParams,
OriginateParams,
TransferParams,
StakeParams,
UnstakeParams,
FinalizeUnstakeParams,
} from '../operations/types';

export type WalletDefinedFields = 'source';

export type WalletTransferParams = Omit<TransferParams, WalletDefinedFields>;

export type WalletStakeParams = Omit<StakeParams, WalletDefinedFields>;

export type WalletUnstakeParams = Omit<UnstakeParams, WalletDefinedFields>;

export type WalletFinalizeUnstakeParams = Omit<FinalizeUnstakeParams, WalletDefinedFields>;

export type WalletOriginateParams<TStorage = any> = Omit<
OriginateParams<TStorage>,
WalletDefinedFields
Expand Down Expand Up @@ -37,6 +46,23 @@ export interface WalletProvider {
*/
mapTransferParamsToWalletParams: (params: () => Promise<WalletTransferParams>) => Promise<any>;

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

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

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

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

Expand All @@ -24,6 +27,18 @@ export class LegacyWalletProvider implements WalletProvider {
return attachKind(await params(), OpKind.TRANSACTION);
}

async mapStakeParamsToWalletParams(params: () => Promise<WalletStakeParams>) {
return attachKind(await params(), OpKind.TRANSACTION);
}

async mapUnstakeParamsToWalletParams(params: () => Promise<WalletUnstakeParams>) {
return attachKind(await params(), OpKind.TRANSACTION);
}

async mapFinalizeUnstakeParamsToWalletParams(params: () => Promise<WalletFinalizeUnstakeParams>) {
return attachKind(await params(), OpKind.TRANSACTION);
}

async mapOriginateParamsToWalletParams(params: () => Promise<WalletOriginateParams>) {
return attachKind(await params(), OpKind.ORIGINATION);
}
Expand Down
98 changes: 95 additions & 3 deletions packages/taquito/src/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ import {
WalletOriginateParams,
WalletProvider,
WalletTransferParams,
WalletStakeParams,
WalletUnstakeParams,
WalletFinalizeUnstakeParams,
} from './interface';
import {
InvalidAddressError,
InvalidContractAddressError,
InvalidOperationKindError,
InvalidStakingAddressError,
InvalidFinalizeUnstakeAmountError,
} from '@taquito/core';
import {
validateAddress,
Expand Down Expand Up @@ -351,11 +356,98 @@ export class Wallet {

/**
*
* @description
* @description Stake a given amount for the source address
*
* @returns
* @returns An operation handle with the result from the rpc node
*
* @param Stake pseudo-operation parameter
*/
stake(params: WalletStakeParams) {
return this.walletCommand(async () => {
const mappedParams = await this.walletProvider.mapStakeParamsToWalletParams(async () => {
const source = await this.pkh();
if (!params.to) {
params.to = source;
}
if (params.to !== source) {
throw new InvalidStakingAddressError(params.to);
}
params.parameter = { entrypoint: 'stake', value: { prim: 'Unit' } };
return params;
});
const opHash = await this.walletProvider.sendOperations([mappedParams]);
return this.context.operationFactory.createTransactionOperation(opHash);
});
}

/**
*
* @description Unstake the given amount. If "everything" is given as amount, unstakes everything from the staking balance.
* Unstaked tez remains frozen for a set amount of cycles (the slashing period) after the operation. Once this period is over,
* the operation "finalize unstake" must be called for the funds to appear in the liquid balance.
*
* @returns An operation handle with the result from the rpc node
*
* @param Unstake pseudo-operation parameter
*/
unstake(params: WalletUnstakeParams) {
return this.walletCommand(async () => {
const mappedParams = await this.walletProvider.mapUnstakeParamsToWalletParams(async () => {
const source = await this.pkh();
if (!params.to) {
params.to = source;
}
if (params.to !== source) {
throw new InvalidStakingAddressError(params.to);
}
params.parameter = { entrypoint: 'unstake', value: { prim: 'Unit' } };
return params;
});
const opHash = await this.walletProvider.sendOperations([mappedParams]);
return await this.context.operationFactory.createTransactionOperation(opHash);
});
}

/**
*
* @description Transfer all the finalizable unstaked funds of the source to their liquid balance
* @returns An operation handle with the result from the rpc node
*
* @param params
* @param Finalize_unstake pseudo-operation parameter
*/
finalizeUnstake(params: WalletFinalizeUnstakeParams) {
return this.walletCommand(async () => {
const mappedParams = await this.walletProvider.mapFinalizeUnstakeParamsToWalletParams(
async () => {
const source = await this.pkh();
if (!params.to) {
params.to = source;
}
if (params.to !== source) {
throw new InvalidStakingAddressError(params.to);
}
if (!params.amount) {
params.amount = 0;
}
if (params.amount !== 0) {
throw new InvalidFinalizeUnstakeAmountError('Amount must be 0 to finalize unstake.');
}
params.parameter = { entrypoint: 'finalize_unstake', value: { prim: 'Unit' } };
return params;
}
);
const opHash = await this.walletProvider.sendOperations([mappedParams]);
return await this.context.operationFactory.createTransactionOperation(opHash);
});
}

/**
*
* @description Increase the paid storage of a smart contract.
*
* @returns A wallet command from which we can send the operation to the wallet
*
* @param params operation parameter
*/
increasePaidStorage(params: WalletIncreasePaidStorageParams) {
const destinationValidation = validateAddress(params.destination);
Expand Down
Loading