From 2090a7691c2a958bece33f147513896d9b1945de Mon Sep 17 00:00:00 2001 From: Gregory Hill Date: Fri, 16 Aug 2024 13:55:09 +0100 Subject: [PATCH] feat: update gateway for v3 Signed-off-by: Gregory Hill --- sdk/package.json | 5 ++- sdk/src/gateway.ts | 105 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/sdk/package.json b/sdk/package.json index b0d7270c..66eee133 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -32,6 +32,7 @@ "@scure/base": "^1.1.7", "@scure/btc-signer": "^1.3.2", "bitcoin-address-validation": "^2.2.3", - "bitcoinjs-lib": "^6.1.6" + "bitcoinjs-lib": "^6.1.6", + "ethers": "^6.13.2" } -} \ No newline at end of file +} diff --git a/sdk/src/gateway.ts b/sdk/src/gateway.ts index d07fdcd6..19027e0b 100644 --- a/sdk/src/gateway.ts +++ b/sdk/src/gateway.ts @@ -1,27 +1,81 @@ +import { ethers, AbiCoder } from "ethers"; + export type EvmAddress = string; type GatewayQuote = { - onramp_address: EvmAddress; - dust_threshold: number; + gatewayAddress: EvmAddress; + dustThreshold: number; satoshis: number; fee: number; gratuity: string; - bitcoin_address: string; - tx_proof_difficulty_factor: number; + bitcoinAddress: string; + txProofDifficultyFactor: number; + + // TODO: add this to API or add here + strategyAddress: EvmAddress | null, +}; + +type GatewayCreateOrderRequest = { + gatewayAddress: EvmAddress, + strategyAddress: EvmAddress | null, + satsToConvertToEth: number, + userAddress: EvmAddress, + gatewayExtraData: string | null, + strategyExtraData: string | null, + satoshis: number, }; type GatewayOrderResponse = { - onramp_address: EvmAddress; - token_address: EvmAddress; + gatewayAddress: EvmAddress; + tokenAddress: EvmAddress; txid: string; status: boolean; timestamp: number; tokens: string; satoshis: number; fee: number; - tx_proof_difficulty_factor: number; + txProofDifficultyFactor: number; +}; + +type GatewayStartOrderResult = { + orderUuid: string, + opReturnHash: string, }; +export type GatewayParams = { + fromChain: 'Bitcoin', // Source chain + fromToken: 'BTC', // Source token + fromUserAddress: EvmAddress, // Source chain wallet address + + amount: number | string, // Amount to transfer in satoshis + + toChain: 'BOB', // Destination chain + toToken: 'wBTC', // Destination token + toUserAddress: EvmAddress, // Ending chain wallet address + + // TODO: remove, can be added later + maxSlippage: 0.01, //An optional percentage value passed as a decimal between 0 and 1. (i.e 0.02 = 2%). Otherwise, slippage defaults to 3%. + + // below is different from SwingSDK + + gasRefill: number, // Amount of satoshis to swap for ETH +} + +function calculateOpReturnHash(req: GatewayCreateOrderRequest) { + const abiCoder = new AbiCoder(); + return ethers.keccak256(abiCoder.encode( + ["address", "address", "uint256", "address", "bytes", "bytes"], + [ + req.gatewayAddress, + req.strategyAddress || ethers.ZeroAddress, + req.satsToConvertToEth, + req.userAddress, + req.gatewayExtraData, + req.strategyExtraData + ] + )) +} + export class GatewayApiClient { private baseUrl: string; @@ -29,8 +83,14 @@ export class GatewayApiClient { this.baseUrl = baseUrl; } - async getQuote(address: string, atomicAmount?: number | string): Promise { - const response = await fetch(`${this.baseUrl}/quote/${address}/${atomicAmount || ''}`, { + async getQuote(gatewayParams: GatewayParams): Promise { + + // TODO: convert toToken to address + // TODO: API or SDK should handle when token is from strategy + const outputToken = gatewayParams.toToken; + const atomicAmount = gatewayParams.amount; + + const response = await fetch(`${this.baseUrl}/quote/${outputToken}/${atomicAmount || ''}`, { headers: { 'Content-Type': 'application/json', Accept: 'application/json' @@ -41,31 +101,46 @@ export class GatewayApiClient { } // TODO: add error handling - async createOrder(contractAddress: string, userAddress: EvmAddress, atomicAmount: number | string): Promise { + async startOrder(gatewayQuote: GatewayQuote, gatewayParams: GatewayParams): Promise { + const request: GatewayCreateOrderRequest = { + gatewayAddress: gatewayQuote.gatewayAddress, + strategyAddress: gatewayQuote.strategyAddress, + satsToConvertToEth: gatewayParams.gasRefill, + userAddress: gatewayParams.toUserAddress, + // TODO: figure out how to get extra data + gatewayExtraData: null, + strategyExtraData: null, + satoshis: gatewayQuote.satoshis, + }; + const response = await fetch(`${this.baseUrl}/order`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, - body: JSON.stringify({ onramp_address: contractAddress, user_address: userAddress, satoshis: atomicAmount }) + body: JSON.stringify(request) }); if (!response.ok) { throw new Error('Failed to create order'); } - return await response.json(); + const uuid = await response.json(); + return { + orderUuid: uuid, + opReturnHash: calculateOpReturnHash(request), + } } - async updateOrder(id: string, tx: string) { - const response = await fetch(`${this.baseUrl}/order/${id}`, { + async finalizeOrder(orderUuid: string, bitcoinTx: string) { + const response = await fetch(`${this.baseUrl}/order/${orderUuid}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, - body: JSON.stringify({ bitcoin_tx: tx }) + body: JSON.stringify({ bitcoin_tx: bitcoinTx }) }); if (!response.ok) {