Skip to content
Open
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
Expand Up @@ -105,6 +105,7 @@ type TransactionStatus = 'idle' | 'loading' | 'success' | 'error';

const ADDRESS_VALIDATION_DELAY = 800;


const validateAddress = (address: string): boolean => {
if (address.toLowerCase().endsWith('.coti')) {
return true;
Expand Down Expand Up @@ -605,6 +606,9 @@ export const TransferTokens: React.FC<TransferTokensProps> = React.memo(
decimals: 18,
};

const isErc20Token = Boolean(currentToken?.address && !currentToken?.tokenId);
const usePublicTransfer = isErc20Token;

const balanceNum = useMemo(() => {
if (!currentBalance || currentBalance === '0') {
return 0;
Expand Down Expand Up @@ -1058,6 +1062,7 @@ export const TransferTokens: React.FC<TransferTokensProps> = React.memo(
to: targetAddress,
amount: amountInWei.toString(),
aesKey: aesKey || '',
publicTransfer: usePublicTransfer,
});
}

Expand Down Expand Up @@ -1088,6 +1093,7 @@ export const TransferTokens: React.FC<TransferTokensProps> = React.memo(
addressInput,
resolvedAddress,
amount,
usePublicTransfer,
currentToken,
fetchTokenBalance,
onBack,
Expand Down Expand Up @@ -1217,6 +1223,7 @@ export const TransferTokens: React.FC<TransferTokensProps> = React.memo(
</MaxButton>
))}
</BalanceRow>

</>
)}

