Skip to content

Commit

Permalink
fix: sip10 token send form fees bug
Browse files Browse the repository at this point in the history
  • Loading branch information
alter-eggo committed May 22, 2024
1 parent 0c6c4d1 commit a6246b5
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Navigate, useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';

import type { CryptoAssetBalance, MarketData, Sip10CryptoAssetInfo } from '@leather-wallet/models';

Expand All @@ -22,12 +22,14 @@ function Sip10TokenSendFormLoader({ children }: Sip10TokenSendFormLoaderProps) {
const token = useSip10Token(contractId ?? '');
const priceAsMarketData = useAlexCurrencyPriceAsMarketData();
const toast = useToast();
const navigate = useNavigate();

if (!contractId) return;

if (!token) {
toast.error('Token not found');
return <Navigate to={RouteUrls.SendCryptoAsset} />;
navigate(RouteUrls.SendCryptoAsset);
return;
}

return children({
Expand Down
1 change: 1 addition & 0 deletions src/app/query/stacks/fees/fees.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function fetchTransactionFeeEstimation(currentNetwork: any, limiter: PQueue) {
}
),
{
priority: 2,
throwOnTimeout: true,
}
);
Expand Down
114 changes: 60 additions & 54 deletions src/app/store/transactions/token-transfer.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
uintCV,
} from '@stacks/transactions';

import { logger } from '@shared/logger';
import type { StacksSendFormValues, StacksTransactionFormValues } from '@shared/models/form.model';

import { stxToMicroStx } from '@app/common/money/unit-conversion';
Expand Down Expand Up @@ -86,65 +87,70 @@ export function useGenerateFtTokenTransferUnsignedTx(info: Sip10CryptoAssetInfo)

return useCallback(
async (values?: StacksSendFormValues | StacksTransactionFormValues) => {
if (!account) return;

const functionName = 'transfer';
const recipient =
values && 'recipient' in values
? createAddress(values.recipient || '')
: createEmptyAddress();
const amount = values && 'amount' in values ? values.amount : 0;
const memo =
values && 'memo' in values && values.memo !== ''
? someCV(bufferCVFromString(values.memo || ''))
: noneCV();

const amountAsFractionalUnit = ftUnshiftDecimals(amount, info.decimals || 0);
const {
address: contractAddress,
contractName,
assetName,
} = getAssetStringParts(info.contractId);

const postConditionOptions = {
amount: amountAsFractionalUnit,
contractAddress,
contractAssetName: assetName,
contractName,
stxAddress: account.address,
};

const postConditions = [makePostCondition(postConditionOptions)];

// (transfer (uint principal principal) (response bool uint))
const functionArgs: ClarityValue[] = [
uintCV(amountAsFractionalUnit),
standardPrincipalCVFromAddress(createAddress(account.address)),
standardPrincipalCVFromAddress(recipient),
];

if (info.hasMemo) {
functionArgs.push(memo);
}
try {
if (!account) return;

const functionName = 'transfer';
const recipient =
values && 'recipient' in values
? createAddress(values.recipient || '')
: createEmptyAddress();
const amount = values && 'amount' in values ? values.amount : 0;
const memo =
values && 'memo' in values && values.memo !== ''
? someCV(bufferCVFromString(values.memo || ''))
: noneCV();

const amountAsFractionalUnit = ftUnshiftDecimals(amount, info.decimals || 0);
const {
address: contractAddress,
contractName,
assetName,
} = getAssetStringParts(info.contractId);

const options = {
txData: {
txType: TransactionTypes.ContractCall,
const postConditionOptions = {
amount: amountAsFractionalUnit,
contractAddress,
contractAssetName: assetName,
contractName,
functionName,
functionArgs: functionArgs.map(serializeCV).map(arg => bytesToHex(arg)),
postConditions,
postConditionMode: PostConditionMode.Deny,
network,
stxAddress: account.address,
};

const postConditions = [makePostCondition(postConditionOptions)];

// (transfer (uint principal principal) (response bool uint))
const functionArgs: ClarityValue[] = [
uintCV(amountAsFractionalUnit),
standardPrincipalCVFromAddress(createAddress(account.address)),
standardPrincipalCVFromAddress(recipient),
];

if (info.hasMemo) {
functionArgs.push(memo);
}

const options = {
txData: {
txType: TransactionTypes.ContractCall,
contractAddress,
contractName,
functionName,
functionArgs: functionArgs.map(serializeCV).map(arg => bytesToHex(arg)),
postConditions,
postConditionMode: PostConditionMode.Deny,
network,
publicKey: account.stxPublicKey,
},
fee: stxToMicroStx(values?.fee || 0).toNumber(),
publicKey: account.stxPublicKey,
},
fee: stxToMicroStx(values?.fee || 0).toNumber(),
publicKey: account.stxPublicKey,
nonce: Number(values?.nonce) ?? nextNonce?.nonce,
} as const;
nonce: Number(values?.nonce) ?? nextNonce?.nonce,
} as const;

return generateUnsignedTransaction(options);
return generateUnsignedTransaction(options);
} catch (error) {
logger.error('Failed to generate unsigned transaction', error);
return;
}
},
[account, info.contractId, info.decimals, info.hasMemo, network, nextNonce?.nonce]
);
Expand Down
14 changes: 14 additions & 0 deletions src/app/ui/utils/get-asset-contract-address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* getAssetContractAddress
*
* Gets the assets contract address of a string: contract_id or fully qualified asset name.
*
* @param value - the source string: [principal].[contract-name] or [principal].[contract-name]::[asset-name]
*/
export function getAssetContractAddress(value: string): string {
if (value.includes('.')) {
return value.split('.')[0];
}

return value;
}
5 changes: 3 additions & 2 deletions src/app/ui/utils/get-asset-string-parts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getAssetContractAddress } from './get-asset-contract-address';
import { getAssetName } from './get-asset-name';
import { getContractName } from './get-contract-name';

