Skip to content

Commit

Permalink
Merge pull request #829 from tradingstrategy-ai/824-usdt-payment
Browse files Browse the repository at this point in the history
Update payment wizard to support USDT
  • Loading branch information
kenkunz authored Oct 7, 2024
2 parents 202146c + d19b67b commit 086eeb8
Show file tree
Hide file tree
Showing 21 changed files with 609 additions and 245 deletions.
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

0 comments on commit 086eeb8

Please sign in to comment.