diff --git a/bun.lockb b/bun.lockb index d1a4ddd7..c60a372e 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/app/.env.example b/packages/app/.env.example index cb72d636..b1a71694 100644 --- a/packages/app/.env.example +++ b/packages/app/.env.example @@ -4,4 +4,6 @@ NEXT_PUBLIC_FATHOM_SITE_ID= RPC_GNOSIS= RPC_MAINNET= -RPC_ARBITRUM= \ No newline at end of file +RPC_ARBITRUM= + +NEXT_STACKLY_SUBGRAPH_API_KEY= \ No newline at end of file diff --git a/packages/landing/analytics/constants.ts b/packages/landing/analytics/constants.ts index daee0bc4..95c955ac 100644 --- a/packages/landing/analytics/constants.ts +++ b/packages/landing/analytics/constants.ts @@ -24,6 +24,7 @@ export const EVENTS = { WHAT_IS_STACK_CLICK: `${FAQ_WHAT_IS}-stack`, WHAT_IS_DCA: `${FAQ_WHAT_IS}-dca`, WHY_TO_DCA: `${FAQ}-why-to-dca`, + NETWORKS_AVAILABLE: `${FAQ}-networks-available`, }, HERO_BANNER: { STACK_NOW_CLICK: `${LAUNCH_APP}-hero-banner`, diff --git a/packages/landing/components/sections/FAQ/constants.ts b/packages/landing/components/sections/FAQ/constants.ts index d759a722..c0c5ee9f 100644 --- a/packages/landing/components/sections/FAQ/constants.ts +++ b/packages/landing/components/sections/FAQ/constants.ts @@ -67,4 +67,11 @@ export const FAQ_QUESTIONS_AND_ANSWERS: FaqQa[] = [ ], trackEventName: EVENTS.SECTIONS.FAQ.HOW_CANCEL_STACK, }, + { + question: "Which networks is Stackly available?", + answers: [ + `Currently Stackly supports Arbitrum, Gnosis and Ethereum mainnet networks.`, + ], + trackEventName: EVENTS.SECTIONS.FAQ.NETWORKS_AVAILABLE, + }, ]; diff --git a/packages/landing/components/sections/HeroBanner/HeroBanner.tsx b/packages/landing/components/sections/HeroBanner/HeroBanner.tsx index 99eea0f9..1b10136a 100644 --- a/packages/landing/components/sections/HeroBanner/HeroBanner.tsx +++ b/packages/landing/components/sections/HeroBanner/HeroBanner.tsx @@ -18,6 +18,35 @@ import { STACKLY_APP_URL } from "@/constants"; export const HeroBanner = () => { return (
+
+

Live on:

+
+ ethereum logo + gnosis logo + arbitrum logo +
+
Empower your portfolio diff --git a/packages/landing/public/assets/images/arbitrum-avatar.svg b/packages/landing/public/assets/images/arbitrum-avatar.svg new file mode 100644 index 00000000..f12a0664 --- /dev/null +++ b/packages/landing/public/assets/images/arbitrum-avatar.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/landing/public/assets/images/ethereum-avatar.svg b/packages/landing/public/assets/images/ethereum-avatar.svg new file mode 100644 index 00000000..ff8d3667 --- /dev/null +++ b/packages/landing/public/assets/images/ethereum-avatar.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/landing/public/assets/images/gnosis-avatar.svg b/packages/landing/public/assets/images/gnosis-avatar.svg new file mode 100644 index 00000000..56db558b --- /dev/null +++ b/packages/landing/public/assets/images/gnosis-avatar.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/sdk/src/vaults/constants.ts b/packages/sdk/src/vaults/constants.ts index 9ad4c24a..e49f786d 100644 --- a/packages/sdk/src/vaults/constants.ts +++ b/packages/sdk/src/vaults/constants.ts @@ -56,12 +56,13 @@ export const COW_SETTLEMENT_ADDRESS_LIST: Record = { [ChainId.ARBITRUM]: COW_SETTLEMENT_ADDRESS, }; -const STACKLY_SUBGRAPH_KEY = "5fdd9e74c326b644f8088068769d72af"; +const STACKLY_SUBGRAPH_API_KEY = + process.env.STACKLY_SUBGRAPH_API_KEY ?? "e7b7ff845e506590498946cd6bf83bf6"; export const SUBGRAPH_ENDPOINT_LIST: Readonly> = { - [ChainId.ETHEREUM]: `https://gateway-arbitrum.network.thegraph.com/api/${STACKLY_SUBGRAPH_KEY}/subgraphs/id/5AMWcp9zv791teVUZT7Nm1jYeaLYmF4VYYnhh3JLZDGc`, - [ChainId.GNOSIS]: `https://gateway-arbitrum.network.thegraph.com/api/${STACKLY_SUBGRAPH_KEY}/subgraphs/id/29A9NjwmhSgF8UKRvEnRbXSyqFmYnrspyPF69mFAMVGX`, - [ChainId.ARBITRUM]: `https://api.studio.thegraph.com/query/63508/stackly-arbitrum-one/version/latest`, // todo: change to correct address + [ChainId.ETHEREUM]: `https://gateway-arbitrum.network.thegraph.com/api/${STACKLY_SUBGRAPH_API_KEY}/subgraphs/id/35bL4ohk2tnXqDnrp7NSyAKW8bbUmGDapyfe2ddCxV8H`, + [ChainId.GNOSIS]: `https://gateway-arbitrum.network.thegraph.com/api/${STACKLY_SUBGRAPH_API_KEY}/subgraphs/id/72Lysd4A2kZFqMqJtPQk3zMEEBExFfXeZbkJGTx8phRL`, + [ChainId.ARBITRUM]: `https://gateway-arbitrum.network.thegraph.com/api/${STACKLY_SUBGRAPH_API_KEY}/subgraphs/id/FNmemHB6tUh7eHmJnBFKYFf27U5GUAzXnatry4ZbrF7f`, }; /** diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index 9db51ac8..7100d167 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -11,9 +11,9 @@ "prepare:gnosis": "ts-node bin/build-subgraph.ts gnosis && bun run codegen", "prepare:mainnet": "ts-node bin/build-subgraph.ts mainnet && bun run codegen", "prepare:arbitrum-one": "ts-node bin/build-subgraph.ts arbitrum-one && bun run codegen", - "deploy:mainnet": "graph deploy --studio stackly-ethereum", - "deploy:gnosis": "graph deploy --studio stackly", - "deploy:arbitrum-one": "graph deploy --studio stackly-arbitrum-one", + "deploy:mainnet": "bun run prepare:mainnet && graph deploy --studio stackly-mainnet", + "deploy:gnosis": "bun run prepare:gnosis && graph deploy --studio stackly-gnosis", + "deploy:arbitrum-one": "bun run prepare:arbitrum-one && graph deploy --studio stackly-arbitrum-one", "create-local": "graph create --node http://localhost:8020/ swaprhq/stackly", "remove-local": "graph remove --node http://localhost:8020/ swaprhq/stackly", "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 swaprhq/stackly", @@ -23,8 +23,8 @@ "@stackly/sdk": "workspace:^" }, "devDependencies": { - "@graphprotocol/graph-cli": "0.51.2", - "@graphprotocol/graph-ts": "0.31.0", + "@graphprotocol/graph-cli": "0.78.0", + "@graphprotocol/graph-ts": "0.35.1", "@types/node": "^18.11.18", "ts-node": "^10.8.2", "yaml": "^2.2.1" diff --git a/packages/subgraph/src/mappings/order.ts b/packages/subgraph/src/mappings/order.ts index fdae66fb..227e5457 100644 --- a/packages/subgraph/src/mappings/order.ts +++ b/packages/subgraph/src/mappings/order.ts @@ -11,47 +11,75 @@ import { OrderFactory } from "../../generated/OrderFactory/OrderFactory"; const HUNDRED_PERCENT = BigInt.fromI32(10000); export function createOrReturnTokenEntity(contractAddress: Address): Token { - // Persist token data if it doesn't already exist - let token = Token.load(contractAddress.toHex()); + let tokenId = contractAddress.toHex(); + let token = Token.load(tokenId); + + // Return existing token if it's already in the store if (token !== null) { return token; } + + // Create a new token entity + token = new Token(tokenId); let tokenContract = ERC20Contract.bind(contractAddress); - token = new Token(contractAddress.toHex()); + + // Set the address field token.address = contractAddress; - token.name = ""; + // Name let tryName = tokenContract.try_name(); - if (!tryName.reverted) { - token.name = tryName.value; - } + token.name = tryName.reverted ? "Unknown Token" : tryName.value; - token.symbol = ""; + // Symbol let trySymbol = tokenContract.try_symbol(); - if (!trySymbol.reverted) { - token.symbol = trySymbol.value; - } + token.symbol = trySymbol.reverted ? "UNKNOWN" : trySymbol.value; + + // Decimals + let tryDecimals = tokenContract.try_decimals(); + token.decimals = tryDecimals.reverted ? 18 : tryDecimals.value; - token.decimals = tokenContract.decimals(); token.save(); return token; } export function handleDCAOrderInitialized(event: Initialized): void { - const orderContract = DCAOrderContract.bind(event.params.order); - const order = new DCAOrder(event.params.order.toHex()); + const orderAddress = event.params.order; + const orderContract = DCAOrderContract.bind(orderAddress); + const order = new DCAOrder(orderAddress.toHex()); + order.createdAt = event.block.timestamp; - order.owner = orderContract.owner(); - order.sellToken = createOrReturnTokenEntity(orderContract.sellToken()).id; - order.buyToken = createOrReturnTokenEntity(orderContract.buyToken()).id; - order.receiver = orderContract.receiver(); + let tryOwner = orderContract.try_owner(); + order.owner = tryOwner.reverted + ? Address.fromString("0x0000000000000000000000000000000000000000") + : tryOwner.value; + + let trySellToken = orderContract.try_sellToken(); + order.sellToken = trySellToken.reverted + ? "" + : createOrReturnTokenEntity(trySellToken.value).id; + + let tryBuyToken = orderContract.try_buyToken(); + order.buyToken = tryBuyToken.reverted + ? "" + : createOrReturnTokenEntity(tryBuyToken.value).id; + + let tryReceiver = orderContract.try_receiver(); + order.receiver = tryReceiver.reverted + ? Address.fromString("0x0000000000000000000000000000000000000000") + : tryReceiver.value; + + let orderSlots: Array = [BigInt.fromI32(0)]; let protocolFee: BigInt = BigInt.fromI32(0); - let orderFactoryAddress: Address = ( - event.transaction.to !== null - ? event.transaction.to - : Address.fromString("0x0") - )!; + + let orderFactoryAddress: Address; + if (event.transaction.to !== null) { + orderFactoryAddress = event.transaction.to as Address; + } else { + orderFactoryAddress = Address.fromString( + "0x0000000000000000000000000000000000000000" + ); + } const factory = OrderFactory.bind(orderFactoryAddress); let tryProtocolFee = factory.try_protocolFee(); @@ -59,15 +87,30 @@ export function handleDCAOrderInitialized(event: Initialized): void { protocolFee = BigInt.fromI32(tryProtocolFee.value); } - order.amount = orderContract.amount(); + let tryOrderSlots = orderContract.try_orderSlots(); + if (!tryOrderSlots.reverted) { + orderSlots = tryOrderSlots.value; + } + + let tryAmount = orderContract.try_amount(); + order.amount = tryAmount.reverted ? BigInt.fromI32(0) : tryAmount.value; + order.fee = protocolFee; order.feeAmount = order.amount .times(protocolFee) .div(HUNDRED_PERCENT.minus(protocolFee)); - order.endTime = orderContract.endTime().toI32(); - order.startTime = orderContract.startTime().toI32(); - order.orderSlots = orderContract.orderSlots(); - order.interval = orderContract.interval(); + + let tryEndTime = orderContract.try_endTime(); + order.endTime = tryEndTime.reverted ? 0 : tryEndTime.value.toI32(); + + let tryStartTime = orderContract.try_startTime(); + order.startTime = tryStartTime.reverted ? 0 : tryStartTime.value.toI32(); + + order.orderSlots = orderSlots; + + let tryInterval = orderContract.try_interval(); + order.interval = tryInterval.reverted ? BigInt.fromI32(0) : tryInterval.value; + order.save(); }