Expand All @@ -23,13 +24,13 @@ export const getAssetStringParts = (
// }
// );
return {
address: fullyQualifiedName,
address: getAssetContractAddress(fullyQualifiedName),
contractName: fullyQualifiedName,
assetName: fullyQualifiedName,
};
}

const address = fullyQualifiedName.split('.')[0];
const address = getAssetContractAddress(fullyQualifiedName);
const contractName = getContractName(fullyQualifiedName);
const assetName = getAssetName(fullyQualifiedName);

Expand Down
24 changes: 24 additions & 0 deletions src/app/ui/utils/stacks-ft-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getAssetContractAddress } from './get-asset-contract-address';

const fullyQualifiedName = 'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.btc-monkeys-bananas::BANANA';
const contractId = 'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.btc-monkeys-bananas';

const contractAddress = 'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQW2S7D0';

describe('getAssetContractAddress', () => {
test('should return the contract address from a fully qualified asset name', () => {
const assetContractAddress = getAssetContractAddress(fullyQualifiedName);

expect(assetContractAddress).toBe(contractAddress);

Check failure on line 12 in src/app/ui/utils/stacks-ft-utils.spec.ts

View workflow job for this annotation

GitHub Actions / test-unit

src/app/ui/utils/stacks-ft-utils.spec.ts > getAssetContractAddress > should return the contract address from a fully qualified asset name

AssertionError: expected 'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3…' to be 'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQW2…' // Object.is equality - Expected + Received - SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQW2S7D0 + SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C ❯ src/app/ui/utils/stacks-ft-utils.spec.ts:12:34
});

test('should return the contract address from a contract id', () => {
const assetContractAddress = getAssetContractAddress(contractId);
return expect(assetContractAddress).toBe(contractAddress);

Check failure on line 17 in src/app/ui/utils/stacks-ft-utils.spec.ts

View workflow job for this annotation

GitHub Actions / test-unit

src/app/ui/utils/stacks-ft-utils.spec.ts > getAssetContractAddress > should return the contract address from a contract id

AssertionError: expected 'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3…' to be 'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQW2…' // Object.is equality - Expected + Received - SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQW2S7D0 + SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C ❯ src/app/ui/utils/stacks-ft-utils.spec.ts:17:41
});

test('should return the contract address from a contract address', () => {
const assetContractAddress = getAssetContractAddress(contractAddress);
expect(assetContractAddress).toBe(contractAddress);
});
});

0 comments on commit a6246b5

Please sign in to comment.