@scallop-io/scallop-deepbook-kit 0.1.4 → 0.1.5-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _mysten_deepbook_v3 from '@mysten/deepbook-v3';
2
- import { MarginPoolContract, DeepBookConfig } from '@mysten/deepbook-v3';
2
+ import { DeepBookConfig, MarginPoolContract } from '@mysten/deepbook-v3';
3
3
  import { SuiClient } from '@mysten/sui/client';
4
4
  import { Transaction } from '@mysten/sui/transactions';
5
5
 
@@ -10,6 +10,8 @@ interface ToolkitConfig {
10
10
  privateKey: string;
11
11
  supplierCapId?: string;
12
12
  fullnodeUrl?: string;
13
+ supplierCapPackageId?: string;
14
+ dbConfig?: DeepBookConfig;
13
15
  }
14
16
  interface TransactionResult {
15
17
  digest: string;
@@ -33,7 +35,9 @@ declare class DeepBookMarginToolkit {
33
35
  private address;
34
36
  private marginPoolContract;
35
37
  private supplierCapId?;
36
- constructor(config: ToolkitConfig);
38
+ private dbConfig;
39
+ private supplierCapPackageId;
40
+ constructor({ network, fullnodeUrl, supplierCapId, privateKey, supplierCapPackageId, dbConfig, }: ToolkitConfig);
37
41
  initialize(): Promise<string>;
38
42
  createSupplierCap(): Promise<string | null>;
39
43
  createSupplyReferral(coin: MarginCoinType): Promise<string | null>;
@@ -47,9 +51,9 @@ declare class DeepBookMarginToolkit {
47
51
 
48
52
  declare const MARGIN_POOL_PARAM_KEYS: readonly ["supplyCap", "maxUtilizationRate", "protocolSpread", "minBorrow", "interestRate", "totalSupply", "supplyShares", "totalBorrow", "borrowShares", "lastUpdateTimestamp"];
49
53
  declare const MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS: readonly ["userSupplyShares", "userSupplyAmount"];
50
-
51
54
  type MarginPoolParamKey = (typeof MARGIN_POOL_PARAM_KEYS)[number];
52
55
  type MarginPoolWithSupplierCapParamKey = (typeof MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS)[number];
56
+
53
57
  type InterestConfig = {
54
58
  highKink: number;
55
59
  baseBorrowApr: number;
@@ -64,12 +68,18 @@ type MarginPoolParams = Record<MarginPoolParamKey | MarginPoolWithSupplierCapPar
64
68
  scalar: number;
65
69
  decimals: number;
66
70
  };
71
+ type DeepBookMarginPoolParams = {
72
+ address?: string;
73
+ suiClient?: SuiClient;
74
+ env?: NetworkType;
75
+ dbConfig?: DeepBookConfig;
76
+ };
67
77
  declare class DeepBookMarginPool {
68
78
  #private;
69
- readonly suiClient: SuiClient;
70
79
  marginPoolContract: MarginPoolContract;
71
80
  dbConfig: DeepBookConfig;
72
- constructor(env?: NetworkType, address?: string, suiClient?: SuiClient, dbConfig?: DeepBookConfig);
81
+ suiClient: SuiClient;
82
+ constructor({ env, address, suiClient, dbConfig, }?: DeepBookMarginPoolParams);
73
83
  get env(): _mysten_deepbook_v3.Environment;
74
84
  getPoolParameters(coinKey: string, supplierCapId?: string, tx?: Transaction): Promise<MarginPoolParams>;
75
85
  getPoolParameters(coinKey: string, supplierCapId: string | undefined, tx: Transaction, inspect: true): Promise<MarginPoolParams>;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _mysten_deepbook_v3 from '@mysten/deepbook-v3';
2
- import { MarginPoolContract, DeepBookConfig } from '@mysten/deepbook-v3';
2
+ import { DeepBookConfig, MarginPoolContract } from '@mysten/deepbook-v3';
3
3
  import { SuiClient } from '@mysten/sui/client';
4
4
  import { Transaction } from '@mysten/sui/transactions';
5
5
 
@@ -10,6 +10,8 @@ interface ToolkitConfig {
10
10
  privateKey: string;
11
11
  supplierCapId?: string;
12
12
  fullnodeUrl?: string;
13
+ supplierCapPackageId?: string;
14
+ dbConfig?: DeepBookConfig;
13
15
  }
14
16
  interface TransactionResult {
15
17
  digest: string;
@@ -33,7 +35,9 @@ declare class DeepBookMarginToolkit {
33
35
  private address;
34
36
  private marginPoolContract;
35
37
  private supplierCapId?;
36
- constructor(config: ToolkitConfig);
38
+ private dbConfig;
39
+ private supplierCapPackageId;
40
+ constructor({ network, fullnodeUrl, supplierCapId, privateKey, supplierCapPackageId, dbConfig, }: ToolkitConfig);
37
41
  initialize(): Promise<string>;
38
42
  createSupplierCap(): Promise<string | null>;
39
43
  createSupplyReferral(coin: MarginCoinType): Promise<string | null>;
@@ -47,9 +51,9 @@ declare class DeepBookMarginToolkit {
47
51
 
48
52
  declare const MARGIN_POOL_PARAM_KEYS: readonly ["supplyCap", "maxUtilizationRate", "protocolSpread", "minBorrow", "interestRate", "totalSupply", "supplyShares", "totalBorrow", "borrowShares", "lastUpdateTimestamp"];
49
53
  declare const MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS: readonly ["userSupplyShares", "userSupplyAmount"];
50
-
51
54
  type MarginPoolParamKey = (typeof MARGIN_POOL_PARAM_KEYS)[number];
52
55
  type MarginPoolWithSupplierCapParamKey = (typeof MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS)[number];
56
+
53
57
  type InterestConfig = {
54
58
  highKink: number;
55
59
  baseBorrowApr: number;
@@ -64,12 +68,18 @@ type MarginPoolParams = Record<MarginPoolParamKey | MarginPoolWithSupplierCapPar
64
68
  scalar: number;
65
69
  decimals: number;
66
70
  };
71
+ type DeepBookMarginPoolParams = {
72
+ address?: string;
73
+ suiClient?: SuiClient;
74
+ env?: NetworkType;
75
+ dbConfig?: DeepBookConfig;
76
+ };
67
77
  declare class DeepBookMarginPool {
68
78
  #private;
69
- readonly suiClient: SuiClient;
70
79
  marginPoolContract: MarginPoolContract;
71
80
  dbConfig: DeepBookConfig;
72
- constructor(env?: NetworkType, address?: string, suiClient?: SuiClient, dbConfig?: DeepBookConfig);
81
+ suiClient: SuiClient;
82
+ constructor({ env, address, suiClient, dbConfig, }?: DeepBookMarginPoolParams);
73
83
  get env(): _mysten_deepbook_v3.Environment;
74
84
  getPoolParameters(coinKey: string, supplierCapId?: string, tx?: Transaction): Promise<MarginPoolParams>;
75
85
  getPoolParameters(coinKey: string, supplierCapId: string | undefined, tx: Transaction, inspect: true): Promise<MarginPoolParams>;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var client=require('@mysten/sui/client'),ed25519=require('@mysten/sui/keypairs/ed25519'),transactions=require('@mysten/sui/transactions'),deepbookV3=require('@mysten/deepbook-v3'),cryptography=require('@mysten/sui/cryptography'),bcs$1=require('@mysten/bcs'),bcs=require('@mysten/sui/bcs'),bignumber_js=require('bignumber.js');var g={DEEPBOOK_PACKAGE_ID:"0xfb28c4cbc6865bd1c897d26aecbe1f8792d1509a20ffec692c800660cbec6982",REGISTRY_ID:"0x7c256edbda983a2cd6f946655f4bf3f00a41043993781f8674a7046e8c0e11d1",DEEP_TREASURY_ID:"0x69fffdae0075f8f71f4fa793549c11079266910e8905169845af1f5d00e09dcb",MARGIN_PACKAGE_ID:"0x7f2d8f15343f210e813595a8798d6197d152061d0a35be938372f4b1cd66f209",MARGIN_INITIAL_PACKAGE_ID:"0x7f2d8f15343f210e813595a8798d6197d152061d0a35be938372f4b1cd66f209",MARGIN_REGISTRY_ID:"0x31b9086767e9b5925cb29414ea623a7705b5600d9594d88c17c7a011cb499ab4"},c={DEEP:{address:"0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8",type:"0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8::deep::DEEP",scalar:1e6,decimals:6},SUI:{address:"0x0000000000000000000000000000000000000000000000000000000000000002",type:"0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI",scalar:1e9,decimals:9},DBUSDC:{address:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7",type:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC",scalar:1e6,decimals:6},DBUSDT:{address:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7",type:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDT::DBUSDT",scalar:1e6,decimals:6},WAL:{address:"0x9ef7676a9f81937a52ae4b2af8d511a28a0b080477c0c2db40b0ab8882240d76",type:"0x9ef7676a9f81937a52ae4b2af8d511a28a0b080477c0c2db40b0ab8882240d76::wal::WAL",scalar:1e9,decimals:9}},S={DEEP_SUI:{address:"0x48c95963e9eac37a316b7ae04a0deb761bcdcc2b67912374d6036e7f0e9bae9f",baseCoin:"DEEP",quoteCoin:"SUI",lotSize:1e6,tickSize:1e6},SUI_DBUSDC:{address:"0x1c19362ca52b8ffd7a33cee805a67d40f31e6ba303753fd3a4cfdfacea7163a5",baseCoin:"SUI",quoteCoin:"DBUSDC",lotSize:1e6,tickSize:1e3},DEEP_DBUSDC:{address:"0xe86b991f8632217505fd859445f9803967ac84a9d4a1219065bf191fcb74b622",baseCoin:"DEEP",quoteCoin:"DBUSDC",lotSize:1e6,tickSize:1e3},DBUSDT_DBUSDC:{address:"0x83970bb02e3636efdff8c141ab06af5e3c9a22e2f74d7f02a9c3430d0d10c1ca",baseCoin:"DBUSDT",quoteCoin:"DBUSDC",lotSize:1e6,tickSize:1e3},WAL_DBUSDC:{address:"0xeb524b6aea0ec4b494878582e0b78924208339d360b62aec4a8ecd4031520dbb",baseCoin:"WAL",quoteCoin:"DBUSDC",lotSize:1e6,tickSize:1e3},WAL_SUI:{address:"0x8c1c1b186c4fddab1ebd53e0895a36c1d1b3b9a77cd34e607bef49a38af0150a",baseCoin:"WAL",quoteCoin:"SUI",lotSize:1e6,tickSize:1e6}},d={SUI:{address:"0xe620d6a5390e57e88baff18af89383130d4210eb496a024edcd62f270a655af7",coinType:"0x2::sui::SUI",initialVersion:658877881,supplyCap:1e6,maxUtilizationRate:.9,referralSpread:.1,minBorrow:.1},DBUSDC:{address:"0xfd0dc290a120ad6c534507614d4dc0b2e78baab649c35bfacbaec2ce18140b69",coinType:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC",initialVersion:658877215,supplyCap:1e6,maxUtilizationRate:.95,referralSpread:.1,minBorrow:.1}};var _=["supplyCap","maxUtilizationRate","protocolSpread","minBorrow","interestRate","totalSupply","supplyShares","totalBorrow","borrowShares","lastUpdateTimestamp"],b=["userSupplyShares","userSupplyAmount"],T={supplyCap:"U64",maxUtilizationRate:"U64",protocolSpread:"U64",minBorrow:"U64",interestRate:"U64",totalSupply:"U64",supplyShares:"U64",totalBorrow:"U64",borrowShares:"U64",lastUpdateTimestamp:"U64",userSupplyShares:"U64",userSupplyAmount:"U64"};var K=s=>/^0x[0-9a-fA-F]+$|^[0-9a-fA-F]+$/.test(s),O=s=>/^[a-zA-Z0-9+/]+={0,2}$/g.test(s),A=s=>{if(K(s))return bcs$1.fromHex(s);if(O(s))return bcs$1.fromBase64(s);throw new Error("The string is not a valid hex or base64 string.")},y=s=>{if(s.length===cryptography.LEGACY_PRIVATE_KEY_SIZE)return s.slice(0,cryptography.PRIVATE_KEY_SIZE);if(s.length===cryptography.PRIVATE_KEY_SIZE+1&&s[0]===0)return s.slice(1);if(s.length===cryptography.PRIVATE_KEY_SIZE)return s;throw new Error("invalid secret key")};var P=class{suiClient;keypair;address;marginPoolContract;supplierCapId;constructor(e){let t=e.fullnodeUrl??client.getFullnodeUrl(e.network);this.suiClient=new client.SuiClient({url:t}),this.keypair=this.#e(e.privateKey),this.address=this.keypair.getPublicKey().toSuiAddress(),this.supplierCapId=e.supplierCapId;let r={DEEP:{address:c.DEEP.address,type:c.DEEP.type,scalar:c.DEEP.scalar},SUI:{address:c.SUI.address,type:c.SUI.type,scalar:c.SUI.scalar},DBUSDC:{address:c.DBUSDC.address,type:c.DBUSDC.type,scalar:c.DBUSDC.scalar}},a={SUI_DBUSDC:{address:S.SUI_DBUSDC.address,baseCoin:S.SUI_DBUSDC.baseCoin,quoteCoin:S.SUI_DBUSDC.quoteCoin}},o={SUI:{address:d.SUI.address,type:d.SUI.coinType},DBUSDC:{address:d.DBUSDC.address,type:d.DBUSDC.coinType}},i=new deepbookV3.DeepBookConfig({address:this.address,env:e.network,coins:r,pools:a,marginPools:o});this.marginPoolContract=new deepbookV3.MarginPoolContract(i);}#e(e){if(e.startsWith(cryptography.SUI_PRIVATE_KEY_PREFIX)){let{secretKey:t}=cryptography.decodeSuiPrivateKey(e);return ed25519.Ed25519Keypair.fromSecretKey(y(t))}return ed25519.Ed25519Keypair.fromSecretKey(y(A(e)))}async#r(){let e=`${g.MARGIN_INITIAL_PACKAGE_ID}::margin_pool::SupplierCap`;return (await this.suiClient.getOwnedObjects({owner:this.address,filter:{StructType:e},options:{showType:true}})).data?.[0]?.data?.objectId}async initialize(){if(this.supplierCapId)return this.supplierCapId;let e=await this.#r();if(e)return this.supplierCapId=e,e;let t=await this.createSupplierCap();if(!t)throw new Error("Failed to create Supplier Cap");return this.supplierCapId=t,t}async createSupplierCap(){try{let e=new transactions.Transaction;e.setSender(this.address);let t=e.moveCall({target:`${g.MARGIN_PACKAGE_ID}::margin_pool::mint_supplier_cap`,arguments:[e.object(g.MARGIN_REGISTRY_ID),e.object.clock()]});e.transferObjects([t],e.pure.address(this.address));let r=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:e,options:{showEffects:!0,showObjectChanges:!0},requestType:"WaitForLocalExecution"});if(r.errors&&r.errors.length>0)throw new Error(`Transaction failed with errors: ${r.errors.map(a=>a.toString()).join(", ")}`);if(r.objectChanges){for(let a of r.objectChanges)if(a.type==="created"&&a.objectType.includes("SupplierCap"))return a.objectId}return null}catch(e){throw new Error(`Failed to create Supplier Cap: ${e.message||e}`)}}async createSupplyReferral(e){try{let t=new transactions.Transaction;t.setSender(this.address);let r=d[e];t.moveCall({target:`${g.MARGIN_PACKAGE_ID}::margin_pool::mint_supply_referral`,arguments:[t.sharedObjectRef({objectId:r.address,initialSharedVersion:r.initialVersion,mutable:!0}),t.object(g.MARGIN_REGISTRY_ID),t.object.clock()],typeArguments:[r.coinType]});let a=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:t,options:{showEffects:!0,showObjectChanges:!0},requestType:"WaitForLocalExecution"});if(a.errors&&a.errors.length>0)throw new Error(`Transaction failed with errors: ${a.errors.map(o=>o.toString()).join(", ")}`);if(a.objectChanges){for(let o of a.objectChanges)if(o.type==="created"&&o.objectType.includes("SupplyReferral"))return o.objectId}return null}catch(t){throw new Error(`Failed to create Supply Referral: ${t.message||t}`)}}async supplyToMarginPool(e,t,r){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let a=new transactions.Transaction;a.setSender(this.address);let o=a.object(this.supplierCapId);a.add(this.marginPoolContract.supplyToMarginPool(e,o,t,r));let{errors:i}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:a,options:{showEffects:!0,showObjectChanges:!0},requestType:"WaitForLocalExecution"});if(i&&i.length>0)throw new Error(`Transaction failed with errors: ${i.map(n=>n.toString()).join(", ")}`);return !0}catch(a){throw new Error(`Failed to supply to margin pool: ${a.message||a}`)}}async withdrawFromMarginPool(e,t){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let r=new transactions.Transaction,a=r.object(this.supplierCapId),i=this.marginPoolContract.withdrawFromMarginPool(e,a,t)(r);r.transferObjects([i],this.address);let{errors:n}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:r,options:{showEffects:!0,showObjectChanges:!0},requestType:"WaitForLocalExecution"});if(n&&n.length>0)throw new Error(`Transaction failed with errors: ${n.map(l=>l.toString()).join(", ")}`);return !0}catch(r){throw new Error(`Failed to withdraw from margin pool: ${r.message||r}`)}}async withdrawReferralFees(e,t){try{let r=new transactions.Transaction;r.add(this.marginPoolContract.withdrawReferralFees(e,t));let{errors:a}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:r,options:{showEffects:!0,showObjectChanges:!0,showBalanceChanges:!0},requestType:"WaitForLocalExecution"});if(a&&a.length>0)throw new Error(`Transaction failed with errors: ${a.map(o=>o.toString()).join(", ")}`);return !0}catch(r){throw new Error(`Failed to withdraw referral fees: ${r.message||r}`)}}async getBalance(e){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let t=new transactions.Transaction;t.add(this.marginPoolContract.userSupplyAmount(e,this.supplierCapId));let r=await this.suiClient.devInspectTransactionBlock({sender:this.address,transactionBlock:t}),a=0;if(r&&r.results&&r.results[0]&&r.results[0].returnValues){let p=r.results[0].returnValues[0];if(p&&p[0]){let f=Buffer.from(p[0]).readBigUInt64LE(),R=e==="SUI"?c.SUI.scalar:c.DBUSDC.scalar;a=Number(f)/R;}}let o=e==="SUI"?c.SUI.type:c.DBUSDC.type,i=await this.suiClient.getBalance({owner:this.address,coinType:o}),n=e==="SUI"?c.SUI.scalar:c.DBUSDC.scalar,l=Number(i.totalBalance)/n;return {userSupplyAmount:a,walletBalance:l}}catch(t){throw new Error(`Failed to get balance: ${t.message||t}`)}}getSupplierCapId(){return this.supplierCapId}getAddress(){return this.address}};var C=(s,e)=>s*e/BigInt(deepbookV3.FLOAT_SCALAR),u=s=>Number(s)/deepbookV3.FLOAT_SCALAR;var V=new Set(b),H=s=>V.has(s),E=class{constructor(e="testnet",t="",r=new client.SuiClient({url:client.getFullnodeUrl(e)}),a){this.suiClient=r;this.dbConfig=a??new deepbookV3.DeepBookConfig({env:e,address:t,coins:c,pools:S,marginPools:{SUI:{address:d.SUI.address,type:d.SUI.coinType},DBUSDC:{address:d.DBUSDC.address,type:d.DBUSDC.coinType}}}),this.marginPoolContract=new deepbookV3.MarginPoolContract(this.dbConfig);}marginPoolContract;dbConfig;get env(){return this.dbConfig.env}#e(e,t,r,a){if(H(t)){let o=this.marginPoolContract[t];if(a==null)throw new Error(`supplierCap is required for '${t}'.`);e.add(o(r,a));}else {let o=this.marginPoolContract[t];e.add(o(r));}}#r(e,t){let r=e.results;return r?t.reduce((a,o,i)=>{let n=r[i]?.returnValues?.[0]?.[0];if(!n)return a;let l=bcs.bcs[T[o]];return a[o]=l.parse(new Uint8Array(n)),a},{}):{}}#a(e,t){let r=this.dbConfig.getCoin(t),a={supplyCap:0,maxUtilizationRate:0,protocolSpread:0,minBorrow:0,interestRate:0,totalSupply:0,supplyShares:0,totalBorrow:0,borrowShares:0,lastUpdateTimestamp:0,userSupplyShares:0,userSupplyAmount:0,decimals:0,highKink:0,baseBorrowApr:0,borrowAprOnHighKink:0,maxBorrowApr:0,supplyApr:0,utilizationRate:0,...r};if(!r)return a;let o=new Set(["interestRate","maxUtilizationRate","protocolSpread"]);for(let[i,n]of Object.entries(e))i==="lastUpdateTimestamp"?a[i]=Number(n):o.has(i)?a[i]=new bignumber_js.BigNumber(n).dividedBy(deepbookV3.FLOAT_SCALAR).toNumber():a[i]=new bignumber_js.BigNumber(n).dividedBy(r.scalar).toNumber();return a}#t(e,t){let r=BigInt(t.base_rate),a=BigInt(t.base_slope),o=BigInt(t.excess_slope),i=BigInt(t.optimal_utilization);return e<i?r+C(e,a):r+C(i,a)+C(e-i,o)}#o(e,t,r,a){let o=BigInt(e.optimal_utilization),i=BigInt(t.max_utilization_rate),n=this.#t(o,e),l=this.#t(i,e),p=BigInt(bignumber_js.BigNumber(r.total_borrow).dividedBy(r.total_supply).shiftedBy(9).decimalPlaces(0).toString()),f=C(C(BigInt(a),p),BigInt(deepbookV3.FLOAT_SCALAR)-BigInt(t.protocol_spread));return {raw:{baseBorrowApr:e.base_rate,highKink:o,borrowAprOnHighKink:n,maxBorrowApr:l,supplyApr:f,utilizationRate:p},normalized:{baseBorrowApr:u(BigInt(e.base_rate)),highKink:u(o),borrowAprOnHighKink:u(n),maxBorrowApr:u(l),supplyApr:u(f),utilizationRate:u(p)}}}async#i(e,t){let{address:r}=this.dbConfig.getMarginPool(e),o=((await this.suiClient.getObject({id:r,options:{showContent:true}})).data?.content).fields,i=o.config.fields,n=i.interest_config.fields,l=i.margin_pool_config.fields,p=o.state.fields,{normalized:f}=this.#o(n,l,p,t);return f}async getPoolParameters(e,t,r=new transactions.Transaction,a=true){if(_.forEach(p=>this.#e(r,p,e)),t&&b.forEach(p=>this.#e(r,p,e,t)),!a)return r;let o=[..._,...b],i=await this.suiClient.devInspectTransactionBlock({transactionBlock:r,sender:this.dbConfig.address}),n=this.#a(this.#r(i,o),e),l=await this.#i(e,n.interestRate*deepbookV3.FLOAT_SCALAR);return {...n,...l}}};exports.DeepBookMarginPool=E;exports.DeepBookMarginToolkit=P;
1
+ 'use strict';var client=require('@mysten/sui/client'),ed25519=require('@mysten/sui/keypairs/ed25519'),transactions=require('@mysten/sui/transactions'),deepbookV3=require('@mysten/deepbook-v3'),cryptography=require('@mysten/sui/cryptography'),bcs$1=require('@mysten/bcs'),bcs=require('@mysten/sui/bcs'),bignumber_js=require('bignumber.js');var K=a=>/^0x[0-9a-fA-F]+$|^[0-9a-fA-F]+$/.test(a),B=a=>/^[a-zA-Z0-9+/]+={0,2}$/g.test(a),S=a=>{if(K(a))return bcs$1.fromHex(a);if(B(a))return bcs$1.fromBase64(a);throw new Error("The string is not a valid hex or base64 string.")},f=a=>{if(a.length===cryptography.LEGACY_PRIVATE_KEY_SIZE)return a.slice(0,cryptography.PRIVATE_KEY_SIZE);if(a.length===cryptography.PRIVATE_KEY_SIZE+1&&a[0]===0)return a.slice(1);if(a.length===cryptography.PRIVATE_KEY_SIZE)return a;throw new Error("invalid secret key")};var C=class{suiClient;keypair;address;marginPoolContract;supplierCapId;dbConfig;supplierCapPackageId;constructor({network:r,fullnodeUrl:e,supplierCapId:t,privateKey:i,supplierCapPackageId:o,dbConfig:n}){let s=e??client.getFullnodeUrl(r);this.suiClient=new client.SuiClient({url:s}),this.keypair=this.#r(i),this.address=this.keypair.getPublicKey().toSuiAddress(),this.supplierCapId=t,this.dbConfig=n??new deepbookV3.DeepBookConfig({env:r,address:this.address}),this.marginPoolContract=new deepbookV3.MarginPoolContract(this.dbConfig),this.supplierCapPackageId=o??this.dbConfig.MARGIN_PACKAGE_ID;}#r(r){if(r.startsWith(cryptography.SUI_PRIVATE_KEY_PREFIX)){let{secretKey:e}=cryptography.decodeSuiPrivateKey(r);return ed25519.Ed25519Keypair.fromSecretKey(f(e))}return ed25519.Ed25519Keypair.fromSecretKey(f(S(r)))}async#t(){let r=`${this.dbConfig.MARGIN_PACKAGE_ID}::margin_pool::SupplierCap`;return (await this.suiClient.getOwnedObjects({owner:this.address,filter:{StructType:r},options:{showType:true}})).data?.[0]?.data?.objectId}async initialize(){if(this.supplierCapId)return this.supplierCapId;let r=await this.#t();if(r)return this.supplierCapId=r,r;let e=await this.createSupplierCap();if(!e)throw new Error("Failed to create Supplier Cap");return this.supplierCapId=e,e}async createSupplierCap(){try{let r=new transactions.Transaction;r.setSender(this.address);let e=r.moveCall({target:`${this.supplierCapPackageId}::margin_pool::mint_supplier_cap`,arguments:[r.object(this.dbConfig.MARGIN_REGISTRY_ID),r.object.clock()]});r.transferObjects([e],r.pure.address(this.address));let t=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:r,options:{showEffects:!0,showObjectChanges:!0}});if(t.errors&&t.errors.length>0)throw new Error(`Transaction failed with errors: ${t.errors.map(i=>i.toString()).join(", ")}`);if(t.objectChanges){for(let i of t.objectChanges)if(i.type==="created"&&i.objectType.includes("SupplierCap"))return i.objectId}return null}catch(r){throw new Error(`Failed to create Supplier Cap: ${r.message||r}`)}}async createSupplyReferral(r){try{let e=new transactions.Transaction;e.setSender(this.address);let t=this.dbConfig.getMarginPool(r);if(!t)throw new Error(`Margin pool configuration not found for coin: ${r}`);e.moveCall({target:`${this.dbConfig.MARGIN_PACKAGE_ID}::margin_pool::mint_supply_referral`,arguments:[e.object(t.address),e.object(this.dbConfig.MARGIN_REGISTRY_ID),e.object.clock()],typeArguments:[t.type]});let i=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:e,options:{showEffects:!0,showObjectChanges:!0}});if(i.errors&&i.errors.length>0)throw new Error(`Transaction failed with errors: ${i.errors.map(o=>o.toString()).join(", ")}`);if(i.objectChanges){for(let o of i.objectChanges)if(o.type==="created"&&o.objectType.includes("SupplyReferral"))return o.objectId}return null}catch(e){throw new Error(`Failed to create Supply Referral: ${e.message||e}`)}}async supplyToMarginPool(r,e,t){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let i=new transactions.Transaction;i.setSender(this.address);let o=i.object(this.supplierCapId);i.add(this.marginPoolContract.supplyToMarginPool(r,o,e,t));let{errors:n}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:i,options:{showEffects:!0,showObjectChanges:!0}});if(n&&n.length>0)throw new Error(`Transaction failed with errors: ${n.map(s=>s.toString()).join(", ")}`);return !0}catch(i){throw new Error(`Failed to supply to margin pool: ${i.message||i}`)}}async withdrawFromMarginPool(r,e){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let t=new transactions.Transaction,i=t.object(this.supplierCapId),n=this.marginPoolContract.withdrawFromMarginPool(r,i,e)(t);t.transferObjects([n],this.address);let{errors:s}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:t,options:{showEffects:!0,showObjectChanges:!0}});if(s&&s.length>0)throw new Error(`Transaction failed with errors: ${s.map(l=>l.toString()).join(", ")}`);return !0}catch(t){throw new Error(`Failed to withdraw from margin pool: ${t.message||t}`)}}async withdrawReferralFees(r,e){try{let t=new transactions.Transaction;t.add(this.marginPoolContract.withdrawReferralFees(r,e));let{errors:i}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:t,options:{showEffects:!0,showObjectChanges:!0,showBalanceChanges:!0}});if(i&&i.length>0)throw new Error(`Transaction failed with errors: ${i.map(o=>o.toString()).join(", ")}`);return !0}catch(t){throw new Error(`Failed to withdraw referral fees: ${t.message||t}`)}}async getBalance(r){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let e=new transactions.Transaction;e.add(this.marginPoolContract.userSupplyAmount(r,this.supplierCapId));let t=await this.suiClient.devInspectTransactionBlock({sender:this.address,transactionBlock:e}),i=0;if(t&&t.results&&t.results[0]&&t.results[0].returnValues){let p=t.results[0].returnValues[0];if(p&&p[0]){let g=Buffer.from(p[0]).readBigUInt64LE(),R=this.dbConfig.getCoin(r).scalar;i=Number(g)/R;}}let o=this.dbConfig.getCoin(r).type,n=await this.suiClient.getBalance({owner:this.address,coinType:o}),s=this.dbConfig.getCoin(r).scalar,l=Number(n.totalBalance)/s;return {userSupplyAmount:i,walletBalance:l}}catch(e){throw new Error(`Failed to get balance: ${e.message||e}`)}}getSupplierCapId(){return this.supplierCapId}getAddress(){return this.address}};var P=["supplyCap","maxUtilizationRate","protocolSpread","minBorrow","interestRate","totalSupply","supplyShares","totalBorrow","borrowShares","lastUpdateTimestamp"],m=["userSupplyShares","userSupplyAmount"],A={supplyCap:"U64",maxUtilizationRate:"U64",protocolSpread:"U64",minBorrow:"U64",interestRate:"U64",totalSupply:"U64",supplyShares:"U64",totalBorrow:"U64",borrowShares:"U64",lastUpdateTimestamp:"U64",userSupplyShares:"U64",userSupplyAmount:"U64"};var d=(a,r)=>a*r/BigInt(deepbookV3.FLOAT_SCALAR),c=a=>Number(a)/deepbookV3.FLOAT_SCALAR;var W=new Set(m),Y=a=>W.has(a),b=class{marginPoolContract;dbConfig;suiClient;constructor({env:r="mainnet",address:e="",suiClient:t=new client.SuiClient({url:client.getFullnodeUrl(r)}),dbConfig:i=new deepbookV3.DeepBookConfig({env:r,address:e})}={}){if(this.dbConfig=i,this.suiClient=t,this.marginPoolContract=new deepbookV3.MarginPoolContract(this.dbConfig),r!==this.env)throw new Error(`Mismatch between provided env (${r}) and dbConfig env (${this.env}).`)}get env(){return this.dbConfig.env}#r(r,e,t,i){if(Y(e)){let o=this.marginPoolContract[e];if(i==null)throw new Error(`supplierCap is required for '${e}'.`);r.add(o(t,i));}else {let o=this.marginPoolContract[e];r.add(o(t));}}#t(r,e){let t=r.results;return t?e.reduce((i,o,n)=>{let s=t[n]?.returnValues?.[0]?.[0];if(!s)return i;let l=bcs.bcs[A[o]];return i[o]=l.parse(new Uint8Array(s)),i},{}):{}}#i(r,e){let t=this.dbConfig.getCoin(e),i={supplyCap:0,maxUtilizationRate:0,protocolSpread:0,minBorrow:0,interestRate:0,totalSupply:0,supplyShares:0,totalBorrow:0,borrowShares:0,lastUpdateTimestamp:0,userSupplyShares:0,userSupplyAmount:0,decimals:this.dbConfig.getCoin(e).scalar.toString().length-1,highKink:0,baseBorrowApr:0,borrowAprOnHighKink:0,maxBorrowApr:0,supplyApr:0,utilizationRate:0,...t};if(!t)return i;let o=new Set(["interestRate","maxUtilizationRate","protocolSpread"]);for(let[n,s]of Object.entries(r))n==="lastUpdateTimestamp"?i[n]=Number(s):o.has(n)?i[n]=new bignumber_js.BigNumber(s).dividedBy(deepbookV3.FLOAT_SCALAR).toNumber():i[n]=new bignumber_js.BigNumber(s).dividedBy(t.scalar).toNumber();return i}#e(r,e){let t=BigInt(e.base_rate),i=BigInt(e.base_slope),o=BigInt(e.excess_slope),n=BigInt(e.optimal_utilization);return r<n?t+d(r,i):t+d(n,i)+d(r-n,o)}#o(r,e,t,i){let o=BigInt(r.optimal_utilization),n=BigInt(e.max_utilization_rate),s=this.#e(o,r),l=this.#e(n,r),p=BigInt(bignumber_js.BigNumber(t.total_borrow).dividedBy(t.total_supply).shiftedBy(9).decimalPlaces(0).toString()),g=d(d(BigInt(i),p),BigInt(deepbookV3.FLOAT_SCALAR)-BigInt(e.protocol_spread));return {raw:{baseBorrowApr:r.base_rate,highKink:o,borrowAprOnHighKink:s,maxBorrowApr:l,supplyApr:g,utilizationRate:p},normalized:{baseBorrowApr:c(BigInt(r.base_rate)),highKink:c(o),borrowAprOnHighKink:c(s),maxBorrowApr:c(l),supplyApr:c(g),utilizationRate:c(p)}}}async#n(r,e){let{address:t}=this.dbConfig.getMarginPool(r),o=((await this.suiClient.getObject({id:t,options:{showContent:true}})).data?.content).fields,n=o.config.fields,s=n.interest_config.fields,l=n.margin_pool_config.fields,p=o.state.fields,{normalized:g}=this.#o(s,l,p,e);return g}async getPoolParameters(r,e,t=new transactions.Transaction,i=true){if(P.forEach(p=>this.#r(t,p,r)),e&&m.forEach(p=>this.#r(t,p,r,e)),!i)return t;let o=[...P,...m],n=await this.suiClient.devInspectTransactionBlock({transactionBlock:t,sender:this.dbConfig.address}),s=this.#i(this.#t(n,o),r),l=await this.#n(r,s.interestRate*deepbookV3.FLOAT_SCALAR);return {...s,...l}}};exports.DeepBookMarginPool=b;exports.DeepBookMarginToolkit=C;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import {getFullnodeUrl,SuiClient}from'@mysten/sui/client';import {Ed25519Keypair}from'@mysten/sui/keypairs/ed25519';import {Transaction}from'@mysten/sui/transactions';import {DeepBookConfig,MarginPoolContract,FLOAT_SCALAR}from'@mysten/deepbook-v3';import {SUI_PRIVATE_KEY_PREFIX,decodeSuiPrivateKey,LEGACY_PRIVATE_KEY_SIZE,PRIVATE_KEY_SIZE}from'@mysten/sui/cryptography';import {fromHex,fromBase64}from'@mysten/bcs';import {bcs}from'@mysten/sui/bcs';import {BigNumber}from'bignumber.js';var g={DEEPBOOK_PACKAGE_ID:"0xfb28c4cbc6865bd1c897d26aecbe1f8792d1509a20ffec692c800660cbec6982",REGISTRY_ID:"0x7c256edbda983a2cd6f946655f4bf3f00a41043993781f8674a7046e8c0e11d1",DEEP_TREASURY_ID:"0x69fffdae0075f8f71f4fa793549c11079266910e8905169845af1f5d00e09dcb",MARGIN_PACKAGE_ID:"0x7f2d8f15343f210e813595a8798d6197d152061d0a35be938372f4b1cd66f209",MARGIN_INITIAL_PACKAGE_ID:"0x7f2d8f15343f210e813595a8798d6197d152061d0a35be938372f4b1cd66f209",MARGIN_REGISTRY_ID:"0x31b9086767e9b5925cb29414ea623a7705b5600d9594d88c17c7a011cb499ab4"},c={DEEP:{address:"0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8",type:"0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8::deep::DEEP",scalar:1e6,decimals:6},SUI:{address:"0x0000000000000000000000000000000000000000000000000000000000000002",type:"0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI",scalar:1e9,decimals:9},DBUSDC:{address:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7",type:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC",scalar:1e6,decimals:6},DBUSDT:{address:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7",type:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDT::DBUSDT",scalar:1e6,decimals:6},WAL:{address:"0x9ef7676a9f81937a52ae4b2af8d511a28a0b080477c0c2db40b0ab8882240d76",type:"0x9ef7676a9f81937a52ae4b2af8d511a28a0b080477c0c2db40b0ab8882240d76::wal::WAL",scalar:1e9,decimals:9}},S={DEEP_SUI:{address:"0x48c95963e9eac37a316b7ae04a0deb761bcdcc2b67912374d6036e7f0e9bae9f",baseCoin:"DEEP",quoteCoin:"SUI",lotSize:1e6,tickSize:1e6},SUI_DBUSDC:{address:"0x1c19362ca52b8ffd7a33cee805a67d40f31e6ba303753fd3a4cfdfacea7163a5",baseCoin:"SUI",quoteCoin:"DBUSDC",lotSize:1e6,tickSize:1e3},DEEP_DBUSDC:{address:"0xe86b991f8632217505fd859445f9803967ac84a9d4a1219065bf191fcb74b622",baseCoin:"DEEP",quoteCoin:"DBUSDC",lotSize:1e6,tickSize:1e3},DBUSDT_DBUSDC:{address:"0x83970bb02e3636efdff8c141ab06af5e3c9a22e2f74d7f02a9c3430d0d10c1ca",baseCoin:"DBUSDT",quoteCoin:"DBUSDC",lotSize:1e6,tickSize:1e3},WAL_DBUSDC:{address:"0xeb524b6aea0ec4b494878582e0b78924208339d360b62aec4a8ecd4031520dbb",baseCoin:"WAL",quoteCoin:"DBUSDC",lotSize:1e6,tickSize:1e3},WAL_SUI:{address:"0x8c1c1b186c4fddab1ebd53e0895a36c1d1b3b9a77cd34e607bef49a38af0150a",baseCoin:"WAL",quoteCoin:"SUI",lotSize:1e6,tickSize:1e6}},d={SUI:{address:"0xe620d6a5390e57e88baff18af89383130d4210eb496a024edcd62f270a655af7",coinType:"0x2::sui::SUI",initialVersion:658877881,supplyCap:1e6,maxUtilizationRate:.9,referralSpread:.1,minBorrow:.1},DBUSDC:{address:"0xfd0dc290a120ad6c534507614d4dc0b2e78baab649c35bfacbaec2ce18140b69",coinType:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC",initialVersion:658877215,supplyCap:1e6,maxUtilizationRate:.95,referralSpread:.1,minBorrow:.1}};var _=["supplyCap","maxUtilizationRate","protocolSpread","minBorrow","interestRate","totalSupply","supplyShares","totalBorrow","borrowShares","lastUpdateTimestamp"],b=["userSupplyShares","userSupplyAmount"],T={supplyCap:"U64",maxUtilizationRate:"U64",protocolSpread:"U64",minBorrow:"U64",interestRate:"U64",totalSupply:"U64",supplyShares:"U64",totalBorrow:"U64",borrowShares:"U64",lastUpdateTimestamp:"U64",userSupplyShares:"U64",userSupplyAmount:"U64"};var K=s=>/^0x[0-9a-fA-F]+$|^[0-9a-fA-F]+$/.test(s),O=s=>/^[a-zA-Z0-9+/]+={0,2}$/g.test(s),A=s=>{if(K(s))return fromHex(s);if(O(s))return fromBase64(s);throw new Error("The string is not a valid hex or base64 string.")},y=s=>{if(s.length===LEGACY_PRIVATE_KEY_SIZE)return s.slice(0,PRIVATE_KEY_SIZE);if(s.length===PRIVATE_KEY_SIZE+1&&s[0]===0)return s.slice(1);if(s.length===PRIVATE_KEY_SIZE)return s;throw new Error("invalid secret key")};var P=class{suiClient;keypair;address;marginPoolContract;supplierCapId;constructor(e){let t=e.fullnodeUrl??getFullnodeUrl(e.network);this.suiClient=new SuiClient({url:t}),this.keypair=this.#e(e.privateKey),this.address=this.keypair.getPublicKey().toSuiAddress(),this.supplierCapId=e.supplierCapId;let r={DEEP:{address:c.DEEP.address,type:c.DEEP.type,scalar:c.DEEP.scalar},SUI:{address:c.SUI.address,type:c.SUI.type,scalar:c.SUI.scalar},DBUSDC:{address:c.DBUSDC.address,type:c.DBUSDC.type,scalar:c.DBUSDC.scalar}},a={SUI_DBUSDC:{address:S.SUI_DBUSDC.address,baseCoin:S.SUI_DBUSDC.baseCoin,quoteCoin:S.SUI_DBUSDC.quoteCoin}},o={SUI:{address:d.SUI.address,type:d.SUI.coinType},DBUSDC:{address:d.DBUSDC.address,type:d.DBUSDC.coinType}},i=new DeepBookConfig({address:this.address,env:e.network,coins:r,pools:a,marginPools:o});this.marginPoolContract=new MarginPoolContract(i);}#e(e){if(e.startsWith(SUI_PRIVATE_KEY_PREFIX)){let{secretKey:t}=decodeSuiPrivateKey(e);return Ed25519Keypair.fromSecretKey(y(t))}return Ed25519Keypair.fromSecretKey(y(A(e)))}async#r(){let e=`${g.MARGIN_INITIAL_PACKAGE_ID}::margin_pool::SupplierCap`;return (await this.suiClient.getOwnedObjects({owner:this.address,filter:{StructType:e},options:{showType:true}})).data?.[0]?.data?.objectId}async initialize(){if(this.supplierCapId)return this.supplierCapId;let e=await this.#r();if(e)return this.supplierCapId=e,e;let t=await this.createSupplierCap();if(!t)throw new Error("Failed to create Supplier Cap");return this.supplierCapId=t,t}async createSupplierCap(){try{let e=new Transaction;e.setSender(this.address);let t=e.moveCall({target:`${g.MARGIN_PACKAGE_ID}::margin_pool::mint_supplier_cap`,arguments:[e.object(g.MARGIN_REGISTRY_ID),e.object.clock()]});e.transferObjects([t],e.pure.address(this.address));let r=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:e,options:{showEffects:!0,showObjectChanges:!0},requestType:"WaitForLocalExecution"});if(r.errors&&r.errors.length>0)throw new Error(`Transaction failed with errors: ${r.errors.map(a=>a.toString()).join(", ")}`);if(r.objectChanges){for(let a of r.objectChanges)if(a.type==="created"&&a.objectType.includes("SupplierCap"))return a.objectId}return null}catch(e){throw new Error(`Failed to create Supplier Cap: ${e.message||e}`)}}async createSupplyReferral(e){try{let t=new Transaction;t.setSender(this.address);let r=d[e];t.moveCall({target:`${g.MARGIN_PACKAGE_ID}::margin_pool::mint_supply_referral`,arguments:[t.sharedObjectRef({objectId:r.address,initialSharedVersion:r.initialVersion,mutable:!0}),t.object(g.MARGIN_REGISTRY_ID),t.object.clock()],typeArguments:[r.coinType]});let a=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:t,options:{showEffects:!0,showObjectChanges:!0},requestType:"WaitForLocalExecution"});if(a.errors&&a.errors.length>0)throw new Error(`Transaction failed with errors: ${a.errors.map(o=>o.toString()).join(", ")}`);if(a.objectChanges){for(let o of a.objectChanges)if(o.type==="created"&&o.objectType.includes("SupplyReferral"))return o.objectId}return null}catch(t){throw new Error(`Failed to create Supply Referral: ${t.message||t}`)}}async supplyToMarginPool(e,t,r){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let a=new Transaction;a.setSender(this.address);let o=a.object(this.supplierCapId);a.add(this.marginPoolContract.supplyToMarginPool(e,o,t,r));let{errors:i}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:a,options:{showEffects:!0,showObjectChanges:!0},requestType:"WaitForLocalExecution"});if(i&&i.length>0)throw new Error(`Transaction failed with errors: ${i.map(n=>n.toString()).join(", ")}`);return !0}catch(a){throw new Error(`Failed to supply to margin pool: ${a.message||a}`)}}async withdrawFromMarginPool(e,t){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let r=new Transaction,a=r.object(this.supplierCapId),i=this.marginPoolContract.withdrawFromMarginPool(e,a,t)(r);r.transferObjects([i],this.address);let{errors:n}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:r,options:{showEffects:!0,showObjectChanges:!0},requestType:"WaitForLocalExecution"});if(n&&n.length>0)throw new Error(`Transaction failed with errors: ${n.map(l=>l.toString()).join(", ")}`);return !0}catch(r){throw new Error(`Failed to withdraw from margin pool: ${r.message||r}`)}}async withdrawReferralFees(e,t){try{let r=new Transaction;r.add(this.marginPoolContract.withdrawReferralFees(e,t));let{errors:a}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:r,options:{showEffects:!0,showObjectChanges:!0,showBalanceChanges:!0},requestType:"WaitForLocalExecution"});if(a&&a.length>0)throw new Error(`Transaction failed with errors: ${a.map(o=>o.toString()).join(", ")}`);return !0}catch(r){throw new Error(`Failed to withdraw referral fees: ${r.message||r}`)}}async getBalance(e){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let t=new Transaction;t.add(this.marginPoolContract.userSupplyAmount(e,this.supplierCapId));let r=await this.suiClient.devInspectTransactionBlock({sender:this.address,transactionBlock:t}),a=0;if(r&&r.results&&r.results[0]&&r.results[0].returnValues){let p=r.results[0].returnValues[0];if(p&&p[0]){let f=Buffer.from(p[0]).readBigUInt64LE(),R=e==="SUI"?c.SUI.scalar:c.DBUSDC.scalar;a=Number(f)/R;}}let o=e==="SUI"?c.SUI.type:c.DBUSDC.type,i=await this.suiClient.getBalance({owner:this.address,coinType:o}),n=e==="SUI"?c.SUI.scalar:c.DBUSDC.scalar,l=Number(i.totalBalance)/n;return {userSupplyAmount:a,walletBalance:l}}catch(t){throw new Error(`Failed to get balance: ${t.message||t}`)}}getSupplierCapId(){return this.supplierCapId}getAddress(){return this.address}};var C=(s,e)=>s*e/BigInt(FLOAT_SCALAR),u=s=>Number(s)/FLOAT_SCALAR;var V=new Set(b),H=s=>V.has(s),E=class{constructor(e="testnet",t="",r=new SuiClient({url:getFullnodeUrl(e)}),a){this.suiClient=r;this.dbConfig=a??new DeepBookConfig({env:e,address:t,coins:c,pools:S,marginPools:{SUI:{address:d.SUI.address,type:d.SUI.coinType},DBUSDC:{address:d.DBUSDC.address,type:d.DBUSDC.coinType}}}),this.marginPoolContract=new MarginPoolContract(this.dbConfig);}marginPoolContract;dbConfig;get env(){return this.dbConfig.env}#e(e,t,r,a){if(H(t)){let o=this.marginPoolContract[t];if(a==null)throw new Error(`supplierCap is required for '${t}'.`);e.add(o(r,a));}else {let o=this.marginPoolContract[t];e.add(o(r));}}#r(e,t){let r=e.results;return r?t.reduce((a,o,i)=>{let n=r[i]?.returnValues?.[0]?.[0];if(!n)return a;let l=bcs[T[o]];return a[o]=l.parse(new Uint8Array(n)),a},{}):{}}#a(e,t){let r=this.dbConfig.getCoin(t),a={supplyCap:0,maxUtilizationRate:0,protocolSpread:0,minBorrow:0,interestRate:0,totalSupply:0,supplyShares:0,totalBorrow:0,borrowShares:0,lastUpdateTimestamp:0,userSupplyShares:0,userSupplyAmount:0,decimals:0,highKink:0,baseBorrowApr:0,borrowAprOnHighKink:0,maxBorrowApr:0,supplyApr:0,utilizationRate:0,...r};if(!r)return a;let o=new Set(["interestRate","maxUtilizationRate","protocolSpread"]);for(let[i,n]of Object.entries(e))i==="lastUpdateTimestamp"?a[i]=Number(n):o.has(i)?a[i]=new BigNumber(n).dividedBy(FLOAT_SCALAR).toNumber():a[i]=new BigNumber(n).dividedBy(r.scalar).toNumber();return a}#t(e,t){let r=BigInt(t.base_rate),a=BigInt(t.base_slope),o=BigInt(t.excess_slope),i=BigInt(t.optimal_utilization);return e<i?r+C(e,a):r+C(i,a)+C(e-i,o)}#o(e,t,r,a){let o=BigInt(e.optimal_utilization),i=BigInt(t.max_utilization_rate),n=this.#t(o,e),l=this.#t(i,e),p=BigInt(BigNumber(r.total_borrow).dividedBy(r.total_supply).shiftedBy(9).decimalPlaces(0).toString()),f=C(C(BigInt(a),p),BigInt(FLOAT_SCALAR)-BigInt(t.protocol_spread));return {raw:{baseBorrowApr:e.base_rate,highKink:o,borrowAprOnHighKink:n,maxBorrowApr:l,supplyApr:f,utilizationRate:p},normalized:{baseBorrowApr:u(BigInt(e.base_rate)),highKink:u(o),borrowAprOnHighKink:u(n),maxBorrowApr:u(l),supplyApr:u(f),utilizationRate:u(p)}}}async#i(e,t){let{address:r}=this.dbConfig.getMarginPool(e),o=((await this.suiClient.getObject({id:r,options:{showContent:true}})).data?.content).fields,i=o.config.fields,n=i.interest_config.fields,l=i.margin_pool_config.fields,p=o.state.fields,{normalized:f}=this.#o(n,l,p,t);return f}async getPoolParameters(e,t,r=new Transaction,a=true){if(_.forEach(p=>this.#e(r,p,e)),t&&b.forEach(p=>this.#e(r,p,e,t)),!a)return r;let o=[..._,...b],i=await this.suiClient.devInspectTransactionBlock({transactionBlock:r,sender:this.dbConfig.address}),n=this.#a(this.#r(i,o),e),l=await this.#i(e,n.interestRate*FLOAT_SCALAR);return {...n,...l}}};export{E as DeepBookMarginPool,P as DeepBookMarginToolkit};
1
+ import {getFullnodeUrl,SuiClient}from'@mysten/sui/client';import {Ed25519Keypair}from'@mysten/sui/keypairs/ed25519';import {Transaction}from'@mysten/sui/transactions';import {DeepBookConfig,MarginPoolContract,FLOAT_SCALAR}from'@mysten/deepbook-v3';import {SUI_PRIVATE_KEY_PREFIX,decodeSuiPrivateKey,LEGACY_PRIVATE_KEY_SIZE,PRIVATE_KEY_SIZE}from'@mysten/sui/cryptography';import {fromHex,fromBase64}from'@mysten/bcs';import {bcs}from'@mysten/sui/bcs';import {BigNumber}from'bignumber.js';var K=a=>/^0x[0-9a-fA-F]+$|^[0-9a-fA-F]+$/.test(a),B=a=>/^[a-zA-Z0-9+/]+={0,2}$/g.test(a),S=a=>{if(K(a))return fromHex(a);if(B(a))return fromBase64(a);throw new Error("The string is not a valid hex or base64 string.")},f=a=>{if(a.length===LEGACY_PRIVATE_KEY_SIZE)return a.slice(0,PRIVATE_KEY_SIZE);if(a.length===PRIVATE_KEY_SIZE+1&&a[0]===0)return a.slice(1);if(a.length===PRIVATE_KEY_SIZE)return a;throw new Error("invalid secret key")};var C=class{suiClient;keypair;address;marginPoolContract;supplierCapId;dbConfig;supplierCapPackageId;constructor({network:r,fullnodeUrl:e,supplierCapId:t,privateKey:i,supplierCapPackageId:o,dbConfig:n}){let s=e??getFullnodeUrl(r);this.suiClient=new SuiClient({url:s}),this.keypair=this.#r(i),this.address=this.keypair.getPublicKey().toSuiAddress(),this.supplierCapId=t,this.dbConfig=n??new DeepBookConfig({env:r,address:this.address}),this.marginPoolContract=new MarginPoolContract(this.dbConfig),this.supplierCapPackageId=o??this.dbConfig.MARGIN_PACKAGE_ID;}#r(r){if(r.startsWith(SUI_PRIVATE_KEY_PREFIX)){let{secretKey:e}=decodeSuiPrivateKey(r);return Ed25519Keypair.fromSecretKey(f(e))}return Ed25519Keypair.fromSecretKey(f(S(r)))}async#t(){let r=`${this.dbConfig.MARGIN_PACKAGE_ID}::margin_pool::SupplierCap`;return (await this.suiClient.getOwnedObjects({owner:this.address,filter:{StructType:r},options:{showType:true}})).data?.[0]?.data?.objectId}async initialize(){if(this.supplierCapId)return this.supplierCapId;let r=await this.#t();if(r)return this.supplierCapId=r,r;let e=await this.createSupplierCap();if(!e)throw new Error("Failed to create Supplier Cap");return this.supplierCapId=e,e}async createSupplierCap(){try{let r=new Transaction;r.setSender(this.address);let e=r.moveCall({target:`${this.supplierCapPackageId}::margin_pool::mint_supplier_cap`,arguments:[r.object(this.dbConfig.MARGIN_REGISTRY_ID),r.object.clock()]});r.transferObjects([e],r.pure.address(this.address));let t=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:r,options:{showEffects:!0,showObjectChanges:!0}});if(t.errors&&t.errors.length>0)throw new Error(`Transaction failed with errors: ${t.errors.map(i=>i.toString()).join(", ")}`);if(t.objectChanges){for(let i of t.objectChanges)if(i.type==="created"&&i.objectType.includes("SupplierCap"))return i.objectId}return null}catch(r){throw new Error(`Failed to create Supplier Cap: ${r.message||r}`)}}async createSupplyReferral(r){try{let e=new Transaction;e.setSender(this.address);let t=this.dbConfig.getMarginPool(r);if(!t)throw new Error(`Margin pool configuration not found for coin: ${r}`);e.moveCall({target:`${this.dbConfig.MARGIN_PACKAGE_ID}::margin_pool::mint_supply_referral`,arguments:[e.object(t.address),e.object(this.dbConfig.MARGIN_REGISTRY_ID),e.object.clock()],typeArguments:[t.type]});let i=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:e,options:{showEffects:!0,showObjectChanges:!0}});if(i.errors&&i.errors.length>0)throw new Error(`Transaction failed with errors: ${i.errors.map(o=>o.toString()).join(", ")}`);if(i.objectChanges){for(let o of i.objectChanges)if(o.type==="created"&&o.objectType.includes("SupplyReferral"))return o.objectId}return null}catch(e){throw new Error(`Failed to create Supply Referral: ${e.message||e}`)}}async supplyToMarginPool(r,e,t){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let i=new Transaction;i.setSender(this.address);let o=i.object(this.supplierCapId);i.add(this.marginPoolContract.supplyToMarginPool(r,o,e,t));let{errors:n}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:i,options:{showEffects:!0,showObjectChanges:!0}});if(n&&n.length>0)throw new Error(`Transaction failed with errors: ${n.map(s=>s.toString()).join(", ")}`);return !0}catch(i){throw new Error(`Failed to supply to margin pool: ${i.message||i}`)}}async withdrawFromMarginPool(r,e){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let t=new Transaction,i=t.object(this.supplierCapId),n=this.marginPoolContract.withdrawFromMarginPool(r,i,e)(t);t.transferObjects([n],this.address);let{errors:s}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:t,options:{showEffects:!0,showObjectChanges:!0}});if(s&&s.length>0)throw new Error(`Transaction failed with errors: ${s.map(l=>l.toString()).join(", ")}`);return !0}catch(t){throw new Error(`Failed to withdraw from margin pool: ${t.message||t}`)}}async withdrawReferralFees(r,e){try{let t=new Transaction;t.add(this.marginPoolContract.withdrawReferralFees(r,e));let{errors:i}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:t,options:{showEffects:!0,showObjectChanges:!0,showBalanceChanges:!0}});if(i&&i.length>0)throw new Error(`Transaction failed with errors: ${i.map(o=>o.toString()).join(", ")}`);return !0}catch(t){throw new Error(`Failed to withdraw referral fees: ${t.message||t}`)}}async getBalance(r){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let e=new Transaction;e.add(this.marginPoolContract.userSupplyAmount(r,this.supplierCapId));let t=await this.suiClient.devInspectTransactionBlock({sender:this.address,transactionBlock:e}),i=0;if(t&&t.results&&t.results[0]&&t.results[0].returnValues){let p=t.results[0].returnValues[0];if(p&&p[0]){let g=Buffer.from(p[0]).readBigUInt64LE(),R=this.dbConfig.getCoin(r).scalar;i=Number(g)/R;}}let o=this.dbConfig.getCoin(r).type,n=await this.suiClient.getBalance({owner:this.address,coinType:o}),s=this.dbConfig.getCoin(r).scalar,l=Number(n.totalBalance)/s;return {userSupplyAmount:i,walletBalance:l}}catch(e){throw new Error(`Failed to get balance: ${e.message||e}`)}}getSupplierCapId(){return this.supplierCapId}getAddress(){return this.address}};var P=["supplyCap","maxUtilizationRate","protocolSpread","minBorrow","interestRate","totalSupply","supplyShares","totalBorrow","borrowShares","lastUpdateTimestamp"],m=["userSupplyShares","userSupplyAmount"],A={supplyCap:"U64",maxUtilizationRate:"U64",protocolSpread:"U64",minBorrow:"U64",interestRate:"U64",totalSupply:"U64",supplyShares:"U64",totalBorrow:"U64",borrowShares:"U64",lastUpdateTimestamp:"U64",userSupplyShares:"U64",userSupplyAmount:"U64"};var d=(a,r)=>a*r/BigInt(FLOAT_SCALAR),c=a=>Number(a)/FLOAT_SCALAR;var W=new Set(m),Y=a=>W.has(a),b=class{marginPoolContract;dbConfig;suiClient;constructor({env:r="mainnet",address:e="",suiClient:t=new SuiClient({url:getFullnodeUrl(r)}),dbConfig:i=new DeepBookConfig({env:r,address:e})}={}){if(this.dbConfig=i,this.suiClient=t,this.marginPoolContract=new MarginPoolContract(this.dbConfig),r!==this.env)throw new Error(`Mismatch between provided env (${r}) and dbConfig env (${this.env}).`)}get env(){return this.dbConfig.env}#r(r,e,t,i){if(Y(e)){let o=this.marginPoolContract[e];if(i==null)throw new Error(`supplierCap is required for '${e}'.`);r.add(o(t,i));}else {let o=this.marginPoolContract[e];r.add(o(t));}}#t(r,e){let t=r.results;return t?e.reduce((i,o,n)=>{let s=t[n]?.returnValues?.[0]?.[0];if(!s)return i;let l=bcs[A[o]];return i[o]=l.parse(new Uint8Array(s)),i},{}):{}}#i(r,e){let t=this.dbConfig.getCoin(e),i={supplyCap:0,maxUtilizationRate:0,protocolSpread:0,minBorrow:0,interestRate:0,totalSupply:0,supplyShares:0,totalBorrow:0,borrowShares:0,lastUpdateTimestamp:0,userSupplyShares:0,userSupplyAmount:0,decimals:this.dbConfig.getCoin(e).scalar.toString().length-1,highKink:0,baseBorrowApr:0,borrowAprOnHighKink:0,maxBorrowApr:0,supplyApr:0,utilizationRate:0,...t};if(!t)return i;let o=new Set(["interestRate","maxUtilizationRate","protocolSpread"]);for(let[n,s]of Object.entries(r))n==="lastUpdateTimestamp"?i[n]=Number(s):o.has(n)?i[n]=new BigNumber(s).dividedBy(FLOAT_SCALAR).toNumber():i[n]=new BigNumber(s).dividedBy(t.scalar).toNumber();return i}#e(r,e){let t=BigInt(e.base_rate),i=BigInt(e.base_slope),o=BigInt(e.excess_slope),n=BigInt(e.optimal_utilization);return r<n?t+d(r,i):t+d(n,i)+d(r-n,o)}#o(r,e,t,i){let o=BigInt(r.optimal_utilization),n=BigInt(e.max_utilization_rate),s=this.#e(o,r),l=this.#e(n,r),p=BigInt(BigNumber(t.total_borrow).dividedBy(t.total_supply).shiftedBy(9).decimalPlaces(0).toString()),g=d(d(BigInt(i),p),BigInt(FLOAT_SCALAR)-BigInt(e.protocol_spread));return {raw:{baseBorrowApr:r.base_rate,highKink:o,borrowAprOnHighKink:s,maxBorrowApr:l,supplyApr:g,utilizationRate:p},normalized:{baseBorrowApr:c(BigInt(r.base_rate)),highKink:c(o),borrowAprOnHighKink:c(s),maxBorrowApr:c(l),supplyApr:c(g),utilizationRate:c(p)}}}async#n(r,e){let{address:t}=this.dbConfig.getMarginPool(r),o=((await this.suiClient.getObject({id:t,options:{showContent:true}})).data?.content).fields,n=o.config.fields,s=n.interest_config.fields,l=n.margin_pool_config.fields,p=o.state.fields,{normalized:g}=this.#o(s,l,p,e);return g}async getPoolParameters(r,e,t=new Transaction,i=true){if(P.forEach(p=>this.#r(t,p,r)),e&&m.forEach(p=>this.#r(t,p,r,e)),!i)return t;let o=[...P,...m],n=await this.suiClient.devInspectTransactionBlock({transactionBlock:t,sender:this.dbConfig.address}),s=this.#i(this.#t(n,o),r),l=await this.#n(r,s.interestRate*FLOAT_SCALAR);return {...s,...l}}};export{b as DeepBookMarginPool,C as DeepBookMarginToolkit};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scallop-io/scallop-deepbook-kit",
3
- "version": "0.1.4",
3
+ "version": "0.1.5-rc.1",
4
4
  "description": "A toolkit for integrating Scallop with DeepBook functionality",
5
5
  "keywords": [
6
6
  "scallop",
@@ -54,8 +54,8 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@mysten/bcs": "^1.9.2",
57
- "@mysten/deepbook-v3": "^0.21.0",
58
- "@mysten/sui": "^1.45.0",
57
+ "@mysten/deepbook-v3": "^0.27.0",
58
+ "@mysten/sui": "^1.45.2",
59
59
  "bignumber.js": "^9.3.1",
60
60
  "dotenv": "^17.2.3"
61
61
  },
@@ -0,0 +1,43 @@
1
+ // ============================================================================
2
+ // Margin Pool Contract Parameter Keys | Margin Pool 合約參數鍵
3
+ // ============================================================================
4
+
5
+ export const MARGIN_POOL_PARAM_KEYS = [
6
+ 'supplyCap',
7
+ 'maxUtilizationRate',
8
+ 'protocolSpread',
9
+ 'minBorrow',
10
+ 'interestRate',
11
+ 'totalSupply',
12
+ 'supplyShares',
13
+ 'totalBorrow',
14
+ 'borrowShares',
15
+ 'lastUpdateTimestamp',
16
+ ] as const;
17
+
18
+ export const MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS = [
19
+ 'userSupplyShares',
20
+ 'userSupplyAmount',
21
+ ] as const;
22
+
23
+ export const MARGIN_POOL_PARAM_KEY_STRUCT_MAP = {
24
+ supplyCap: 'U64',
25
+ maxUtilizationRate: 'U64',
26
+ protocolSpread: 'U64',
27
+ minBorrow: 'U64',
28
+ interestRate: 'U64',
29
+ totalSupply: 'U64',
30
+ supplyShares: 'U64',
31
+ totalBorrow: 'U64',
32
+ borrowShares: 'U64',
33
+ lastUpdateTimestamp: 'U64',
34
+ userSupplyShares: 'U64',
35
+ userSupplyAmount: 'U64',
36
+ } as Record<
37
+ (typeof MARGIN_POOL_PARAM_KEYS)[number] | (typeof MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS)[number],
38
+ 'U64'
39
+ >;
40
+
41
+ export type MarginPoolParamKey = (typeof MARGIN_POOL_PARAM_KEYS)[number];
42
+ export type MarginPoolWithSupplierCapParamKey =
43
+ (typeof MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS)[number];
@@ -1,19 +1,18 @@
1
1
  import { DeepBookConfig, FLOAT_SCALAR, MarginPoolContract } from '@mysten/deepbook-v3';
2
+ import { bcs } from '@mysten/sui/bcs';
2
3
  import { DevInspectResults, getFullnodeUrl, SuiClient } from '@mysten/sui/client';
3
4
  import { Transaction } from '@mysten/sui/transactions';
5
+ import { BigNumber } from 'bignumber.js';
4
6
  import {
5
7
  MARGIN_POOL_PARAM_KEY_STRUCT_MAP,
6
8
  MARGIN_POOL_PARAM_KEYS,
7
9
  MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS,
8
- } from '../testnet-config';
9
- import { bcs } from '@mysten/sui/bcs';
10
- import { TESTNET_COINS, TESTNET_MARGIN_POOLS, TESTNET_POOLS } from '../testnet-config';
11
- import { NetworkType } from './types';
12
- import { BigNumber } from 'bignumber.js';
10
+ MarginPoolParamKey,
11
+ MarginPoolWithSupplierCapParamKey,
12
+ } from '../margin-pool-config';
13
13
  import { mul, normalize } from '../utils/math';
14
+ import { NetworkType } from './types';
14
15
 
15
- type MarginPoolParamKey = (typeof MARGIN_POOL_PARAM_KEYS)[number];
16
- type MarginPoolWithSupplierCapParamKey = (typeof MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS)[number];
17
16
  type InterestConfig = {
18
17
  highKink: number;
19
18
  baseBorrowApr: number;
@@ -64,6 +63,13 @@ const isWithSupplierCapKey = (
64
63
  return _WITH_CAP_KEYS.has(key as string);
65
64
  };
66
65
 
66
+ type DeepBookMarginPoolParams = {
67
+ address?: string;
68
+ suiClient?: SuiClient;
69
+ env?: NetworkType;
70
+ dbConfig?: DeepBookConfig;
71
+ };
72
+
67
73
  /**
68
74
  * DeepBookMarginPool
69
75
  * -------------------
@@ -75,37 +81,32 @@ const isWithSupplierCapKey = (
75
81
  export class DeepBookMarginPool {
76
82
  marginPoolContract: MarginPoolContract;
77
83
  dbConfig: DeepBookConfig;
84
+ suiClient: SuiClient;
78
85
 
79
86
  /**
80
87
  * @param dbConfig - DeepBook configuration instance.
81
88
  * @param suiClient - Optional SuiClient; defaults to fullnode client based on config env.
82
89
  */
83
- constructor(
84
- env: NetworkType = 'testnet',
90
+ constructor({
91
+ env = 'mainnet',
85
92
  address = '',
86
- readonly suiClient = new SuiClient({ url: getFullnodeUrl(env) }),
87
- dbConfig?: DeepBookConfig
88
- ) {
89
- this.dbConfig =
90
- dbConfig ??
91
- new DeepBookConfig({
92
- env,
93
- address,
94
- coins: TESTNET_COINS,
95
- pools: TESTNET_POOLS,
96
- marginPools: {
97
- SUI: {
98
- address: TESTNET_MARGIN_POOLS.SUI.address,
99
- type: TESTNET_MARGIN_POOLS.SUI.coinType,
100
- },
101
- DBUSDC: {
102
- address: TESTNET_MARGIN_POOLS.DBUSDC.address,
103
- type: TESTNET_MARGIN_POOLS.DBUSDC.coinType,
104
- },
105
- },
106
- });
93
+ suiClient = new SuiClient({
94
+ url: getFullnodeUrl(env),
95
+ }),
96
+ dbConfig = new DeepBookConfig({
97
+ env,
98
+ address,
99
+ }),
100
+ }: DeepBookMarginPoolParams = {}) {
101
+ this.dbConfig = dbConfig;
102
+ this.suiClient = suiClient;
103
+
107
104
  // Initialize smart contract wrapper
108
105
  this.marginPoolContract = new MarginPoolContract(this.dbConfig);
106
+
107
+ if (env !== this.env) {
108
+ throw new Error(`Mismatch between provided env (${env}) and dbConfig env (${this.env}).`);
109
+ }
109
110
  }
110
111
 
111
112
  get env() {
@@ -207,7 +208,7 @@ export class DeepBookMarginPool {
207
208
  lastUpdateTimestamp: 0,
208
209
  userSupplyShares: 0,
209
210
  userSupplyAmount: 0,
210
- decimals: 0,
211
+ decimals: this.dbConfig.getCoin(coinKey).scalar.toString().length - 1,
211
212
  highKink: 0,
212
213
  baseBorrowApr: 0,
213
214
  borrowAprOnHighKink: 0,
@@ -295,9 +296,7 @@ export class DeepBookMarginPool {
295
296
  // raw 1e9-scaled values
296
297
  raw: {
297
298
  baseBorrowApr: interestConfig.base_rate,
298
- // midKink, // utilization at kink1
299
299
  highKink, // utilization at kink2
300
- // midBorrowApr, // APR at midKink
301
300
  borrowAprOnHighKink, // APR at highKink
302
301
  maxBorrowApr, // APR at U = 1.0
303
302
  supplyApr,
@@ -35,12 +35,6 @@ import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
35
35
  import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
36
36
  import { Transaction } from '@mysten/sui/transactions';
37
37
  import { MarginPoolContract, DeepBookConfig } from '@mysten/deepbook-v3';
38
- import {
39
- TESTNET_COINS,
40
- TESTNET_POOLS,
41
- TESTNET_MARGIN_POOLS,
42
- TESTNET_PACKAGES,
43
- } from '../testnet-config';
44
38
  import { ToolkitConfig, MarginCoinType, MarginBalance } from './types';
45
39
  import { decodeSuiPrivateKey, SUI_PRIVATE_KEY_PREFIX } from '@mysten/sui/cryptography';
46
40
  import { hexOrBase64ToUint8Array, normalizePrivateKey } from '../utils/private-key';
@@ -54,70 +48,40 @@ export class DeepBookMarginToolkit {
54
48
  private address: string;
55
49
  private marginPoolContract: MarginPoolContract;
56
50
  private supplierCapId?: string;
57
-
58
- constructor(config: ToolkitConfig) {
51
+ private dbConfig: DeepBookConfig;
52
+ private supplierCapPackageId: string;
53
+
54
+ constructor({
55
+ network,
56
+ fullnodeUrl,
57
+ supplierCapId,
58
+ privateKey,
59
+ supplierCapPackageId,
60
+ dbConfig,
61
+ }: ToolkitConfig) {
59
62
  // Initialize SuiClient | 初始化 SuiClient
60
- const rpcUrl = config.fullnodeUrl ?? getFullnodeUrl(config.network);
63
+ const rpcUrl = fullnodeUrl ?? getFullnodeUrl(network);
61
64
  this.suiClient = new SuiClient({ url: rpcUrl });
62
65
 
63
66
  // Initialize keypair | 初始化密鑰對
64
- this.keypair = this.#parseSecretKey(config.privateKey);
67
+ this.keypair = this.#parseSecretKey(privateKey);
65
68
  this.address = this.keypair.getPublicKey().toSuiAddress();
66
69
 
67
70
  // Store Supplier Cap ID if provided | 儲存 Supplier Cap ID(如果提供)
68
- this.supplierCapId = config.supplierCapId;
69
-
70
- // Prepare coins configuration | 準備 coins 配置
71
- const coins = {
72
- DEEP: {
73
- address: TESTNET_COINS.DEEP.address,
74
- type: TESTNET_COINS.DEEP.type,
75
- scalar: TESTNET_COINS.DEEP.scalar,
76
- },
77
- SUI: {
78
- address: TESTNET_COINS.SUI.address,
79
- type: TESTNET_COINS.SUI.type,
80
- scalar: TESTNET_COINS.SUI.scalar,
81
- },
82
- DBUSDC: {
83
- address: TESTNET_COINS.DBUSDC.address,
84
- type: TESTNET_COINS.DBUSDC.type,
85
- scalar: TESTNET_COINS.DBUSDC.scalar,
86
- },
87
- };
88
-
89
- // Prepare pools configuration | 準備 pools 配置
90
- const pools = {
91
- SUI_DBUSDC: {
92
- address: TESTNET_POOLS.SUI_DBUSDC.address,
93
- baseCoin: TESTNET_POOLS.SUI_DBUSDC.baseCoin,
94
- quoteCoin: TESTNET_POOLS.SUI_DBUSDC.quoteCoin,
95
- },
96
- };
97
-
98
- // Prepare margin pools configuration | 準備 margin pools 配置
99
- const marginPools = {
100
- SUI: {
101
- address: TESTNET_MARGIN_POOLS.SUI.address,
102
- type: TESTNET_MARGIN_POOLS.SUI.coinType,
103
- },
104
- DBUSDC: {
105
- address: TESTNET_MARGIN_POOLS.DBUSDC.address,
106
- type: TESTNET_MARGIN_POOLS.DBUSDC.coinType,
107
- },
108
- };
71
+ this.supplierCapId = supplierCapId;
109
72
 
110
73
  // Create DeepBookConfig | 創建 DeepBookConfig
111
- const deepbookConfig = new DeepBookConfig({
112
- address: this.address,
113
- env: config.network,
114
- coins,
115
- pools,
116
- marginPools,
117
- });
74
+ this.dbConfig =
75
+ dbConfig ??
76
+ new DeepBookConfig({
77
+ env: network,
78
+ address: this.address,
79
+ });
118
80
 
119
81
  // Initialize MarginPoolContract | 初始化 MarginPoolContract
120
- this.marginPoolContract = new MarginPoolContract(deepbookConfig);
82
+ this.marginPoolContract = new MarginPoolContract(this.dbConfig);
83
+
84
+ this.supplierCapPackageId = supplierCapPackageId ?? this.dbConfig.MARGIN_PACKAGE_ID;
121
85
  }
122
86
 
123
87
  #parseSecretKey(secretKey: string): Ed25519Keypair {
@@ -129,8 +93,9 @@ export class DeepBookMarginToolkit {
129
93
  return Ed25519Keypair.fromSecretKey(normalizePrivateKey(hexOrBase64ToUint8Array(secretKey)));
130
94
  }
131
95
 
96
+ // @TODO: Handle more than 1 supplier cap in future
132
97
  async #getExistingSupplierCapId() {
133
- const type = `${TESTNET_PACKAGES.MARGIN_INITIAL_PACKAGE_ID}::margin_pool::SupplierCap`;
98
+ const type = `${this.dbConfig.MARGIN_PACKAGE_ID}::margin_pool::SupplierCap`;
134
99
  const resp = await this.suiClient.getOwnedObjects({
135
100
  owner: this.address,
136
101
  filter: {
@@ -184,8 +149,8 @@ export class DeepBookMarginToolkit {
184
149
  // Direct moveCall to get the returned object reference
185
150
  // Based on SDK source: margin_pool::mint_supplier_cap
186
151
  const supplierCap = tx.moveCall({
187
- target: `${TESTNET_PACKAGES.MARGIN_PACKAGE_ID}::margin_pool::mint_supplier_cap`,
188
- arguments: [tx.object(TESTNET_PACKAGES.MARGIN_REGISTRY_ID), tx.object.clock()],
152
+ target: `${this.supplierCapPackageId}::margin_pool::mint_supplier_cap`,
153
+ arguments: [tx.object(this.dbConfig.MARGIN_REGISTRY_ID), tx.object.clock()],
189
154
  });
190
155
 
191
156
  // Transfer the created Supplier Cap to the sender
@@ -198,7 +163,6 @@ export class DeepBookMarginToolkit {
198
163
  showEffects: true,
199
164
  showObjectChanges: true,
200
165
  },
201
- requestType: 'WaitForLocalExecution',
202
166
  });
203
167
 
204
168
  if (result.errors && result.errors.length > 0) {
@@ -233,22 +197,21 @@ export class DeepBookMarginToolkit {
233
197
  tx.setSender(this.address);
234
198
 
235
199
  // Get margin pool configuration
236
- const marginPool = TESTNET_MARGIN_POOLS[coin];
200
+ const marginPool = this.dbConfig.getMarginPool(coin);
201
+ if (!marginPool) {
202
+ throw new Error(`Margin pool configuration not found for coin: ${coin}`);
203
+ }
237
204
 
238
205
  // Use the initialVersion from config as the initial_shared_version
239
206
  // Margin pools are shared objects on Sui
240
207
  tx.moveCall({
241
- target: `${TESTNET_PACKAGES.MARGIN_PACKAGE_ID}::margin_pool::mint_supply_referral`,
208
+ target: `${this.dbConfig.MARGIN_PACKAGE_ID}::margin_pool::mint_supply_referral`,
242
209
  arguments: [
243
- tx.sharedObjectRef({
244
- objectId: marginPool.address,
245
- initialSharedVersion: marginPool.initialVersion,
246
- mutable: true,
247
- }),
248
- tx.object(TESTNET_PACKAGES.MARGIN_REGISTRY_ID),
210
+ tx.object(marginPool.address),
211
+ tx.object(this.dbConfig.MARGIN_REGISTRY_ID),
249
212
  tx.object.clock(),
250
213
  ],
251
- typeArguments: [marginPool.coinType],
214
+ typeArguments: [marginPool.type],
252
215
  });
253
216
 
254
217
  const result = await this.suiClient.signAndExecuteTransaction({
@@ -258,7 +221,6 @@ export class DeepBookMarginToolkit {
258
221
  showEffects: true,
259
222
  showObjectChanges: true,
260
223
  },
261
- requestType: 'WaitForLocalExecution',
262
224
  });
263
225
 
264
226
  if (result.errors && result.errors.length > 0) {
@@ -314,7 +276,6 @@ export class DeepBookMarginToolkit {
314
276
  showEffects: true,
315
277
  showObjectChanges: true,
316
278
  },
317
- requestType: 'WaitForLocalExecution',
318
279
  });
319
280
 
320
281
  if (errors && errors.length > 0) {
@@ -363,7 +324,6 @@ export class DeepBookMarginToolkit {
363
324
  showEffects: true,
364
325
  showObjectChanges: true,
365
326
  },
366
- requestType: 'WaitForLocalExecution',
367
327
  });
368
328
 
369
329
  if (errors && errors.length > 0) {
@@ -399,7 +359,6 @@ export class DeepBookMarginToolkit {
399
359
  showObjectChanges: true,
400
360
  showBalanceChanges: true,
401
361
  },
402
- requestType: 'WaitForLocalExecution',
403
362
  });
404
363
 
405
364
  if (errors && errors.length > 0) {
@@ -440,19 +399,19 @@ export class DeepBookMarginToolkit {
440
399
  if (supplyData && supplyData[0]) {
441
400
  const rawAmount = Buffer.from(supplyData[0]).readBigUInt64LE();
442
401
  // Convert from smallest unit to human-readable | 從最小單位轉換為人類可讀
443
- const scalar = coin === 'SUI' ? TESTNET_COINS.SUI.scalar : TESTNET_COINS.DBUSDC.scalar;
402
+ const scalar = this.dbConfig.getCoin(coin).scalar;
444
403
  userSupplyAmount = Number(rawAmount) / scalar;
445
404
  }
446
405
  }
447
406
 
448
407
  // Query wallet balance | 查詢錢包餘額
449
- const coinType = coin === 'SUI' ? TESTNET_COINS.SUI.type : TESTNET_COINS.DBUSDC.type;
408
+ const coinType = this.dbConfig.getCoin(coin).type;
450
409
  const balance = await this.suiClient.getBalance({
451
410
  owner: this.address,
452
411
  coinType,
453
412
  });
454
413
 
455
- const scalar = coin === 'SUI' ? TESTNET_COINS.SUI.scalar : TESTNET_COINS.DBUSDC.scalar;
414
+ const scalar = this.dbConfig.getCoin(coin).scalar;
456
415
  const walletBalance = Number(balance.totalBalance) / scalar;
457
416
 
458
417
  return {
@@ -2,6 +2,8 @@
2
2
  * Type definitions for DeepBook Margin Toolkit | DeepBook Margin Toolkit 類型定義
3
3
  */
4
4
 
5
+ import { DeepBookConfig } from '@mysten/deepbook-v3';
6
+
5
7
  // ============================================================================
6
8
  // Network & Configuration Types | 網路與配置類型
7
9
  // ============================================================================
@@ -31,6 +33,11 @@ export interface ToolkitConfig {
31
33
 
32
34
  /** Optional: custom fullnode URL | 可選:自訂 fullnode URL */
33
35
  fullnodeUrl?: string;
36
+
37
+ /** Optional: Supplier Cap Object type ID | 可選:Supplier Cap 物件類型 ID */
38
+ supplierCapPackageId?: string;
39
+
40
+ dbConfig?: DeepBookConfig;
34
41
  }
35
42
 
36
43
  /**
@@ -1,258 +0,0 @@
1
- /**
2
- * DeepBook V3 & Margin Testnet Configuration | DeepBook V3 & Margin 測試網配置
3
- * Source | 來源: https://github.com/MystenLabs/ts-sdks/tree/main/packages/deepbook-v3
4
- * Document | 文件: https://docs.google.com/document/d/1UQw2JZ3X3UN4641_WkvqdCehP7jDPgFmgt2KFti6bjs
5
- * Last updated | 最後更新: 2025-11-20
6
- */
7
-
8
- // ============================================================================
9
- // Package IDs
10
- // ============================================================================
11
-
12
- export const TESTNET_PACKAGES = {
13
- DEEPBOOK_PACKAGE_ID: '0xfb28c4cbc6865bd1c897d26aecbe1f8792d1509a20ffec692c800660cbec6982',
14
- REGISTRY_ID: '0x7c256edbda983a2cd6f946655f4bf3f00a41043993781f8674a7046e8c0e11d1',
15
- DEEP_TREASURY_ID: '0x69fffdae0075f8f71f4fa793549c11079266910e8905169845af1f5d00e09dcb',
16
- MARGIN_PACKAGE_ID: '0x7f2d8f15343f210e813595a8798d6197d152061d0a35be938372f4b1cd66f209',
17
- MARGIN_INITIAL_PACKAGE_ID: '0x7f2d8f15343f210e813595a8798d6197d152061d0a35be938372f4b1cd66f209', // VERSION 1 - for original events
18
- MARGIN_REGISTRY_ID: '0x31b9086767e9b5925cb29414ea623a7705b5600d9594d88c17c7a011cb499ab4',
19
- } as const;
20
-
21
- // ============================================================================
22
- // Coin Configuration | 代幣配置
23
- // ============================================================================
24
-
25
- export const TESTNET_COINS = {
26
- DEEP: {
27
- address: '0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8',
28
- type: '0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8::deep::DEEP',
29
- scalar: 1_000_000,
30
- decimals: 6,
31
- },
32
- SUI: {
33
- address: '0x0000000000000000000000000000000000000000000000000000000000000002',
34
- type: '0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI',
35
- scalar: 1_000_000_000,
36
- decimals: 9,
37
- },
38
- DBUSDC: {
39
- address: '0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7',
40
- type: '0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC',
41
- scalar: 1_000_000,
42
- decimals: 6,
43
- },
44
- DBUSDT: {
45
- address: '0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7',
46
- type: '0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDT::DBUSDT',
47
- scalar: 1_000_000,
48
- decimals: 6,
49
- },
50
- WAL: {
51
- address: '0x9ef7676a9f81937a52ae4b2af8d511a28a0b080477c0c2db40b0ab8882240d76',
52
- type: '0x9ef7676a9f81937a52ae4b2af8d511a28a0b080477c0c2db40b0ab8882240d76::wal::WAL',
53
- scalar: 1_000_000_000,
54
- decimals: 9,
55
- },
56
- } as const;
57
-
58
- // ============================================================================
59
- // Trading Pool Configuration | 交易池配置
60
- // ============================================================================
61
-
62
- export const TESTNET_POOLS = {
63
- DEEP_SUI: {
64
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
65
- address: '0x48c95963e9eac37a316b7ae04a0deb761bcdcc2b67912374d6036e7f0e9bae9f',
66
- baseCoin: 'DEEP',
67
- quoteCoin: 'SUI',
68
- lotSize: 1_000_000, // 1 DEEP (6 decimals) | 1 DEEP(6 位小數)
69
- tickSize: 1_000_000, // 0.001 SUI (9 decimals) | 0.001 SUI(9 位小數)
70
- },
71
- SUI_DBUSDC: {
72
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
73
- address: '0x1c19362ca52b8ffd7a33cee805a67d40f31e6ba303753fd3a4cfdfacea7163a5',
74
- baseCoin: 'SUI',
75
- quoteCoin: 'DBUSDC',
76
- lotSize: 1_000_000, // 0.001 SUI (9 decimals) | 0.001 SUI(9 位小數)
77
- tickSize: 1_000, // 0.001 DBUSDC (6 decimals) | 0.001 DBUSDC(6 位小數)
78
- },
79
- DEEP_DBUSDC: {
80
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
81
- address: '0xe86b991f8632217505fd859445f9803967ac84a9d4a1219065bf191fcb74b622',
82
- baseCoin: 'DEEP',
83
- quoteCoin: 'DBUSDC',
84
- lotSize: 1_000_000, // 1 DEEP (6 decimals) | 1 DEEP(6 位小數)
85
- tickSize: 1_000, // 0.001 DBUSDC (6 decimals) | 0.001 DBUSDC(6 位小數)
86
- },
87
- DBUSDT_DBUSDC: {
88
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
89
- address: '0x83970bb02e3636efdff8c141ab06af5e3c9a22e2f74d7f02a9c3430d0d10c1ca',
90
- baseCoin: 'DBUSDT',
91
- quoteCoin: 'DBUSDC',
92
- lotSize: 1_000_000, // 1 DBUSDT (6 decimals) | 1 DBUSDT(6 位小數)
93
- tickSize: 1_000, // 0.001 DBUSDC (6 decimals) | 0.001 DBUSDC(6 位小數)
94
- },
95
- WAL_DBUSDC: {
96
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
97
- address: '0xeb524b6aea0ec4b494878582e0b78924208339d360b62aec4a8ecd4031520dbb',
98
- baseCoin: 'WAL',
99
- quoteCoin: 'DBUSDC',
100
- lotSize: 1_000_000, // 0.001 WAL (9 decimals) | 0.001 WAL(9 位小數)
101
- tickSize: 1_000, // 0.001 DBUSDC (6 decimals) | 0.001 DBUSDC(6 位小數)
102
- },
103
- WAL_SUI: {
104
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
105
- address: '0x8c1c1b186c4fddab1ebd53e0895a36c1d1b3b9a77cd34e607bef49a38af0150a',
106
- baseCoin: 'WAL',
107
- quoteCoin: 'SUI',
108
- lotSize: 1_000_000, // 0.001 WAL (9 decimals) | 0.001 WAL(9 位小數)
109
- tickSize: 1_000_000, // 0.001 SUI (9 decimals) | 0.001 SUI(9 位小數)
110
- },
111
- } as const;
112
-
113
- // ============================================================================
114
- // Margin Pool Configuration | Margin 池配置
115
- // ============================================================================
116
-
117
- export const TESTNET_MARGIN_POOLS = {
118
- SUI: {
119
- // Actual margin pool address on testnet (updated 2025-11-20) | 實際存在於 testnet 上的 Margin Pool 地址(2025-11-20 更新)
120
- address: '0xe620d6a5390e57e88baff18af89383130d4210eb496a024edcd62f270a655af7',
121
- coinType: '0x2::sui::SUI',
122
- initialVersion: 658877881,
123
- supplyCap: 1000000, // 供應上限 | Supply cap
124
- maxUtilizationRate: 0.9, // 最大使用率 | Max utilization rate
125
- referralSpread: 0.1, // Referral spread
126
- minBorrow: 0.1, // 最小借款額 | Min borrow amount
127
- },
128
- DBUSDC: {
129
- // Actual margin pool address on testnet (updated 2025-11-20) | 實際存在於 testnet 上的 Margin Pool 地址(2025-11-20 更新)
130
- address: '0xfd0dc290a120ad6c534507614d4dc0b2e78baab649c35bfacbaec2ce18140b69',
131
- coinType: '0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC',
132
- initialVersion: 658877215,
133
- supplyCap: 1000000, // 供應上限 | Supply cap
134
- maxUtilizationRate: 0.95, // 最大使用率 | Max utilization rate
135
- referralSpread: 0.1, // Referral spread
136
- minBorrow: 0.1, // 最小借款額 | Min borrow amount
137
- },
138
- } as const;
139
-
140
- // ============================================================================
141
- // Risk Parameters Configuration | 風險參數配置
142
- // ============================================================================
143
-
144
- /**
145
- * Risk parameters for trading pairs | 交易對的風險參數
146
- */
147
- export const TESTNET_RISK_PARAMETERS = {
148
- SUI_DBUSDC: {
149
- minWithdrawRiskRatio: 2, // 最小提款風險比率 | Min withdraw risk ratio
150
- minBorrowRiskRatio: 1.25, // 最小借款風險比率 | Min borrow risk ratio
151
- liquidationRiskRatio: 1.1, // 清算風險比率 | Liquidation risk ratio
152
- targetLiquidationRiskRatio: 1.25, // 目標清算風險比率 | Target liquidation risk ratio
153
- userLiquidationReward: 0.03, // 用戶清算獎勵 | User liquidation reward (3%)
154
- poolLiquidationReward: 0.02, // 池子清算獎勵 | Pool liquidation reward (2%)
155
- },
156
- } as const;
157
-
158
- // ============================================================================
159
- // Pyth Oracle Configuration | Pyth Oracle 配置
160
- // ============================================================================
161
-
162
- export const TESTNET_PYTH = {
163
- pythStateId: '0x243759059f4c3111179da5878c12f68d612c21a8d54d85edc86164bb18be1c7c',
164
- wormholeStateId: '0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790',
165
- } as const;
166
-
167
- // ============================================================================
168
- // Pool Keys (SDK shorthand) | Pool Keys(SDK 使用的簡寫)
169
- // ============================================================================
170
-
171
- export const TESTNET_POOL_KEYS = [
172
- 'DEEP_SUI',
173
- 'SUI_DBUSDC',
174
- 'DEEP_DBUSDC',
175
- 'DBUSDT_DBUSDC',
176
- 'WAL_DBUSDC',
177
- 'WAL_SUI',
178
- ] as const;
179
-
180
- export type TestnetPoolKey = (typeof TESTNET_POOL_KEYS)[number];
181
-
182
- // ============================================================================
183
- // Margin Pool Contract Parameter Keys | Margin Pool 合約參數鍵
184
- // ============================================================================
185
-
186
- export const MARGIN_POOL_PARAM_KEYS = [
187
- 'supplyCap',
188
- 'maxUtilizationRate',
189
- 'protocolSpread',
190
- 'minBorrow',
191
- 'interestRate',
192
- 'totalSupply',
193
- 'supplyShares',
194
- 'totalBorrow',
195
- 'borrowShares',
196
- 'lastUpdateTimestamp',
197
- ] as const;
198
-
199
- export const MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS = [
200
- 'userSupplyShares',
201
- 'userSupplyAmount',
202
- ] as const;
203
-
204
- export const MARGIN_POOL_PARAM_KEY_STRUCT_MAP = {
205
- supplyCap: 'U64',
206
- maxUtilizationRate: 'U64',
207
- protocolSpread: 'U64',
208
- minBorrow: 'U64',
209
- interestRate: 'U64',
210
- totalSupply: 'U64',
211
- supplyShares: 'U64',
212
- totalBorrow: 'U64',
213
- borrowShares: 'U64',
214
- lastUpdateTimestamp: 'U64',
215
- userSupplyShares: 'U64',
216
- userSupplyAmount: 'U64',
217
- } as Record<
218
- (typeof MARGIN_POOL_PARAM_KEYS)[number] | (typeof MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS)[number],
219
- 'U64'
220
- >;
221
-
222
- // ============================================================================
223
- // Utility Functions | 輔助函數
224
- // ============================================================================
225
-
226
- /**
227
- * Get full pool configuration | 取得 Pool 的完整配置
228
- */
229
- export function getPoolConfig(poolKey: TestnetPoolKey) {
230
- return TESTNET_POOLS[poolKey];
231
- }
232
-
233
- /**
234
- * Get full coin configuration | 取得代幣的完整配置
235
- */
236
- export function getCoinConfig(coinSymbol: keyof typeof TESTNET_COINS) {
237
- return TESTNET_COINS[coinSymbol];
238
- }
239
-
240
- /**
241
- * Format coin amount (convert from smallest unit to human-readable) | 格式化代幣數量(從最小單位轉換為人類可讀)
242
- */
243
- export function formatCoinAmount(
244
- amount: number | bigint,
245
- coinSymbol: keyof typeof TESTNET_COINS
246
- ): string {
247
- const config = TESTNET_COINS[coinSymbol];
248
- const value = Number(amount) / config.scalar;
249
- return value.toFixed(config.decimals);
250
- }
251
-
252
- /**
253
- * Parse coin amount (convert from human-readable to smallest unit) | 解析代幣數量(從人類可讀轉換為最小單位)
254
- */
255
- export function parseCoinAmount(amount: number, coinSymbol: keyof typeof TESTNET_COINS): number {
256
- const config = TESTNET_COINS[coinSymbol];
257
- return Math.floor(amount * config.scalar);
258
- }