Skip to content

Commit

Permalink
Added support for approved transfer API and fixed an issue with appro… (
Browse files Browse the repository at this point in the history
#67)

Signed-off-by: Kipkap <[email protected]>
  • Loading branch information
himalayan-dev authored Feb 1, 2024
1 parent d247a2a commit c912078
Show file tree
Hide file tree
Showing 14 changed files with 202 additions and 106 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import _ from 'lodash';
import { FC, useContext, useRef, useState } from 'react';
import { Form } from 'react-bootstrap';
import {
MetaMaskContext,
MetamaskActions,
Expand Down Expand Up @@ -35,6 +37,8 @@ const TransferCrypto: FC<Props> = ({
const [assetType, setAssetType] = useState<'HBAR' | 'TOKEN' | 'NFT'>('HBAR');
const [assetId, setAssetId] = useState('');
const [sendAmount, setSendAmount] = useState(0);
const [isDelegatedTransfer, setIsDelegatedTransfer] = useState(false); // New state for Delegated Transfer checkbox
const [sendFromAddress, setSendFromAddress] = useState('');

const externalAccountRef = useRef<GetExternalAccountRef>(null);

Expand All @@ -54,6 +58,9 @@ const TransferCrypto: FC<Props> = ({
if (assetType === 'TOKEN' || assetType === 'NFT') {
transfers[0].assetId = assetId;
}
if (isDelegatedTransfer && !_.isEmpty(sendFromAddress)) {
transfers[0].from = sendFromAddress;
}
// const maxFee = 1; // Note that if you don't pass this, default is whatever the snap has set

const TUUMESERVICEADDRESS = '0.0.98'; // Hedera Fee collection account
Expand Down Expand Up @@ -99,6 +106,31 @@ const TransferCrypto: FC<Props> = ({
form: (
<>
<ExternalAccount ref={externalAccountRef} />
<Form>
<Form.Check
type="checkbox"
id="delegated-transfer-checkbox"
label="Delegated Transfer"
onChange={(e) => {
setIsDelegatedTransfer(e.target.checked);
}}
/>
{isDelegatedTransfer && (
<>
<Form.Label>
Enter an account Id to send Hbar from (Only needed for
delegated transfer)
</Form.Label>
<Form.Control
size="lg"
type="text"
placeholder="Owner Account Id to send from"
style={{ marginBottom: 8 }}
onChange={(e) => setSendFromAddress(e.target.value)}
/>
</>
)}
</Form>
<label>
Enter an account Id or an EVM address to send Hbar to
<input
Expand Down
7 changes: 3 additions & 4 deletions packages/hedera-wallet-snap/packages/site/src/types/snap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,9 @@ export type Account = {
export type SimpleTransfer = {
assetType: 'HBAR' | 'TOKEN' | 'NFT';
to: string;
// amount must be in low denom
amount: number;
// Token or NFT ID (as string)
assetId?: string;
amount: number; // amount must be in low denom
assetId?: string; // Token or NFT ID (as string)
from?: string; // Only for approved allowances
};

export type ServiceFee = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "git+https://github.com/hashgraph/hedera-metamask-snaps.git"
},
"source": {
"shasum": "y5nhwdqF3cvVd+mZM1l+cjuOgJ/aZ+oy2uWnTWp7N0c=",
"shasum": "V70Jy5+S4r6Yh3S5lRIbYu2rw9plcvg6gzlCd+WUHxs=",
"location": {
"npm": {
"filePath": "dist/snap.js",
Expand Down
8 changes: 0 additions & 8 deletions packages/hedera-wallet-snap/packages/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
receipt: await approveAllowance(walletSnapParams, request.params),
};
}
case 'transferApprovedCrypto': {
isValidTransferCryptoParams(request.params);
return {
currentAccount: state.currentAccount,
receipt: await transferCrypto(walletSnapParams, request.params),
// receipt: await transferApprovedCrypto(walletSnapParams, request.params),
};
}
case 'deleteAllowance': {
isValidDeleteAllowanceParams(request.params);
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ export async function approveAllowance(
): Promise<TxReceipt> {
const { origin, state, mirrorNodeUrl } = walletSnapParams;

const { spenderAccountId, amount, assetType, assetDetail } =
approveAllowanceRequestParams;
const {
spenderAccountId,
amount,
assetType,
assetDetail = {} as ApproveAllowanceAssetDetail,
} = approveAllowanceRequestParams;

const { hederaEvmAddress, hederaAccountId, network } = state.currentAccount;

Expand All @@ -88,10 +92,15 @@ export async function approveAllowance(
if (assetType === 'HBAR') {
panelToShow.push(text(`Asset: ${assetType}`));
} else {
const walletBalance =
state.accountState[hederaEvmAddress][network].accountInfo.balance;
assetDetail.assetDecimals = walletBalance.tokens[assetDetail.assetId]
? walletBalance.tokens[assetDetail.assetId].decimals
: NaN;

const hederaService = new HederaServiceImpl(network, mirrorNodeUrlToUse);
const assetProp = assetDetail as ApproveAllowanceAssetDetail;
const tokenInfo: MirrorTokenInfo = await hederaService.getTokenById(
assetProp.assetId,
assetDetail.assetId,
);
if (assetType === 'NFT' && assetDetail?.all) {
panelToShow.push(
Expand All @@ -100,11 +109,26 @@ export async function approveAllowance(
),
);
}
if (_.isEmpty(tokenInfo)) {
const errMessage = `Error while trying to get token info for ${assetDetail.assetId} from Hedera Mirror Nodes at this time`;
console.error(errMessage);
panelToShow.push(text(errMessage));
panelToShow.push(
text(`Proceed only if you are sure about the asset ID being correct`),
);
} else {
panelToShow.push(text(`Asset Name: ${tokenInfo.name}`));
panelToShow.push(text(`Asset Type: ${tokenInfo.type}`));
panelToShow.push(text(`Id: ${assetDetail.assetId}`));
panelToShow.push(text(`Symbol: ${tokenInfo.symbol}`));
assetDetail.assetDecimals = Number(tokenInfo.decimals);
}
if (!Number.isFinite(assetDetail.assetDecimals)) {
const errMessage = `Error while trying to get token info for ${assetDetail.assetId} from Hedera Mirror Nodes at this time`;
console.error(errMessage);
throw providerErrors.unsupportedMethod(errMessage);
}

panelToShow.push(text(`Asset Name: ${tokenInfo.name}`));
panelToShow.push(text(`Asset Type: ${tokenInfo.type}`));
panelToShow.push(text(`Id: ${assetProp.assetId}`));
panelToShow.push(text(`Symbol: ${tokenInfo.symbol}`));
panelToShow.push(
text(
`Total Supply: ${(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ export async function getAccountInfo(
// Deduct service Fee if set
if (serviceFee.percentageCut > 0) {
await deductServiceFee(
state.accountState[hederaEvmAddress][network].accountInfo.balance,
serviceFeeToPay,
serviceFee.toAddress,
hederaClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@
import { providerErrors } from '@metamask/rpc-errors';
import { divider, heading, text } from '@metamask/snaps-ui';
import _ from 'lodash';
import {
AccountBalance,
SimpleTransfer,
TxReceipt,
} from '../../services/hedera';
import { AccountInfo } from 'src/types/account';
import { SimpleTransfer, TxReceipt } from '../../services/hedera';
import { HederaServiceImpl } from '../../services/impl/hedera';
import { createHederaClient } from '../../snap/account';
import { generateCommonPanel, snapDialog } from '../../snap/dialog';
Expand Down Expand Up @@ -104,11 +101,6 @@ export async function transferCrypto(
{ origin, state, mirrorNodeUrl: mirrorNodeUrlToUse } as WalletSnapParams,
{} as GetAccountInfoRequestParams,
);
let currentBalance =
state.accountState[hederaEvmAddress][network].accountInfo.balance;
if (!currentBalance) {
currentBalance = {} as AccountBalance;
}

const panelToShow = [
heading('Transfer Crypto'),
Expand All @@ -130,28 +122,56 @@ export async function transferCrypto(
panelToShow.push(divider());
panelToShow.push(divider());

panelToShow.push(text(`Asset Type: ${transfer.assetType}`));
panelToShow.push(divider());
let asset = '';
let feeToDisplay = 0;
let walletBalance =
state.accountState[hederaEvmAddress][network].accountInfo.balance;
if (!_.isEmpty(transfer.from)) {
const ownerAccountInfo: AccountInfo = await getAccountInfo(
{
origin,
state,
mirrorNodeUrl: mirrorNodeUrlToUse,
} as WalletSnapParams,
{ accountId: transfer.from } as GetAccountInfoRequestParams,
);
walletBalance = ownerAccountInfo.balance;
panelToShow.push(text(`Transaction Type: Delegated Transfer`));
panelToShow.push(text(`Owner Account Id: ${transfer.from as string}`));
}
panelToShow.push(text(`Asset Type: ${transfer.assetType}`));
panelToShow.push(divider());
if (transfer.assetType === 'HBAR') {
if (currentBalance.hbars < transfer.amount + serviceFeesToPay.HBAR) {
const errMessage = `You do not have enough Hbar in your balance to transfer the requested amount`;
if (walletBalance.hbars < transfer.amount + serviceFeesToPay.HBAR) {
const errMessage = `There is not enough Hbar in the wallet to transfer the requested amount`;
console.error(errMessage);
throw providerErrors.unauthorized(errMessage);
panelToShow.push(text(errMessage));
panelToShow.push(
text(
`Proceed only if you are sure about the amount being transferred`,
),
);
}
asset = 'HBAR';
} else {
transfer.decimals = walletBalance.tokens[transfer.assetId as string]
? walletBalance.tokens[transfer.assetId as string].decimals
: NaN;
if (
!currentBalance.tokens[transfer.assetId as string] ||
currentBalance.tokens[transfer.assetId as string].balance <
!walletBalance.tokens[transfer.assetId as string] ||
walletBalance.tokens[transfer.assetId as string].balance <
transfer.amount
) {
const errMessage = `You either do not own ${
const errMessage = `This wallet either does not own ${
transfer.assetId as string
} or do not have enough in your balance to transfer the requested amount`;
} or there is not enough balance to transfer the requested amount`;
console.error(errMessage);
throw providerErrors.unauthorized(errMessage);
panelToShow.push(text(errMessage));
panelToShow.push(
text(
`Proceed only if you are sure about the amount being transferred`,
),
);
}
panelToShow.push(text(`Asset Id: ${transfer.assetId as string}`));
const tokenInfo = await hederaService.getTokenById(
Expand All @@ -173,6 +193,14 @@ export async function transferCrypto(
panelToShow.push(text(`Asset Name: ${tokenInfo.name}`));
panelToShow.push(text(`Asset Type: ${tokenInfo.type}`));
panelToShow.push(text(`Symbol: ${asset}`));
transfer.decimals = Number(tokenInfo.decimals);
}
if (!Number.isFinite(transfer.decimals)) {
const errMessage = `Error while trying to get token info for ${
transfer.assetId as string
} from Hedera Mirror Nodes at this time`;
console.error(errMessage);
throw providerErrors.unsupportedMethod(errMessage);
}

if (serviceFeesToPay[transfer.assetType] > 0) {
Expand Down Expand Up @@ -227,7 +255,6 @@ export async function transferCrypto(
);

txReceipt = await hederaClient.transferCrypto({
currentBalance,
transfers,
memo,
maxFee,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ import { ApproveAllowanceAssetDetail } from '../types/params';
export type SimpleTransfer = {
assetType: 'HBAR' | 'TOKEN' | 'NFT';
to: string;
// amount must be in low denom
amount: number;
// Token or NFT ID (as string)
assetId?: string;
amount: number; // amount must be in low denom
assetId?: string; // Token or NFT ID (as string)
from?: string; // Only for approved allowances
decimals?: number; // Only for Token/NFT transfers
};

export type Token = {
Expand Down Expand Up @@ -168,7 +168,6 @@ export type SimpleHederaClient = {
associateTokens(options: { tokenIds: string[] }): Promise<TxReceipt>;

transferCrypto(options: {
currentBalance: AccountBalance;
transfers: SimpleTransfer[];
memo: string | null;
maxFee: number | null; // hbars
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import {
AccountAllowanceApproveTransaction,
AccountId,
Hbar,
type Client,
} from '@hashgraph/sdk';
Expand Down Expand Up @@ -52,32 +53,32 @@ export async function approveAllowance(

if (options.assetType === 'HBAR') {
transaction.approveHbarAllowance(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
client.operatorAccountId!,
client.operatorAccountId as AccountId,
options.spenderAccountId,
new Hbar(options.amount),
);
} else if (options.assetType === 'TOKEN') {
const multiplier = Math.pow(
10,
options.assetDetail?.assetDecimals as number,
);
transaction.approveTokenAllowance(
options.assetDetail?.assetId as string,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
client.operatorAccountId!,
client.operatorAccountId as AccountId,
options.spenderAccountId,
options.amount,
options.amount * multiplier,
);
} else if (options.assetType === 'NFT') {
if (options.assetDetail?.all) {
transaction.approveTokenNftAllowanceAllSerials(
options.assetDetail?.assetId,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
client.operatorAccountId!,
client.operatorAccountId as AccountId,
options.spenderAccountId,
);
} else {
transaction.approveTokenNftAllowance(
options.assetDetail?.assetId as string,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
client.operatorAccountId!,
client.operatorAccountId as AccountId,
options.spenderAccountId,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,7 @@ import {

import { AccountInfo } from '../../../../types/account';
import { ApproveAllowanceAssetDetail } from '../../../../types/params';
import {
AccountBalance,
SimpleHederaClient,
SimpleTransfer,
TxReceipt,
} from '../../../hedera';
import { SimpleHederaClient, SimpleTransfer, TxReceipt } from '../../../hedera';
import { approveAllowance } from './approveAllowance';
import { deleteAccount } from './deleteAccount';
import { deleteAllowance } from './deleteAllowance';
Expand Down Expand Up @@ -85,7 +80,6 @@ export class SimpleHederaClientImpl implements SimpleHederaClient {
}

async transferCrypto(options: {
currentBalance: AccountBalance;
transfers: SimpleTransfer[];
memo: string | null;
maxFee: number | null;
Expand Down
Loading

0 comments on commit c912078

Please sign in to comment.