Expand Down
14 changes: 14 additions & 0 deletions packages/site/src/components/Header/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,20 @@ export const MobileMenuDropdown = styled.div<{ $isVisible: boolean }>`
top: 60px;
}

@media screen and (min-width: 771px) {
left: auto;
right: 48px;
transform: ${(props) =>
props.$isVisible
? 'translateY(0) scale(1)'
: 'translateY(-12px) scale(0.95)'};
transform-origin: top right;
}

@media screen and (min-width: 771px) and (max-width: 992px) {
right: 32px;
}

@media screen and (min-width: 771px) and (min-height: 821px) {
display: none;
}
Expand Down
95 changes: 74 additions & 21 deletions packages/site/src/hooks/useTokenOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ export type TransferParams = {
amount?: string;
tokenId?: string;
aesKey?: string;
publicTransfer?: boolean;
};

export type ImportTokenParams = {
Expand Down Expand Up @@ -529,6 +530,14 @@ export const useTokenOperations = (provider: BrowserProvider) => {
let detectedVersion: 64 | 256 | undefined;
let isConfidential = false;

const probed256 = await probeConfidentialVersion256(
tokenAddress,
browserProvider,
);
if (probed256) {
return { confidential: true, version: probed256 };
}

if (typeof erc165.supportsInterface === 'function') {
try {
const supports256 = await erc165.supportsInterface(
Expand Down Expand Up @@ -571,7 +580,9 @@ export const useTokenOperations = (provider: BrowserProvider) => {
.id('transfer(address,(uint256,bytes))')
.slice(2);
const selector256 = ethers
.id('transfer(address,((uint256,uint256),bytes))')
.id(
'transfer(address,(((uint256,uint256),(uint256,uint256)),bytes[2][2]))',
)
.slice(2);

if (
Expand Down Expand Up @@ -621,6 +632,7 @@ export const useTokenOperations = (provider: BrowserProvider) => {
to,
amount,
aesKey,
publicTransfer,
}: TransferParams & { amount: string; aesKey?: string }) => {
return withLoading(async () => {
if (!amount) {
Expand All @@ -632,37 +644,78 @@ export const useTokenOperations = (provider: BrowserProvider) => {

const { confidential, version } =
await getTokenConfidentialStatus(tokenAddress);
const signer = await getBrowserProvider().getSigner();
const browserProvider = getBrowserProvider();
const signer = await browserProvider.getSigner();
let tx;

if (confidential) {
if (confidential && publicTransfer) {
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
tx = await (contract as any)['transfer(address,uint256)'](
to,
ethers.toBigInt(amount),
);
} else if (confidential) {
if (!aesKey) {
throw new Error('AES key is required for private ERC20 transfer');
}
signer.setAesKey(aesKey);
const abi = version === 256 ? PRIVATE_ERC20_256_ABI : PRIVATE_ERC20_ABI;
const selector =
version === 256
? 'transfer(address,((uint256,uint256),bytes))'
: 'transfer(address,(uint256,bytes))';
const contract = new Contract(tokenAddress, abi, signer);
let encryptedAmount = (await signer.encryptValue(
ethers.toBigInt(amount),
tokenAddress,
getSelector(selector),
)) as itUint;
if (version === 256) {
encryptedAmount = normalizeItUint256ForAbi(encryptedAmount);

let confidentialVersion = version;
if (confidentialVersion !== 256) {
const probed = await probeConfidentialVersion256(
tokenAddress,
browserProvider,
);
if (probed) {
confidentialVersion = probed;
}
}

if (confidentialVersion === 256) {
const SELECTOR_256 =
'transfer(address,(((uint256,uint256),(uint256,uint256)),bytes[2][2]))';
const selectorHex = getSelector(SELECTOR_256);
const amountBigInt = ethers.toBigInt(amount);
const mask64 = (1n << 64n) - 1n;

const d1 = (amountBigInt >> 192n) & mask64;
const d2 = (amountBigInt >> 128n) & mask64;
const d3 = (amountBigInt >> 64n) & mask64;
const d4 = amountBigInt & mask64;

const it1 = (await signer.encryptValue(d1, tokenAddress, selectorHex)) as itUint;
const it2 = (await signer.encryptValue(d2, tokenAddress, selectorHex)) as itUint;
const it3 = (await signer.encryptValue(d3, tokenAddress, selectorHex)) as itUint;
const it4 = (await signer.encryptValue(d4, tokenAddress, selectorHex)) as itUint;

const encryptedAmount256 = {
ciphertext: {
high: { high: it1.ciphertext, low: it2.ciphertext },
low: { high: it3.ciphertext, low: it4.ciphertext },
},
signature: [
[it1.signature, it2.signature],
[it3.signature, it4.signature],
],
};

const contract = new Contract(tokenAddress, PRIVATE_ERC20_256_ABI, signer);
tx = await (contract as any)[SELECTOR_256](to, encryptedAmount256);
} else {
const selector = 'transfer(address,(uint256,bytes))';
const contract = new Contract(tokenAddress, PRIVATE_ERC20_ABI, signer);
const encryptedAmount = (await signer.encryptValue(
ethers.toBigInt(amount),
tokenAddress,
getSelector(selector),
)) as itUint;
tx = await (contract as any)[selector](to, encryptedAmount);
}
tx = await (contract as any)[selector](to, encryptedAmount, {
gasLimit: 12000000,
});
} else {
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
tx = await (contract as any)['transfer(address,uint256)'](
to,
ethers.toBigInt(amount),
{ gasLimit: 12000000 },
);
}

Expand Down Expand Up @@ -997,7 +1050,6 @@ export const useTokenOperations = (provider: BrowserProvider) => {
tokenId,
amount,
'0x',
{ gasLimit: 12000000 },
);
if (!tx) {
throw new Error('Transaction could not be initiated');
Expand Down Expand Up @@ -1312,5 +1364,6 @@ export const useTokenOperations = (provider: BrowserProvider) => {
addTokenToMetaMask,
addNFTToMetaMask,
addERC1155ToMetaMask,
getTokenConfidentialStatus,
};
};
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/coti-io/coti-snap.git"
},
"source": {
"shasum": "9rWKLu0IsCpv8L6BMQHJlJaziOG78EuEec36lIqgaDQ=",
"shasum": "WvPDLHiPruUIlHFJ5Azd6EORYbWcM+o7S+ElN9c3wcs=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down