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

Update payment wizard to support USDT #829

Merged
merged 14 commits into from
Oct 7, 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
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ TS_PUBLIC_TYPESENSE_API_URL=https://typesense.tradingstrategy.ai
TS_PUBLIC_TYPESENSE_API_KEY=npdPPJNELDhdr7v6IS9rQUpFG2VvdyAL
TS_PUBLIC_DISCORD_URL=https://discord.gg/5M88m9nM8H
TS_PUBLIC_WALLET_CONNECT_PROJECT_ID=9ee7efad98897eb60ba023db6aa72355
TS_PUBLIC_STRATEGIES='[{"id":"enzyme-polygon-matic-eth-usdc","name":"ETH-MATIC-USDC momentum","url":"https://enzyme-polygon-matic-eth-usdc.tradingstrategy.ai","frontpage":true},{"id":"enzyme-arbitrum-eth-btc-rsi","name":"ETH-BTC price surge (Arbitrum)","url":"https://enzyme-arbitrum-eth-btc-rsi.tradingstrategy.ai/","depositOnEnzyme":true,"frontpage":true},{"id":"enzyme-polygon-eth-rolling-ratio","name":"ETH/BTC rolling ratio","url":"https://enzyme-polygon-eth-rolling-ratio.tradingstrategy.ai/","frontpage":true},{"id":"enzyme-polygon-eth-btc-rsi","name":"ETH-BTC price surge","url":"https://enzyme-polygon-eth-btc-rsi.tradingstrategy.ai/","frontpage":true,"hiddenPositions":[4]},{"id":"enzyme-ethereum-btc-eth-stoch-rsi","name":"Stochastic ETH/BTC long","url":"https://enzyme-ethereum-btc-eth-stoch-rsi.tradingstrategy.ai","frontpage":true},{"id":"enzyme-polygon-eth-btc-usdc","name":"ETH-BTC-USDC momentum","url":"https://enzyme-polygon-eth-btc-usdc.tradingstrategy.ai","new_version_id":"enzyme-polygon-eth-btc-rsi","frontpage":true},{"id":"enzyme-polygon-matic-usdc","name":"MATIC breakout","url":"https://enzyme-polygon-matic-usdc.tradingstrategy.ai"},{"id":"enzyme-polygon-eth-breakout","name":"ETH breakout","url":"https://enzyme-polygon-eth-breakout.tradingstrategy.ai"},{"id":"enzyme-polygon-eth-usdc","name":"ETH Breakout bounce","url":"https://enzyme-polygon-eth-usdc.tradingstrategy.ai"},{"id":"enzyme-polygon-eth-usdc-sls","name":"ETH Balance snap","url":"https://enzyme-polygon-eth-usdc-sls.tradingstrategy.ai"},{"id":"polygon-eth-spot-short","name":"ETH mean reversion bounce","url":"https://polygon-eth-spot-short.tradingstrategy.ai"},{"id":"arbitrum-btc-breakout","name":"BTC Barrier Breach","url":"https://arbitrum-btc-breakout.tradingstrategy.ai"}]'
TS_PUBLIC_STRATEGIES='[{"id":"enzyme-polygon-matic-eth-usdc","name":"ETH-MATIC-USDC momentum","url":"https://enzyme-polygon-matic-eth-usdc.tradingstrategy.ai","frontpage":true},{"id":"enzyme-arbitrum-eth-btc-rsi","name":"ETH-BTC price surge (Arbitrum)","url":"https://enzyme-arbitrum-eth-btc-rsi.tradingstrategy.ai/","frontpage":true},{"id":"enzyme-polygon-eth-rolling-ratio","name":"ETH/BTC rolling ratio","url":"https://enzyme-polygon-eth-rolling-ratio.tradingstrategy.ai/","frontpage":true},{"id":"enzyme-polygon-eth-btc-rsi","name":"ETH-BTC price surge","url":"https://enzyme-polygon-eth-btc-rsi.tradingstrategy.ai/","frontpage":true,"hiddenPositions":[4]},{"id":"enzyme-ethereum-btc-eth-stoch-rsi","name":"Stochastic ETH/BTC long","url":"https://enzyme-ethereum-btc-eth-stoch-rsi.tradingstrategy.ai","frontpage":true},{"id":"enzyme-polygon-eth-btc-usdc","name":"ETH-BTC-USDC momentum","url":"https://enzyme-polygon-eth-btc-usdc.tradingstrategy.ai","new_version_id":"enzyme-polygon-eth-btc-rsi","frontpage":true},{"id":"enzyme-polygon-matic-usdc","name":"MATIC breakout","url":"https://enzyme-polygon-matic-usdc.tradingstrategy.ai"},{"id":"enzyme-polygon-eth-breakout","name":"ETH breakout","url":"https://enzyme-polygon-eth-breakout.tradingstrategy.ai"},{"id":"enzyme-polygon-eth-usdc","name":"ETH Breakout bounce","url":"https://enzyme-polygon-eth-usdc.tradingstrategy.ai"},{"id":"enzyme-polygon-eth-usdc-sls","name":"ETH Balance snap","url":"https://enzyme-polygon-eth-usdc-sls.tradingstrategy.ai"},{"id":"polygon-eth-spot-short","name":"ETH mean reversion bounce","url":"https://polygon-eth-spot-short.tradingstrategy.ai"},{"id":"arbitrum-btc-breakout","name":"BTC Barrier Breach","url":"https://arbitrum-btc-breakout.tradingstrategy.ai"}]'
TS_PUBLIC_GEO_BLOCK='{"strategies:view":["CU","IR","KP","RU","SY"],"strategies:deposit":["CU","IR","KP","RU","SY","US","UK"]}'
# Uncomment to test chain maintenance error
# TS_PUBLIC_CHAINS_UNDER_MAINTENANCE='{ "binance": "BNB Chain" }'
Expand Down
11 changes: 11 additions & 0 deletions src/lib/assets/logos/tokens/pol.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/lib/assets/logos/tokens/usdt.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 0 additions & 41 deletions src/lib/assets/tos/tos-map.json

