Skip to content

Commit

Permalink
chore: docs and other improvements (#14)
Browse files Browse the repository at this point in the history
* chore: docs and other improvements

* fix: format
  • Loading branch information
R0bi7 authored Nov 14, 2024
1 parent a68749a commit 80fc8e3
Show file tree
Hide file tree
Showing 4 changed files with 324 additions and 23 deletions.
236 changes: 235 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,235 @@
# balanced-solver-sdk
# Balanced Intent Solver SDK (Alpha)

Balanced Intent Solver SDK provides abstractions to assist you with interacting with the cross-chain Intent Smart Contracts and Solver.

**Note** SDK is currently in alpha testing stage and is subject to change!

## Installation

### NPM

Installing through npm:

`npm i --save @balanced/solver-sdk`

**NOTE** Package is not yet published to the npm registry!!!

### Local

Package can be locally installed by following this steps:

1. Clone this repository to your local machine.
2. `cd` into repository folder location.
3. Execute `npm install` command in your CLI to install dependencies.
4. Execute `npm run build` to build the package.
5. In your app repository `package.json` file, define dependency named `"@balanced/solver-sdk"` under `"dependencies"`.
Instead of version define absolute path to your SDK repository `"file:<sdk-repository-path>"` (e.g. `"file:/Users/dev/balanced-solver-sdk"`).
Full example: `"@balanced/solver-sdk": "file:/Users/dev/balanced-solver-sdk"`.

## Local Development

How to setup local development

1. Clone repository.
2. Make sure you have [Node.js](https://nodejs.org/en/download/package-manager) v18+ and corresponding npm installed on your system.
3. Execute `npm install` command in your CLI to install dependencies.
4. Make code changes.
1. Do not forget to export TS files in same folder `index.ts`.
2. Always import files using `.js` postfix.
5. Before commiting execute `npm run prepublishOnly` in order to verify build, format and exports.

## Load SDK Config

SDK includes predefined configurations of supported chains, tokens and other relevant information for the client to consume.

```typescript
import { ChainName, ChainConfig, chainConfig, Token } from "@balanced/solver-sdk"

// all supported Intent chains
const supportedChains: ChainName[] = IntentService.getSupportedChains()

// retrieve arbitrum chain config
const arbChainConfig: EvmChainConfig = IntentService.getChainConfig("arb")

// example of how to construct token per chain map using supported chains array
const supportedTokensPerChain: Map<ChainName, Token[]> = new Map(
supportedChains.map((chain) => {
return [chain, IntentService.getChainConfig(chain).supportedTokens]
}),
)

// example of how to construct chain name to chain config map
const chainConfigs: Map<ChainName, ChainConfig> = new Map(
supportedChains.map((chain) => {
return [chain, IntentService.getChainConfig(chain)]
}),
)
```

## Request a Quote

Requesting a quote should require you to just consume user input amount and converting it to the appropriate token amount (scaled by token decimals).
All the required configurations (chain id [nid], token decimals and address) should be loaded as described in [Load SDK Config](#load-sdk-config).

```typescript
import { IntentService } from "@balanced/solver-sdk"

const quoteResult = await IntentService.getQuote({
token_src: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
token_src_blockchain_id: "0xa4b1.arbitrum",
token_dst: "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI",
token_dst_blockchain_id: "sui",
src_amount: "100000000000000000",
})

/**
* Example response (quoteResult)
* {
* "ok": true,
* "value": {
* "output": {
* "expected_output":"1000000000000", // to be used in create intent order as toAmount
* "uuid":"e2795d2c-14a5-4d18-9be6-a257d7c9d274" // to be used in create intent order as quote_uuid
* }
* }
* }
*/
```

## Initialising Providers

SDK abstracts away the wallet and public RPC clients using `ChainProviderType` TS type which can be one of the following:

- `EvmProvider`: Provider used for EVM type chains (ETH, BSC, etc..). Implemented using [viem](https://viem.sh/docs/clients/wallet#json-rpc-accounts).
- `SuiProvider`: Provider used for SUI type chains (SUI). Implemented using [@mysten/sui](https://github.com/MystenLabs/sui) and [@mysten/wallet-standard](https://docs.sui.io/standards/wallet-standard).

Providers are used to request wallet actions (prompts wallet extension) and make RPC calls to the RPC nodes.

EVM Provider example:

```typescript
import { EvmProvider } from "@balanced/solver-sdk"

// NOTE: user address should be provided by application when user connects wallet
const evmProvider = new EvmProvider("0x3FF796F1968C515f6AC2833545B5Dd2cE765A1a1", (window as any).ethereum)
```

SUI Provider example (uses [SUI dApp Kit](https://sdk.mystenlabs.com/dapp-kit/):

```typescript
import { SuiProvider } from "@balanced/solver-sdk"
import { useCurrentWallet, useCurrentAccount } from "@mysten/dapp-kit"

const account = useCurrentAccount()
const { currentWallet, connectionStatus } = useCurrentWallet()

// check that wallet is connected and account is defined
if (connectionStatus === "connected" && account) {
const suiProvider = new SuiProvider(currentWallet, account, "mainnet")
} else {
throw new Error("Wallet or Account undefined. Please connect wallet and select account.")
}
```

## Create Intent Order

Creating Intent Order requires creating provider for the chain that intent is going to be created on (`fromChain`).

Example for ARB -> SUI Intent Order:

```typescript
import { IntentService, EvmProvider, CreateIntentOrderPayload, IntentStatusCode } from "@balanced/solver-sdk"

// create EVM provider because "arb" is of ChainType "evm" (defined in ChainConfig type - see section Load SDK Config)
// NOTE: window can only be accessed client side (browser)
const evmProvider = new EvmProvider("0x601020c5797Cdd34f64476b9bf887a353150Cb9a", (window as any).ethereum)

const intentOrderPayload: CreateIntentOrderPayload = {
quote_uuid: "a0dd7652-b360-4123-ab2d-78cfbcd20c6b",
fromAddress: "0x601020c5797Cdd34f64476b9bf887a353150Cb9a", // address we are sending funds from (fromChain)
toAddress: "0x81600ec58a2efd97f41380370cddf25b7a416d03ee081552becfa9710ea30878", // destination address where funds are transfered to (toChain)
fromChain: "arb", // ChainName
toChain: "sui", // ChainName
token: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
amount: BigInt("100000000000000000"),
toToken: "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI",
toAmount: BigInt("1000000000000"),
} as const

// checks if token transfer amount is approved (required for EVM, can be skipped for SUI as it defaults to true)
const isAllowanceValid = await IntentService.isAllowanceValid(intentOrderPayload, evmProvider)

if (isAllowanceValid.ok) {
if (!isAllowanceValid.value) {
// allowance invalid, prompt approval
const approvalResult = await IntentService.approve(
"0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
BigInt("100000000000000000"),
IntentService.getChainConfig("arb").intentContract,
evmProvider,
)

if (approvalResult.ok) {
const executionResult = await IntentService.executeIntentOrder(intentOrderPayload, evmProvider)

if (executionResult.ok) {
const intentStatus = await IntentService.getStatus({
task_id: executionResult.value.task_id,
})

if (intentStatus.ok) {
console.log(intentStatus)

/**
* Example status
* {
* "ok": true,
* "value": {
* "output": {
* "status":3, // use IntentStatusCode to map status code
* "tx_hash":"0xabcdefasdasdsafssadasdsadsadasdsadasdsadsa"
* }
* }
* }
*/
} else {
// handle error
}
} else {
// handle error
}
} else {
// handle error
}
}
} else {
// handle error
}
```

## Get Intent Status

After Intent Order is created, the resulting `task_id` can be used to query the status of the task.

Example status check:

```typescript
import { IntentService } from "@balanced/solver-sdk"

const intentStatus = await IntentService.getStatus({
task_id: "a0dd7652-b360-4123-ab2d-78cfbcd20c6b",
})

/**
* Example intentStatus response
* {
* "ok": true,
* "value": {
* "output": {
* "status":3, // use IntentStatusCode to map status code
* "tx_hash":"0xabcdefasdasdsafssadasdsadsadasdsadasdsadsa"
* }
* }
* }
*/
```
42 changes: 32 additions & 10 deletions src/services/IntentService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { type Address, type Hash } from "viem"
import { type Address, type Hash, type TransactionReceipt } from "viem"
import {
type ChainName,
type ChainConfig,
type Result,
type IntentQuoteRequest,
type IntentQuoteResponse,
Expand All @@ -10,6 +9,7 @@ import {
type IntentExecutionResponse,
type IntentStatusRequest,
type IntentStatusResponse,
type GetChainConfigType,
} from "../types.js"
import { chainConfig, supportedChains } from "../constants.js"
import { isEvmChainConfig, isSuiChainConfig } from "../guards.js"
Expand All @@ -31,15 +31,15 @@ export class IntentService {
* "token_src_blockchain_id": "42161",
* "token_dst": "0x2::sui::SUI",
* "token_dst_blockchain_id": "101",
* "src_amount": "0.00001"
* "src_amount": "10000"
* }
*
* // response
* {
* "ok": true,
* "value": {
* "output": {
* "expected_output":"0.009813013",
* "expected_output":"981301300",
* "uuid":"e2795d2c-14a5-4d18-9be6-a257d7c9d274"
* }
* }
Expand All @@ -60,7 +60,6 @@ export class IntentService {
provider: GetChainProviderType<T["fromChain"]>,
): Promise<Result<boolean>> {
invariant(payload.amount > 0n, "Invalid amount")
invariant(payload.amount > 0n, "Invalid amount")

try {
const fromChainConfig = chainConfig[payload.fromChain]
Expand Down Expand Up @@ -107,13 +106,36 @@ export class IntentService {
}
}

/**
* Approve ERC20 amount spending
* @param token - ERC20 token address
* @param amount - Amount to approve
* @param address - Address to approve spending for
* @param provider - EVM Provider
*/
static async approve(
token: Address,
amount: bigint,
address: Address,
provider: EvmProvider,
): Promise<Result<TransactionReceipt>> {
return EvmIntentService.approve(token, amount, address, provider)
}

/**
* Execute intent order
* @example
* // request
* {
* "intent_tx_hash": "0xba3dce19347264db32ced212ff1a2036f20d9d2c7493d06af15027970be061af",
* "quote_uuid": "a0dd7652-b360-4123-ab2d-78cfbcd20c6b"
* "quote_uuid": "a0dd7652-b360-4123-ab2d-78cfbcd20c6b",
* "fromAddress": "0x601020c5797Cdd34f64476b9bf887a353150Cb9a",
* "toAddress": "0x81600ec58a2efd97f41380370cddf25b7a416d03ee081552becfa9710ea30878",
* "fromChain": "0xa4b1.arbitrum",
* "toChain": "sui",
* "token": "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
* "amount": "10000",
* "toToken": "0x2::sui::SUI",
* "toAmount": "9813013000",
* }
*
* // response
Expand Down Expand Up @@ -219,7 +241,7 @@ export class IntentService {
* "value": {
* "output": {
* "status":3,
* "tx_hash":"0xabcdef"
* "tx_hash":"0xabcdefasdasdsafssadasdsadsadasdsadasdsadsa"
* }
* }
* }
Expand All @@ -240,13 +262,13 @@ export class IntentService {
/**
* Get config of a specific chain
*/
public static getChainConfig(chain: ChainName): ChainConfig {
public static getChainConfig<T extends ChainName>(chain: T): GetChainConfigType<T> {
const data = chainConfig[chain]

if (!chainConfig) {
throw new Error(`Unsupported chain: ${chain}`)
}

return data
return data as GetChainConfigType<T>
}
}
Loading

0 comments on commit 80fc8e3

Please sign in to comment.