diff --git a/packages/filler-v2/src/tests/filler.test.ts b/packages/filler-v2/src/tests/filler.test.ts index 54f30468..26f3e3a5 100644 --- a/packages/filler-v2/src/tests/filler.test.ts +++ b/packages/filler-v2/src/tests/filler.test.ts @@ -316,7 +316,7 @@ describe.skip("Filler V2 - Tron Source Chain", () => { destination: toHex(polygonAmoyId), deadline: 12545151568145n, nonce: 0n, - fees: parseUnits("0.005", sourceUsdtDecimals), + fees: 0n, session: "0x0000000000000000000000000000000000000000" as HexString, predispatch: { assets: [], call: "0x" as HexString }, inputs, diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index 6644a064..55342922 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -1,5 +1,11 @@ # @hyperbridge/sdk +## 1.6.3 + +### Patch Changes + +- Further improvements to intents v2 + ## 1.6.2 ### Patch Changes diff --git a/packages/sdk/package.json b/packages/sdk/package.json index bdb2bad0..0c7b129d 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@hyperbridge/sdk", - "version": "1.6.2", + "version": "1.6.3", "description": "The hyperclient SDK provides utilities for querying proofs and statuses for cross-chain requests from HyperBridge.", "type": "module", "types": "./dist/node/index.d.ts", diff --git a/packages/sdk/src/configs/chain.ts b/packages/sdk/src/configs/chain.ts index 3186c831..2474da6e 100644 --- a/packages/sdk/src/configs/chain.ts +++ b/packages/sdk/src/configs/chain.ts @@ -231,10 +231,12 @@ export const chainConfigs: Record = { DAI: "0x6b175474e89094c44da98b954eedeac495271d0f", USDC: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", USDT: "0xdac17f958d2ee523a2206206994597c13d831ec7", + cNGN: "0x17CDB2a01e7a34CbB3DD4b83260B05d0274C8dab", }, tokenDecimals: { USDC: 6, USDT: 6, + cNGN: 6, }, tokenStorageSlots: { USDT: { balanceSlot: 2, allowanceSlot: 5 }, @@ -244,6 +246,8 @@ export const chainConfigs: Record = { }, addresses: { IntentGateway: "0x1a4ee689a004b10210a1df9f24a387ea13359acf", + IntentGatewayV2: "0x2d61624A17f361020679FaA16fbB566C344AaF4B", + SolverAccount: "0xd4d594C99f23b1Fb9d65fdd9062854B1A1C5780b", TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE", Host: "0x792A6236AF69787C40cF76b69B4c8c7B28c4cA20", UniswapRouter02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", @@ -292,6 +296,8 @@ export const chainConfigs: Record = { }, addresses: { IntentGateway: "0x1a4ee689a004b10210a1df9f24a387ea13359acf", + IntentGatewayV2: "0x2d61624A17f361020679FaA16fbB566C344AaF4B", + SolverAccount: "0xd4d594C99f23b1Fb9d65fdd9062854B1A1C5780b", TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE", Host: "0x24B5d421Ec373FcA57325dd2F0C074009Af021F7", UniswapRouter02: "0x10ED43C718714eb63d5aA57B78B54704E256024E", @@ -342,6 +348,8 @@ export const chainConfigs: Record = { }, addresses: { IntentGateway: "0x1a4ee689a004b10210a1df9f24a387ea13359acf", + IntentGatewayV2: "0x2d61624A17f361020679FaA16fbB566C344AaF4B", + SolverAccount: "0xd4d594C99f23b1Fb9d65fdd9062854B1A1C5780b", TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE", Host: "0xE05AFD4Eb2ce6d65c40e1048381BD0Ef8b4B299e", UniswapRouter02: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24", @@ -377,10 +385,12 @@ export const chainConfigs: Record = { DAI: "0x50c5725949a6f0c72e6c4a641f24049a917db0cb", USDC: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", USDT: "0xfde4c96c8593536e31f229ea8f37b2ada2699bb2", + cNGN: "0x46C85152bFe9f96829aA94755D9f915F9B10EF5F", }, tokenDecimals: { USDC: 6, USDT: 6, + cNGN: 6, }, tokenStorageSlots: { USDT: { balanceSlot: 0, allowanceSlot: 1 }, @@ -390,6 +400,8 @@ export const chainConfigs: Record = { }, addresses: { IntentGateway: "0x1a4ee689a004b10210a1df9f24a387ea13359acf", + IntentGatewayV2: "0x2d61624A17f361020679FaA16fbB566C344AaF4B", + SolverAccount: "0xd4d594C99f23b1Fb9d65fdd9062854B1A1C5780b", TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE", Host: "0x6FFe92e4d7a9D589549644544780e6725E84b248", UniswapRouter02: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24", @@ -425,10 +437,12 @@ export const chainConfigs: Record = { DAI: "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", USDC: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", USDT: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", + cNGN: "0x52828daa48C1a9A06F37500882b42daf0bE04C3B", }, tokenDecimals: { USDC: 6, USDT: 6, + cNGN: 6, }, tokenStorageSlots: { USDT: { balanceSlot: 0, allowanceSlot: 1 }, @@ -438,6 +452,8 @@ export const chainConfigs: Record = { }, addresses: { IntentGateway: "0x1a4ee689a004b10210a1df9f24a387ea13359acf", + IntentGatewayV2: "0x2d61624A17f361020679FaA16fbB566C344AaF4B", + SolverAccount: "0xd4d594C99f23b1Fb9d65fdd9062854B1A1C5780b", TokenGateway: "0x8b536105b6Fae2aE9199f5146D3C57Dfe53b614E", Host: "0xD8d3db17C1dF65b301D45C84405CcAC1395C559a", UniswapRouter02: "0xd2f9496824951D5237cC71245D659E48d0d5f9E8", @@ -480,6 +496,8 @@ export const chainConfigs: Record = { }, addresses: { IntentGateway: "0x1a4ee689a004b10210a1df9f24a387ea13359acf", + IntentGatewayV2: "0x2d61624A17f361020679FaA16fbB566C344AaF4B", + SolverAccount: "0xd4d594C99f23b1Fb9d65fdd9062854B1A1C5780b", TokenGateway: "0x8b536105b6Fae2aE9199f5146D3C57Dfe53b614E", Host: "0x2A17C1c3616Bbc33FCe5aF5B965F166ba76cEDAf", UniswapRouter02: "0x284f11109359a7e1306c3e447ef14d38400063ff", diff --git a/packages/sdk/src/protocols/intentsV2/GasEstimator.ts b/packages/sdk/src/protocols/intentsV2/GasEstimator.ts index 2469674e..bc1e617e 100644 --- a/packages/sdk/src/protocols/intentsV2/GasEstimator.ts +++ b/packages/sdk/src/protocols/intentsV2/GasEstimator.ts @@ -1,4 +1,4 @@ -import { encodeFunctionData, toHex, pad, maxUint256, concat, keccak256 } from "viem" +import { encodeFunctionData, toHex, pad, maxUint256, concat, keccak256, isHex, hexToString } from "viem" import { generatePrivateKey, privateKeyToAccount, privateKeyToAddress } from "viem/accounts" import { ABI as IntentGatewayV2ABI } from "@/abis/IntentGatewayV2" import IntentGateway from "@/abis/IntentGateway" @@ -41,11 +41,11 @@ export class GasEstimator { const { order } = params const solverPrivateKey = generatePrivateKey() const solverAccountAddress = privateKeyToAddress(solverPrivateKey) - const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayV2Address(order.destination) - const entryPointAddress = this.ctx.dest.configService.getEntryPointV08Address(order.destination) - const chainId = BigInt( - this.ctx.dest.client.chain?.id ?? Number.parseInt(this.ctx.dest.config.stateMachineId.split("-")[1]), - ) + const souceStateMachineId = isHex(order.source) ? hexToString(order.source) : order.source + const destStateMachineId = isHex(order.destination) ? hexToString(order.destination) : order.destination + const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayV2Address(destStateMachineId) + const entryPointAddress = this.ctx.dest.configService.getEntryPointV08Address(destStateMachineId) + const chainId = BigInt(Number.parseInt(destStateMachineId.split("-")[1])) const totalEthValue = order.output.assets .filter((output) => bytes32ToBytes20(output.token) === ADDRESS_ZERO) @@ -61,14 +61,14 @@ export class GasEstimator { const { viem: stateOverrides, bundler: bundlerStateOverrides } = await this.buildStateOverride({ accountAddress: solverAccountAddress, - chain: order.destination, + chain: destStateMachineId, outputAssets: assetsForOverrides, spenderAddress: intentGatewayV2Address, intentGatewayV2Address, entryPointAddress, }) - const isSameChain = order.source === order.destination + const isSameChain = souceStateMachineId === destStateMachineId let postRequestFeeInDestFeeToken = 0n let protocolFeeInNativeToken = 0n @@ -78,7 +78,7 @@ export class GasEstimator { this.ctx, postRequestGas, "source", - order.source, + souceStateMachineId, ) postRequestFeeInDestFeeToken = adjustDecimals( postRequestFeeInSourceFeeToken, @@ -87,13 +87,13 @@ export class GasEstimator { ) const postRequest: IPostRequest = { - source: order.destination, - dest: order.source, + source: destStateMachineId, + dest: souceStateMachineId, body: constructRedeemEscrowRequestBody({ ...order, id: orderV2Commitment(order) }, MOCK_ADDRESS), timeoutTimestamp: 0n, nonce: await this.ctx.source.getHostNonce(), - from: this.ctx.source.configService.getIntentGatewayV2Address(order.destination), - to: this.ctx.source.configService.getIntentGatewayV2Address(order.source), + from: this.ctx.source.configService.getIntentGatewayV2Address(destStateMachineId), + to: this.ctx.source.configService.getIntentGatewayV2Address(souceStateMachineId), } protocolFeeInNativeToken = await this.quoteNative(postRequest, postRequestFeeInDestFeeToken).catch(() => @@ -216,7 +216,7 @@ export class GasEstimator { this.ctx, totalGas, "dest", - order.destination, + destStateMachineId, gasPrice, ) const totalGasInSourceFeeToken = adjustDecimals( diff --git a/packages/sdk/src/protocols/intentsV2/IntentsV2.ts b/packages/sdk/src/protocols/intentsV2/IntentsV2.ts index 8ced3aca..e795dec8 100644 --- a/packages/sdk/src/protocols/intentsV2/IntentsV2.ts +++ b/packages/sdk/src/protocols/intentsV2/IntentsV2.ts @@ -22,6 +22,7 @@ import { OrderExecutor } from "./OrderExecutor" import { OrderCanceller } from "./OrderCanceller" import { BidManager } from "./BidManager" import { GasEstimator } from "./GasEstimator" +import { OrderStatusChecker } from "./OrderStatusChecker" import type { ERC7821Call } from "@/types" import { DEFAULT_GRAFFITI } from "@/utils" @@ -41,6 +42,7 @@ export class IntentsV2 { private readonly orderPlacer: OrderPlacer private readonly orderExecutor: OrderExecutor private readonly orderCanceller: OrderCanceller + private readonly orderStatusChecker: OrderStatusChecker private readonly bidManager: BidManager private readonly gasEstimator: GasEstimator @@ -80,6 +82,7 @@ export class IntentsV2 { this.orderPlacer = new OrderPlacer(this.ctx) this.orderExecutor = new OrderExecutor(this.ctx, bidManager) this.orderCanceller = new OrderCanceller(this.ctx) + this.orderStatusChecker = new OrderStatusChecker(this.ctx) this.bidManager = bidManager this.gasEstimator = gasEstimator this._crypto = crypto @@ -217,4 +220,12 @@ export class IntentsV2 { decodeERC7821Execute(callData: HexString): ERC7821Call[] | null { return this._crypto.decodeERC7821Execute(callData) } + + async isOrderFilled(order: OrderV2): Promise { + return this.orderStatusChecker.isOrderFilled(order) + } + + async isOrderRefunded(order: OrderV2): Promise { + return this.orderStatusChecker.isOrderRefunded(order) + } } diff --git a/packages/sdk/src/protocols/intentsV2/OrderStatusChecker.ts b/packages/sdk/src/protocols/intentsV2/OrderStatusChecker.ts new file mode 100644 index 00000000..68d060aa --- /dev/null +++ b/packages/sdk/src/protocols/intentsV2/OrderStatusChecker.ts @@ -0,0 +1,79 @@ +import { isHex, hexToString } from "viem" +import { ABI as IntentGatewayV2ABI } from "@/abis/IntentGatewayV2" +import { orderV2Commitment, bytes32ToBytes20 } from "@/utils" +import type { OrderV2, HexString } from "@/types" +import type { IntentsV2Context } from "./types" + +export class OrderStatusChecker { + constructor(private readonly ctx: IntentsV2Context) {} + + /** + * Checks if a V2 order has been filled by reading the commitment storage slot on the destination chain. + * + * Reads the storage slot returned by `calculateCommitmentSlotHash` on the IntentGatewayV2 contract. + * A non-zero value at that slot means the solver has called `fillOrder` and the order is complete + * from the user's perspective (the beneficiary has received their tokens). + * + * @param order - The V2 order to check. `order.id` is used as the commitment; if not set it is computed. + * @returns True if the order has been filled on the destination chain, false otherwise. + */ + async isOrderFilled(order: OrderV2): Promise { + const commitment = (order.id ?? orderV2Commitment(order)) as HexString + const destStateMachineId = isHex(order.destination) + ? hexToString(order.destination as HexString) + : order.destination + + const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayV2Address(destStateMachineId) + + const filledSlot = await this.ctx.dest.client.readContract({ + abi: IntentGatewayV2ABI, + address: intentGatewayV2Address, + functionName: "calculateCommitmentSlotHash", + args: [commitment], + }) + + const filledStatus = await this.ctx.dest.client.getStorageAt({ + address: intentGatewayV2Address, + slot: filledSlot as HexString, + }) + + return filledStatus !== "0x0000000000000000000000000000000000000000000000000000000000000000" + } + + /** + * Checks if a V2 order has been refunded by reading the `_orders` mapping on the source chain. + * + * Calls `_orders(commitment, tokenAddress)` for each input token. When the order is placed the + * escrowed amounts are stored there. After a successful refund the contract zeroes them out. + * An order is considered refunded when all escrowed input amounts have been returned (i.e. are 0). + * + * @param order - The V2 order to check. `order.id` is used as the commitment; if not set it is computed. + * @returns True if all escrowed inputs have been returned to the user on the source chain, false otherwise. + */ + async isOrderRefunded(order: OrderV2): Promise { + if (!order.inputs || order.inputs.length === 0) return false + + const commitment = (order.id ?? orderV2Commitment(order)) as HexString + const sourceStateMachineId = isHex(order.source) + ? hexToString(order.source as HexString) + : order.source + + const intentGatewayV2Address = this.ctx.source.configService.getIntentGatewayV2Address(sourceStateMachineId) + + for (const input of order.inputs) { + const tokenAddress = bytes32ToBytes20(input.token) + const escrowedAmount = await this.ctx.source.client.readContract({ + abi: IntentGatewayV2ABI, + address: intentGatewayV2Address, + functionName: "_orders", + args: [commitment, tokenAddress], + }) + + if (escrowedAmount !== 0n) { + return false + } + } + + return true + } +} diff --git a/packages/sdk/src/protocols/intentsV2/index.ts b/packages/sdk/src/protocols/intentsV2/index.ts index 44e0d3e7..978f89f4 100644 --- a/packages/sdk/src/protocols/intentsV2/index.ts +++ b/packages/sdk/src/protocols/intentsV2/index.ts @@ -1,4 +1,5 @@ export { IntentsV2 } from "./IntentsV2" +export { OrderStatusChecker } from "./OrderStatusChecker" export { encodeERC7821ExecuteBatch, transformOrderForContract, fetchSourceProof } from "./utils" export { SELECT_SOLVER_TYPEHASH, PACKED_USEROP_TYPEHASH, DOMAIN_TYPEHASH } from "./CryptoUtils" export {