This file was deleted.

69 changes: 45 additions & 24 deletions src/lib/eth-defi/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { Abi, Log } from 'viem';
import type { Config, GetBalanceParameters, GetBalanceReturnType } from '@wagmi/core';
import { decodeEventLog, formatUnits, isAddressEqual, parseAbi, erc20Abi } from 'viem';
import { readContract, readContracts } from '@wagmi/core';
import { readContract, readContracts, simulateContract, writeContract } from '@wagmi/core';
import comptrollerABI from '$lib/eth-defi/abi/enzyme/ComptrollerLib.json';
import { formatNumber } from '$lib/helpers/formatters';
import tosMap from '$lib/assets/tos/tos-map.json';

/**
* Extract events from transaction logs
Expand Down Expand Up @@ -119,38 +118,60 @@ export function getTokenLabel(symbol: string | undefined, address: Address) {
}

/**
* Get a strategy denomination token balance for a given comptroller and address
* Get a strategy denomination token address for a given chain and comptroller
*/
export async function getDenominationToken(
export async function getDenominationAsset(
config: Config,
{ comptroller, address, chainId }: { comptroller: Address; address: Address; chainId?: number | undefined }
{ chainId, comptroller }: { chainId?: number; comptroller: Address }
) {
const token = (await readContract(config, {
return readContract(config, {
chainId,
address: comptroller,
abi: comptrollerABI,
functionName: 'getDenominationAsset'
})) as Address;

return getTokenBalance(config, { address, token, chainId });
}) as Promise<Address>;
}

export type TosInfo = {
fileName?: string;
acceptanceMessage?: string;
};
/**
* Get strategy denomination token info for a given chain and comptroller
*/
export async function getDenominationTokenInfo(
config: Config,
{ chainId, comptroller }: { chainId?: number; comptroller: Address }
) {
const address = await getDenominationAsset(config, { chainId, comptroller });
return getTokenInfo(config, { chainId, address });
}

/**
* Get Terms of Service info based on the mapping stored in: src/lib/assets/tos/tos-map.json
*
* The mapping is stored in the form: chainId -> address -> version -> TosInfo
*
* @param chainId - chainId of the strategy
* @param address - Terms of Service contract address of the strategy
* @param version - Terms of Service version
*
* Get strategy denomination token balance for a given chain, comptroller and address
*/
export function getTosInfo(chainId: number, address: string, version: number): TosInfo {
// @ts-ignore
return tosMap[chainId]?.[address]?.[version] ?? {};
export async function getDenominationTokenBalance(
config: Config,
{ chainId, comptroller, address }: { chainId?: number; comptroller: Address; address: Address }
) {
const token = await getDenominationAsset(config, { chainId, comptroller });
return getTokenBalance(config, { chainId, token, address });
}

type ApproveTokenTransferParams = {
chainId?: number;
address: Address;
sender: Address;
value: number | bigint;
};

export async function approveTokenTransfer(
config: Config,
{ chainId, address, sender, value }: ApproveTokenTransferParams
) {
const { request } = await simulateContract(config, {
abi: erc20Abi,
chainId,
address,
functionName: 'approve',
args: [sender, BigInt(value)]
});

return writeContract(config, request);
}
96 changes: 96 additions & 0 deletions src/lib/trade-executor/helpers/tos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* This module is used to map Terms of Service contracts and versions for each blockchain
* to corresponding ToS file versions and acceptance messages.
*
* The acceptance message is needed to generate a hash and signature; we currently don't
* have a way to retrieve this directly from the blockchain, so it is made available via
* this local mapping.
*/
export type TosVersionParams = {
chainId: number;
address: Address;
version: number;
};

export type TosVersion = TosVersionParams & {
fileName?: string;
acceptanceMessage?: string;
};

const tosVersions: TosVersion[] = [
// Etherium versions
{
chainId: 1,
address: '0xd63c1bE9D8B56CCcD6fd2Dd9F9c030c6a9916f5F',
version: 1,
fileName: '2024-03-20.txt',
acceptanceMessage:
'I agree on Terms of Service. I understand smart contract trading is risky and I may lose all of my deposits. \n\nThis Terms of Service version 1, dated 2024-03-20, was published at https://tradingstrategy.ai/tos/2024-03-20.txt'
},

// Polygon versions
{
chainId: 137,
address: '0xbe1418df0bAd87577de1A41385F19c6e77312780',
version: 1,
fileName: '155d6737cb.txt',
acceptanceMessage:
'I read and agree on terms of service (version 1) to use\nsmart contract software deployed on a blockchain. \n\nThe terms of service text was published 10.1.2024 at https://example.com.\nThe unique identifier hash for this terms of service text was 0x0000000000000000000000000000000000000000.'
},
{
chainId: 137,
address: '0xbe1418df0bAd87577de1A41385F19c6e77312780',
version: 2,
fileName: '2024-03-20.txt',
acceptanceMessage:
'I read and agree on Terms of Service to access the\\nsmart contract software deployed on a blockchain.\\n\\nThe Terms of Service version 2, dated 2024-03-20, was published at \\nhttps://tradingstrategy.ai/tos/2024-03-20.txt'
},
{
chainId: 137,
address: '0xbe1418df0bAd87577de1A41385F19c6e77312780',
version: 3,
fileName: '2024-03-20.txt',
acceptanceMessage:
'I read and agree on Terms of Service to access the\nsmart contract software deployed on a blockchain.\n\nThe Terms of Service version 3, dated 2024-03-20, was published at \nhttps://tradingstrategy.ai/tos/2024-03-20.txt'
},
{
chainId: 137,
address: '0xbe1418df0bAd87577de1A41385F19c6e77312780',
version: 4,
fileName: '2024-03-30.txt',
acceptanceMessage: '<failed>'
},
{
chainId: 137,
address: '0xbe1418df0bAd87577de1A41385F19c6e77312780',
version: 5,
fileName: '2024-03-30.txt',
acceptanceMessage:
'I agree on Terms of Service. I understand smart contract trading is risky and I may lose all of my deposits. \n\nThis Terms of Service version 5, dated 2024-03-30, was published at https://tradingstrategy.ai/tos/2024-03-30.txt'
},

// Arbitrum versions
{
chainId: 42161,
address: '0xDCD7C644a6AA72eb2f86781175b18ADc30Aa4f4d',
version: 1,
fileName: '2024-03-20.txt',
acceptanceMessage:
'I agree on Terms of Service. I understand smart contract trading is risky and I may lose all of my deposits. \n\nThis Terms of Service version 1, dated 2024-03-20, was published at https://tradingstrategy.ai/tos/2024-03-20.txt'
}
];

/**
* Get Terms of Service version info for a given chain, address and version
*
* @param chainId - chainId of the strategy
* @param address - Terms of Service contract address of the strategy
* @param version - Terms of Service version
*
*/
export function getTosVersion(params: TosVersionParams): TosVersion {
const tosVersion = tosVersions.find((tos) => {
return (['chainId', 'address', 'version'] as const).every((key) => tos[key] === params[key]);
});
return { ...params, ...tosVersion };
}
15 changes: 5 additions & 10 deletions src/lib/trade-executor/strategy/summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,18 @@ import { keyMetricSchema } from '../statistics/key-metric';
export const assetManagementMode = z.enum(['hot_wallet', 'enzyme']);

export const enzymeSmartContractsSchema = z.object({
vault: hexString.nullish(),
comptroller: hexString.nullish(),
generic_adapter: hexString.nullish(),
gas_relay_paymaster_lib: hexString.nullish(),
gas_relay_paymaster_factory: hexString.nullish(),
integration_manager: hexString.nullish(),
fund_value_calculator: hexString.nullish(),
payment_forwarder: hexString.nullish(),
guard: hexString.nullish(),
vault: hexString,
comptroller: hexString,
fund_value_calculator: hexString,
payment_forwarder: hexString,
terms_of_service: hexString.nullish()
});
export type EnzymeSmartContracts = z.infer<typeof enzymeSmartContractsSchema>;

export const onChainDataSchema = z.object({
chain_id: chainId.nullish(),
asset_management_mode: assetManagementMode,
smart_contracts: enzymeSmartContractsSchema,
smart_contracts: enzymeSmartContractsSchema.partial(),
owner: hexString.nullish(),
trade_executor_hot_wallet: hexString.nullish()
});
Expand Down
2 changes: 1 addition & 1 deletion src/lib/wallet/VaultBalance.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
const value = fetchVaultNetValue(address);

async function fetchVaultShares(address: Address) {
const vaultShares = await getTokenBalance(config, { token: contracts.vault!, address });
const vaultShares = await getTokenBalance(config, { token: contracts.vault, address });
dispatch('dataFetch', { vaultShares });
return vaultShares;
}
Expand Down
9 changes: 6 additions & 3 deletions src/routes/wizard/connect-wallet/balance/+page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ import { get } from 'svelte/store';
import { wizard } from 'wizard/store';
import { type ConfiguredChainId, config } from '$lib/wallet';
import { getAccount, getBalance } from '@wagmi/core';
import { type GetTokenBalanceReturnType, getDenominationToken } from '$lib/eth-defi/helpers';
import { type GetTokenBalanceReturnType, getDenominationTokenBalance } from '$lib/eth-defi/helpers';

export async function load() {
const { address } = getAccount(config) as { address: Address };
const { chainId, contracts } = get(wizard).data! as { chainId: ConfiguredChainId; contracts: EnzymeSmartContracts };
const { chainId, contracts } = get(wizard).data! as {
chainId: ConfiguredChainId;
contracts: Partial<EnzymeSmartContracts>;
};
const { comptroller } = contracts;

let denominationToken: Promise<GetTokenBalanceReturnType> | undefined = undefined;
if (comptroller) {
denominationToken = getDenominationToken(config, { address, comptroller, chainId });
denominationToken = getDenominationTokenBalance(config, { address, comptroller, chainId });
}

return {
Expand Down
28 changes: 26 additions & 2 deletions src/routes/wizard/deposit/+layout.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import type { EnzymeSmartContracts } from 'trade-executor/strategy/summary';
import { type ConfiguredChainId, config } from '$lib/wallet';
import { get } from 'svelte/store';
import { wizard } from 'wizard/store';
import { assertNotGeoBlocked } from '$lib/helpers/geo';
import { type TokenInfo, type GetTokenBalanceReturnType, getDenominationTokenInfo } from '$lib/eth-defi/helpers';

export type DepositWizardData = {
chainId: ConfiguredChainId;
strategyName: string;
contracts: EnzymeSmartContracts;
canForwardPayment: boolean;
denominationTokenInfo: TokenInfo;
denominationToken?: GetTokenBalanceReturnType;
nativeCurrency?: GetTokenBalanceReturnType;
tosSignature?: Address | '';
tosHash?: Address;
};

export async function load({ parent }) {
const { admin, ipCountry } = await parent();
Expand All @@ -18,11 +32,21 @@ export async function load({ parent }) {
{ slug: 'success', label: 'Success' }
];

const { chainId, contracts } = get(wizard).data as DepositWizardData;
const { comptroller, terms_of_service } = contracts;

// skip "Terms of service" step if no terms_of_service contract
const contracts: EnzymeSmartContracts = get(wizard).data!.contracts;
if (!contracts.terms_of_service) {
if (!terms_of_service) {
steps = steps.filter(({ slug }) => slug !== 'tos');
}

// get denomination token info
const denominationTokenInfo = await getDenominationTokenInfo(config, { chainId, comptroller });

// USDC can forward payment using transferWithAuthorizations; other tokens can't (yet)
const canForwardPayment = denominationTokenInfo.symbol === 'USDC';

wizard.updateData({ denominationTokenInfo, canForwardPayment });

return { title, steps };
}
Loading
Loading