@scallop-io/scallop-deepbook-kit 0.1.3 → 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,17 +51,16 @@ 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
- midKink: number;
55
58
  highKink: number;
56
59
  baseBorrowApr: number;
57
- midBorrowApr: number;
58
- highBorrowApr: number;
60
+ borrowAprOnHighKink: number;
59
61
  maxBorrowApr: number;
60
- borrowApr: number;
62
+ supplyApr: number;
63
+ utilizationRate: number;
61
64
  };
62
65
  type MarginPoolParams = Record<MarginPoolParamKey | MarginPoolWithSupplierCapParamKey, number> & InterestConfig & {
63
66
  address: string;
@@ -65,12 +68,18 @@ type MarginPoolParams = Record<MarginPoolParamKey | MarginPoolWithSupplierCapPar
65
68
  scalar: number;
66
69
  decimals: number;
67
70
  };
71
+ type DeepBookMarginPoolParams = {
72
+ address?: string;
73
+ suiClient?: SuiClient;
74
+ env?: NetworkType;
75
+ dbConfig?: DeepBookConfig;
76
+ };
68
77
  declare class DeepBookMarginPool {
69
78
  #private;
70
- readonly suiClient: SuiClient;
71
79
  marginPoolContract: MarginPoolContract;
72
80
  dbConfig: DeepBookConfig;
73
- constructor(env?: NetworkType, address?: string, suiClient?: SuiClient, dbConfig?: DeepBookConfig);
81
+ suiClient: SuiClient;
82
+ constructor({ env, address, suiClient, dbConfig, }?: DeepBookMarginPoolParams);
74
83
  get env(): _mysten_deepbook_v3.Environment;
75
84
  getPoolParameters(coinKey: string, supplierCapId?: string, tx?: Transaction): Promise<MarginPoolParams>;
76
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,17 +51,16 @@ 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
- midKink: number;
55
58
  highKink: number;
56
59
  baseBorrowApr: number;
57
- midBorrowApr: number;
58
- highBorrowApr: number;
60
+ borrowAprOnHighKink: number;
59
61
  maxBorrowApr: number;
60
- borrowApr: number;
62
+ supplyApr: number;
63
+ utilizationRate: number;
61
64
  };
62
65
  type MarginPoolParams = Record<MarginPoolParamKey | MarginPoolWithSupplierCapParamKey, number> & InterestConfig & {
63
66
  address: string;
@@ -65,12 +68,18 @@ type MarginPoolParams = Record<MarginPoolParamKey | MarginPoolWithSupplierCapPar
65
68
  scalar: number;
66
69
  decimals: number;
67
70
  };
71
+ type DeepBookMarginPoolParams = {
72
+ address?: string;
73
+ suiClient?: SuiClient;
74
+ env?: NetworkType;
75
+ dbConfig?: DeepBookConfig;
76
+ };
68
77
  declare class DeepBookMarginPool {
69
78
  #private;
70
- readonly suiClient: SuiClient;
71
79
  marginPoolContract: MarginPoolContract;
72
80
  dbConfig: DeepBookConfig;
73
- constructor(env?: NetworkType, address?: string, suiClient?: SuiClient, dbConfig?: DeepBookConfig);
81
+ suiClient: SuiClient;
82
+ constructor({ env, address, suiClient, dbConfig, }?: DeepBookMarginPoolParams);
74
83
  get env(): _mysten_deepbook_v3.Environment;
75
84
  getPoolParameters(coinKey: string, supplierCapId?: string, tx?: Transaction): Promise<MarginPoolParams>;
76
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 D={MARGIN_INITIAL_PACKAGE_ID:"0x7f2d8f15343f210e813595a8798d6197d152061d0a35be938372f4b1cd66f209"},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}},f={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"},DBUSDC:{address:"0xfd0dc290a120ad6c534507614d4dc0b2e78baab649c35bfacbaec2ce18140b69",coinType:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC"}};var P=["supplyCap","maxUtilizationRate","protocolSpread","minBorrow","interestRate","totalSupply","supplyShares","totalBorrow","borrowShares","lastUpdateTimestamp"],b=["userSupplyShares","userSupplyAmount"],B={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=n=>/^0x[0-9a-fA-F]+$|^[0-9a-fA-F]+$/.test(n),L=n=>/^[a-zA-Z0-9+/]+={0,2}$/g.test(n),x=n=>{if(k(n))return bcs$1.fromHex(n);if(L(n))return bcs$1.fromBase64(n);throw new Error("The string is not a valid hex or base64 string.")},_=n=>{if(n.length===cryptography.LEGACY_PRIVATE_KEY_SIZE)return n.slice(0,cryptography.PRIVATE_KEY_SIZE);if(n.length===cryptography.PRIVATE_KEY_SIZE+1&&n[0]===0)return n.slice(1);if(n.length===cryptography.PRIVATE_KEY_SIZE)return n;throw new Error("invalid secret key")};var w=class{suiClient;keypair;address;marginPoolContract;supplierCapId;constructor(r){let a=r.fullnodeUrl??client.getFullnodeUrl(r.network);this.suiClient=new client.SuiClient({url:a}),this.keypair=this.#r(r.privateKey),this.address=this.keypair.getPublicKey().toSuiAddress(),this.supplierCapId=r.supplierCapId;let e={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}},t={SUI_DBUSDC:{address:f.SUI_DBUSDC.address,baseCoin:f.SUI_DBUSDC.baseCoin,quoteCoin:f.SUI_DBUSDC.quoteCoin}},i={SUI:{address:d.SUI.address,type:d.SUI.coinType},DBUSDC:{address:d.DBUSDC.address,type:d.DBUSDC.coinType}},o=new deepbookV3.DeepBookConfig({address:this.address,env:r.network,coins:e,pools:t,marginPools:i});this.marginPoolContract=new deepbookV3.MarginPoolContract(o);}#r(r){if(r.startsWith(cryptography.SUI_PRIVATE_KEY_PREFIX)){let{secretKey:a}=cryptography.decodeSuiPrivateKey(r);return ed25519.Ed25519Keypair.fromSecretKey(_(a))}return ed25519.Ed25519Keypair.fromSecretKey(_(x(r)))}async#e(){let r=`${D.MARGIN_INITIAL_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.#e();if(r)return this.supplierCapId=r,r;let a=await this.createSupplierCap();if(!a)throw new Error("Failed to create Supplier Cap");return this.supplierCapId=a,a}async createSupplierCap(){try{let r=new transactions.Transaction,a=this.marginPoolContract.mintSupplierCap()(r);r.transferObjects([a],this.address);let e=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:r,options:{showEffects:!0,showObjectChanges:!0}});if(e.errors&&e.errors.length>0)throw new Error(`Transaction failed with errors: ${e.errors.map(t=>t.toString()).join(", ")}`);if(e.objectChanges){for(let t of e.objectChanges)if(t.type==="created"&&t.objectType.includes("SupplierCap"))return t.objectId}return null}catch(r){throw new Error(`Failed to create Supplier Cap: ${r.message||r}`)}}async createSupplyReferral(r){try{let a=new transactions.Transaction;this.marginPoolContract.mintSupplyReferral(r)(a);let e=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:a,options:{showEffects:!0,showObjectChanges:!0}});if(e.errors&&e.errors.length>0)throw new Error(`Transaction failed with errors: ${e.errors.map(t=>t.toString()).join(", ")}`);if(e.objectChanges){for(let t of e.objectChanges)if(t.type==="created"&&t.objectType.includes("SupplyReferral"))return t.objectId}return null}catch(a){throw new Error(`Failed to create Supply Referral: ${a.message||a}`)}}async supplyToMarginPool(r,a,e){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let t=new transactions.Transaction;t.setSender(this.address);let i=t.object(this.supplierCapId);t.add(this.marginPoolContract.supplyToMarginPool(r,i,a,e));let{errors:o}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:t,options:{showEffects:!0,showObjectChanges:!0}});if(o&&o.length>0)throw new Error(`Transaction failed with errors: ${o.map(s=>s.toString()).join(", ")}`);return !0}catch(t){throw new Error(`Failed to supply to margin pool: ${t.message||t}`)}}async withdrawFromMarginPool(r,a){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let e=new transactions.Transaction,t=e.object(this.supplierCapId),o=this.marginPoolContract.withdrawFromMarginPool(r,t,a)(e);e.transferObjects([o],this.address);let{errors:s}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:e,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(e){throw new Error(`Failed to withdraw from margin pool: ${e.message||e}`)}}async withdrawReferralFees(r,a){try{let e=new transactions.Transaction;e.add(this.marginPoolContract.withdrawReferralFees(r,a));let{errors:t}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:e,options:{showEffects:!0,showObjectChanges:!0,showBalanceChanges:!0}});if(t&&t.length>0)throw new Error(`Transaction failed with errors: ${t.map(i=>i.toString()).join(", ")}`);return !0}catch(e){throw new Error(`Failed to withdraw referral fees: ${e.message||e}`)}}async getBalance(r){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let a=new transactions.Transaction;a.add(this.marginPoolContract.userSupplyAmount(r,this.supplierCapId));let e=await this.suiClient.devInspectTransactionBlock({sender:this.address,transactionBlock:a}),t=0;if(e&&e.results&&e.results[0]&&e.results[0].returnValues){let p=e.results[0].returnValues[0];if(p&&p[0]){let m=Buffer.from(p[0]).readBigUInt64LE(),C=r==="SUI"?c.SUI.scalar:c.DBUSDC.scalar;t=Number(m)/C;}}let i=r==="SUI"?c.SUI.type:c.DBUSDC.type,o=await this.suiClient.getBalance({owner:this.address,coinType:i}),s=r==="SUI"?c.SUI.scalar:c.DBUSDC.scalar,l=Number(o.totalBalance)/s;return {userSupplyAmount:t,walletBalance:l}}catch(a){throw new Error(`Failed to get balance: ${a.message||a}`)}}getSupplierCapId(){return this.supplierCapId}getAddress(){return this.address}};var X=new Set(b),J=n=>X.has(n),I=class{constructor(r="testnet",a="",e=new client.SuiClient({url:client.getFullnodeUrl(r)}),t){this.suiClient=e;this.dbConfig=t??new deepbookV3.DeepBookConfig({env:r,address:a,coins:c,pools:f,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}#r(r,a,e,t){if(J(a)){let i=this.marginPoolContract[a];if(t==null)throw new Error(`supplierCap is required for '${a}'.`);r.add(i(e,t));}else {let i=this.marginPoolContract[a];r.add(i(e));}}#e(r,a){let e=r.results;return e?a.reduce((t,i,o)=>{let s=e[o]?.returnValues?.[0]?.[0];if(!s)return t;let l=bcs.bcs[B[i]];return t[i]=l.parse(new Uint8Array(s)),t},{}):{}}#t(r,a){let e=this.dbConfig.getCoin(a),t={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,midKink:0,highKink:0,baseBorrowApr:0,midBorrowApr:0,highBorrowApr:0,maxBorrowApr:0,borrowApr:0,...e};if(!e)return t;let i=new Set(["interestRate","maxUtilizationRate","protocolSpread"]);for(let[o,s]of Object.entries(r))o==="lastUpdateTimestamp"?t[o]=Number(s):i.has(o)?t[o]=new bignumber_js.BigNumber(s).dividedBy(deepbookV3.FLOAT_SCALAR).toNumber():t[o]=new bignumber_js.BigNumber(s).dividedBy(e.scalar).toNumber();return t}#a(r,a){let e=BigInt(a.base_rate),t=BigInt(a.base_slope),i=BigInt(a.excess_slope),o=BigInt(a.optimal_utilization),s=r<0n?0n:r>BigInt(deepbookV3.FLOAT_SCALAR)?BigInt(deepbookV3.FLOAT_SCALAR):BigInt(r);return s<=o?e+t*s/o:e+t+i*(s-o)/(BigInt(deepbookV3.FLOAT_SCALAR)-o)}#o(r,a,e){let t=BigInt(r.base_rate),i=BigInt(r.base_slope),o=BigInt(r.excess_slope),s=BigInt(r.optimal_utilization),l=BigInt(a.max_utilization_rate),p=s,m=l,C=t,E=t+i,T=t+i+o*(l-s)/(1000000000n-s),A=t+i+o,U=this.#a(BigInt(bignumber_js.BigNumber(e.total_borrow).dividedBy(e.total_supply).shiftedBy(9).decimalPlaces(0).toString()),r),u=M=>bignumber_js.BigNumber(M).dividedBy(deepbookV3.FLOAT_SCALAR).toNumber();return {raw:{midKink:p,highKink:m,baseBorrowApr:C,midBorrowApr:E,highBorrowApr:T,maxBorrowApr:A,borrowApr:U},normalized:{midKink:u(p),highKink:u(m),baseBorrowApr:u(C),midBorrowApr:u(E),highBorrowApr:u(T),maxBorrowApr:u(A),borrowApr:u(U)}}}async#i(r){let{address:a}=this.dbConfig.getMarginPool(r),t=((await this.suiClient.getObject({id:a,options:{showContent:true}})).data?.content).fields,i=t.config.fields,o=i.interest_config.fields,s=i.margin_pool_config.fields,l=t.state.fields,{normalized:p}=this.#o(o,s,l);return p}async getPoolParameters(r,a,e=new transactions.Transaction,t=true){if(P.forEach(p=>this.#r(e,p,r)),a&&b.forEach(p=>this.#r(e,p,r,a)),!t)return e;let i=[...P,...b],o=await this.suiClient.devInspectTransactionBlock({transactionBlock:e,sender:this.dbConfig.address}),s=await this.#i(r);return {...this.#t(this.#e(o,i),r),...s}}};exports.DeepBookMarginPool=I;exports.DeepBookMarginToolkit=w;
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 D={MARGIN_INITIAL_PACKAGE_ID:"0x7f2d8f15343f210e813595a8798d6197d152061d0a35be938372f4b1cd66f209"},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}},f={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"},DBUSDC:{address:"0xfd0dc290a120ad6c534507614d4dc0b2e78baab649c35bfacbaec2ce18140b69",coinType:"0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC"}};var P=["supplyCap","maxUtilizationRate","protocolSpread","minBorrow","interestRate","totalSupply","supplyShares","totalBorrow","borrowShares","lastUpdateTimestamp"],b=["userSupplyShares","userSupplyAmount"],B={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=n=>/^0x[0-9a-fA-F]+$|^[0-9a-fA-F]+$/.test(n),L=n=>/^[a-zA-Z0-9+/]+={0,2}$/g.test(n),x=n=>{if(k(n))return fromHex(n);if(L(n))return fromBase64(n);throw new Error("The string is not a valid hex or base64 string.")},_=n=>{if(n.length===LEGACY_PRIVATE_KEY_SIZE)return n.slice(0,PRIVATE_KEY_SIZE);if(n.length===PRIVATE_KEY_SIZE+1&&n[0]===0)return n.slice(1);if(n.length===PRIVATE_KEY_SIZE)return n;throw new Error("invalid secret key")};var w=class{suiClient;keypair;address;marginPoolContract;supplierCapId;constructor(r){let a=r.fullnodeUrl??getFullnodeUrl(r.network);this.suiClient=new SuiClient({url:a}),this.keypair=this.#r(r.privateKey),this.address=this.keypair.getPublicKey().toSuiAddress(),this.supplierCapId=r.supplierCapId;let e={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}},t={SUI_DBUSDC:{address:f.SUI_DBUSDC.address,baseCoin:f.SUI_DBUSDC.baseCoin,quoteCoin:f.SUI_DBUSDC.quoteCoin}},i={SUI:{address:d.SUI.address,type:d.SUI.coinType},DBUSDC:{address:d.DBUSDC.address,type:d.DBUSDC.coinType}},o=new DeepBookConfig({address:this.address,env:r.network,coins:e,pools:t,marginPools:i});this.marginPoolContract=new MarginPoolContract(o);}#r(r){if(r.startsWith(SUI_PRIVATE_KEY_PREFIX)){let{secretKey:a}=decodeSuiPrivateKey(r);return Ed25519Keypair.fromSecretKey(_(a))}return Ed25519Keypair.fromSecretKey(_(x(r)))}async#e(){let r=`${D.MARGIN_INITIAL_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.#e();if(r)return this.supplierCapId=r,r;let a=await this.createSupplierCap();if(!a)throw new Error("Failed to create Supplier Cap");return this.supplierCapId=a,a}async createSupplierCap(){try{let r=new Transaction,a=this.marginPoolContract.mintSupplierCap()(r);r.transferObjects([a],this.address);let e=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:r,options:{showEffects:!0,showObjectChanges:!0}});if(e.errors&&e.errors.length>0)throw new Error(`Transaction failed with errors: ${e.errors.map(t=>t.toString()).join(", ")}`);if(e.objectChanges){for(let t of e.objectChanges)if(t.type==="created"&&t.objectType.includes("SupplierCap"))return t.objectId}return null}catch(r){throw new Error(`Failed to create Supplier Cap: ${r.message||r}`)}}async createSupplyReferral(r){try{let a=new Transaction;this.marginPoolContract.mintSupplyReferral(r)(a);let e=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:a,options:{showEffects:!0,showObjectChanges:!0}});if(e.errors&&e.errors.length>0)throw new Error(`Transaction failed with errors: ${e.errors.map(t=>t.toString()).join(", ")}`);if(e.objectChanges){for(let t of e.objectChanges)if(t.type==="created"&&t.objectType.includes("SupplyReferral"))return t.objectId}return null}catch(a){throw new Error(`Failed to create Supply Referral: ${a.message||a}`)}}async supplyToMarginPool(r,a,e){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let t=new Transaction;t.setSender(this.address);let i=t.object(this.supplierCapId);t.add(this.marginPoolContract.supplyToMarginPool(r,i,a,e));let{errors:o}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:t,options:{showEffects:!0,showObjectChanges:!0}});if(o&&o.length>0)throw new Error(`Transaction failed with errors: ${o.map(s=>s.toString()).join(", ")}`);return !0}catch(t){throw new Error(`Failed to supply to margin pool: ${t.message||t}`)}}async withdrawFromMarginPool(r,a){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let e=new Transaction,t=e.object(this.supplierCapId),o=this.marginPoolContract.withdrawFromMarginPool(r,t,a)(e);e.transferObjects([o],this.address);let{errors:s}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:e,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(e){throw new Error(`Failed to withdraw from margin pool: ${e.message||e}`)}}async withdrawReferralFees(r,a){try{let e=new Transaction;e.add(this.marginPoolContract.withdrawReferralFees(r,a));let{errors:t}=await this.suiClient.signAndExecuteTransaction({signer:this.keypair,transaction:e,options:{showEffects:!0,showObjectChanges:!0,showBalanceChanges:!0}});if(t&&t.length>0)throw new Error(`Transaction failed with errors: ${t.map(i=>i.toString()).join(", ")}`);return !0}catch(e){throw new Error(`Failed to withdraw referral fees: ${e.message||e}`)}}async getBalance(r){try{if(!this.supplierCapId)throw new Error("Supplier Cap not initialized. Call initialize() first.");let a=new Transaction;a.add(this.marginPoolContract.userSupplyAmount(r,this.supplierCapId));let e=await this.suiClient.devInspectTransactionBlock({sender:this.address,transactionBlock:a}),t=0;if(e&&e.results&&e.results[0]&&e.results[0].returnValues){let p=e.results[0].returnValues[0];if(p&&p[0]){let m=Buffer.from(p[0]).readBigUInt64LE(),C=r==="SUI"?c.SUI.scalar:c.DBUSDC.scalar;t=Number(m)/C;}}let i=r==="SUI"?c.SUI.type:c.DBUSDC.type,o=await this.suiClient.getBalance({owner:this.address,coinType:i}),s=r==="SUI"?c.SUI.scalar:c.DBUSDC.scalar,l=Number(o.totalBalance)/s;return {userSupplyAmount:t,walletBalance:l}}catch(a){throw new Error(`Failed to get balance: ${a.message||a}`)}}getSupplierCapId(){return this.supplierCapId}getAddress(){return this.address}};var X=new Set(b),J=n=>X.has(n),I=class{constructor(r="testnet",a="",e=new SuiClient({url:getFullnodeUrl(r)}),t){this.suiClient=e;this.dbConfig=t??new DeepBookConfig({env:r,address:a,coins:c,pools:f,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}#r(r,a,e,t){if(J(a)){let i=this.marginPoolContract[a];if(t==null)throw new Error(`supplierCap is required for '${a}'.`);r.add(i(e,t));}else {let i=this.marginPoolContract[a];r.add(i(e));}}#e(r,a){let e=r.results;return e?a.reduce((t,i,o)=>{let s=e[o]?.returnValues?.[0]?.[0];if(!s)return t;let l=bcs[B[i]];return t[i]=l.parse(new Uint8Array(s)),t},{}):{}}#t(r,a){let e=this.dbConfig.getCoin(a),t={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,midKink:0,highKink:0,baseBorrowApr:0,midBorrowApr:0,highBorrowApr:0,maxBorrowApr:0,borrowApr:0,...e};if(!e)return t;let i=new Set(["interestRate","maxUtilizationRate","protocolSpread"]);for(let[o,s]of Object.entries(r))o==="lastUpdateTimestamp"?t[o]=Number(s):i.has(o)?t[o]=new BigNumber(s).dividedBy(FLOAT_SCALAR).toNumber():t[o]=new BigNumber(s).dividedBy(e.scalar).toNumber();return t}#a(r,a){let e=BigInt(a.base_rate),t=BigInt(a.base_slope),i=BigInt(a.excess_slope),o=BigInt(a.optimal_utilization),s=r<0n?0n:r>BigInt(FLOAT_SCALAR)?BigInt(FLOAT_SCALAR):BigInt(r);return s<=o?e+t*s/o:e+t+i*(s-o)/(BigInt(FLOAT_SCALAR)-o)}#o(r,a,e){let t=BigInt(r.base_rate),i=BigInt(r.base_slope),o=BigInt(r.excess_slope),s=BigInt(r.optimal_utilization),l=BigInt(a.max_utilization_rate),p=s,m=l,C=t,E=t+i,T=t+i+o*(l-s)/(1000000000n-s),A=t+i+o,U=this.#a(BigInt(BigNumber(e.total_borrow).dividedBy(e.total_supply).shiftedBy(9).decimalPlaces(0).toString()),r),u=M=>BigNumber(M).dividedBy(FLOAT_SCALAR).toNumber();return {raw:{midKink:p,highKink:m,baseBorrowApr:C,midBorrowApr:E,highBorrowApr:T,maxBorrowApr:A,borrowApr:U},normalized:{midKink:u(p),highKink:u(m),baseBorrowApr:u(C),midBorrowApr:u(E),highBorrowApr:u(T),maxBorrowApr:u(A),borrowApr:u(U)}}}async#i(r){let{address:a}=this.dbConfig.getMarginPool(r),t=((await this.suiClient.getObject({id:a,options:{showContent:true}})).data?.content).fields,i=t.config.fields,o=i.interest_config.fields,s=i.margin_pool_config.fields,l=t.state.fields,{normalized:p}=this.#o(o,s,l);return p}async getPoolParameters(r,a,e=new Transaction,t=true){if(P.forEach(p=>this.#r(e,p,r)),a&&b.forEach(p=>this.#r(e,p,r,a)),!t)return e;let i=[...P,...b],o=await this.suiClient.devInspectTransactionBlock({transactionBlock:e,sender:this.dbConfig.address}),s=await this.#i(r);return {...this.#t(this.#e(o,i),r),...s}}};export{I as DeepBookMarginPool,w 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.3",
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,26 +1,25 @@
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';
10
+ MarginPoolParamKey,
11
+ MarginPoolWithSupplierCapParamKey,
12
+ } from '../margin-pool-config';
13
+ import { mul, normalize } from '../utils/math';
11
14
  import { NetworkType } from './types';
12
- import { BigNumber } from 'bignumber.js';
13
15
 
14
- type MarginPoolParamKey = (typeof MARGIN_POOL_PARAM_KEYS)[number];
15
- type MarginPoolWithSupplierCapParamKey = (typeof MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS)[number];
16
16
  type InterestConfig = {
17
- midKink: number;
18
17
  highKink: number;
19
18
  baseBorrowApr: number;
20
- midBorrowApr: number;
21
- highBorrowApr: number;
19
+ borrowAprOnHighKink: number;
22
20
  maxBorrowApr: number;
23
- borrowApr: number;
21
+ supplyApr: number;
22
+ utilizationRate: number;
24
23
  };
25
24
  export type MarginPoolParams = Record<
26
25
  MarginPoolParamKey | MarginPoolWithSupplierCapParamKey,
@@ -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,14 +208,13 @@ export class DeepBookMarginPool {
207
208
  lastUpdateTimestamp: 0,
208
209
  userSupplyShares: 0,
209
210
  userSupplyAmount: 0,
210
- decimals: 0,
211
- midKink: 0,
211
+ decimals: this.dbConfig.getCoin(coinKey).scalar.toString().length - 1,
212
212
  highKink: 0,
213
213
  baseBorrowApr: 0,
214
- midBorrowApr: 0,
215
- highBorrowApr: 0,
214
+ borrowAprOnHighKink: 0,
216
215
  maxBorrowApr: 0,
217
- borrowApr: 0,
216
+ supplyApr: 0,
217
+ utilizationRate: 0,
218
218
  ...coin,
219
219
  };
220
220
 
@@ -242,94 +242,76 @@ export class DeepBookMarginPool {
242
242
  return formatted;
243
243
  }
244
244
 
245
- #getCurrentBorrowApr(utilizationRate: bigint, interestConfig: RawInterestConfig) {
246
- const baseRate = BigInt(interestConfig.base_rate); // B
247
- const baseSlope = BigInt(interestConfig.base_slope); // S
248
- const excessSlope = BigInt(interestConfig.excess_slope); // E
249
- const optimalUtil = BigInt(interestConfig.optimal_utilization); // Uopt
250
-
251
- // clamp U to [0, 1e9]
252
- const U =
253
- utilizationRate < 0n
254
- ? 0n
255
- : utilizationRate > BigInt(FLOAT_SCALAR)
256
- ? BigInt(FLOAT_SCALAR)
257
- : BigInt(utilizationRate);
258
-
259
- if (U <= optimalUtil) {
260
- // r(U) = B + S * U / Uopt
261
- return baseRate + (baseSlope * U) / optimalUtil;
245
+ /**
246
+ * Compute the borrow APR based on utilization and interest config.
247
+ *
248
+ * if (U < optimal) {
249
+ * r(U) = base_rate + mul(U, base_slope)
250
+ * } else {
251
+ * r(U) = base_rate + mul(optimal, base_slope) + mul(U - optimal, excess_slope)
252
+ * }
253
+ */
254
+ #computeBorrowAprAtUtil(
255
+ util: bigint, // 1e9-scaled utilization
256
+ cfg: RawInterestConfig
257
+ ): bigint {
258
+ const baseRate = BigInt(cfg.base_rate);
259
+ const baseSlope = BigInt(cfg.base_slope);
260
+ const excessSlope = BigInt(cfg.excess_slope);
261
+ const optimalUtil = BigInt(cfg.optimal_utilization);
262
+
263
+ if (util < optimalUtil) {
264
+ return baseRate + mul(util, baseSlope);
262
265
  }
263
266
 
264
- // r(U) = B + S + E * (U - Uopt) / (1 - Uopt)
265
- return (
266
- baseRate +
267
- baseSlope +
268
- (excessSlope * (U - optimalUtil)) / (BigInt(FLOAT_SCALAR) - optimalUtil)
269
- );
267
+ return baseRate + mul(optimalUtil, baseSlope) + mul(util - optimalUtil, excessSlope);
270
268
  }
271
269
 
272
- #calculateKinksAndBorrowApr(
270
+ #calculateKinksAndRate(
273
271
  interestConfig: RawInterestConfig,
274
272
  marginPoolConfig: RawMarginPoolConfig,
275
- state: RawStatePoolConfig
273
+ state: RawStatePoolConfig,
274
+ borrowApr: number
276
275
  ) {
277
- const baseRate = BigInt(interestConfig.base_rate);
278
- const baseSlope = BigInt(interestConfig.base_slope);
279
- const excessSlope = BigInt(interestConfig.excess_slope);
280
- const optimalUtil = BigInt(interestConfig.optimal_utilization);
281
- const maxUtil = BigInt(marginPoolConfig.max_utilization_rate);
282
-
283
- // Kinks (still 1e9-scaled)
284
- const midKink = optimalUtil;
285
- const highKink = maxUtil;
286
-
287
- // APRs in 1e9 scale
288
- const baseBorrowApr = baseRate;
289
- const midBorrowApr = baseRate + baseSlope;
290
-
291
- // highBorrowApr at U = maxUtil
292
- // r(U) = base + slope + excess * (U - optimal) / (1 - optimal)
293
- const highBorrowApr =
294
- baseRate +
295
- baseSlope +
296
- (excessSlope * (maxUtil - optimalUtil)) / (1_000_000_000n - optimalUtil);
297
-
298
- // maxBorrowApr at U = 1.0 (i.e. 1e9)
299
- const maxBorrowApr = baseRate + baseSlope + excessSlope;
300
- const currentBorrowApr = this.#getCurrentBorrowApr(
301
- BigInt(
302
- BigNumber(state.total_borrow)
303
- .dividedBy(state.total_supply)
304
- .shiftedBy(9)
305
- .decimalPlaces(0)
306
- .toString()
307
- ),
308
- interestConfig
276
+ const highKink = BigInt(interestConfig.optimal_utilization); // v0
277
+ const maxKink = BigInt(marginPoolConfig.max_utilization_rate); // U_max
278
+
279
+ // const midBorrowApr = this.#computeBorrowAprAtUtil(midKink, interestConfig);
280
+ const borrowAprOnHighKink = this.#computeBorrowAprAtUtil(highKink, interestConfig);
281
+ const maxBorrowApr = this.#computeBorrowAprAtUtil(maxKink, interestConfig);
282
+
283
+ const utilizationRate = BigInt(
284
+ BigNumber(state.total_borrow)
285
+ .dividedBy(state.total_supply)
286
+ .shiftedBy(9)
287
+ .decimalPlaces(0)
288
+ .toString()
289
+ );
290
+ const supplyApr = mul(
291
+ mul(BigInt(borrowApr), utilizationRate),
292
+ BigInt(FLOAT_SCALAR) - BigInt(marginPoolConfig.protocol_spread)
309
293
  );
310
-
311
- const normalize = (value: bigint) => BigNumber(value).dividedBy(FLOAT_SCALAR).toNumber();
312
294
 
313
295
  return {
314
- // raw 1e9-scaled values (BigInt)
296
+ // raw 1e9-scaled values
315
297
  raw: {
316
- midKink,
317
- highKink,
318
- baseBorrowApr,
319
- midBorrowApr,
320
- highBorrowApr,
321
- maxBorrowApr,
322
- borrowApr: currentBorrowApr,
298
+ baseBorrowApr: interestConfig.base_rate,
299
+ highKink, // utilization at kink2
300
+ borrowAprOnHighKink, // APR at highKink
301
+ maxBorrowApr, // APR at U = 1.0
302
+ supplyApr,
303
+ utilizationRate,
323
304
  },
324
- // convenience: normalized numbers (e.g. 0.125 = 12.5%)
305
+ // convenience normalized numbers
325
306
  normalized: {
326
- midKink: normalize(midKink),
307
+ baseBorrowApr: normalize(BigInt(interestConfig.base_rate)),
308
+ // midKink: normalize(midKink),
327
309
  highKink: normalize(highKink),
328
- baseBorrowApr: normalize(baseBorrowApr),
329
- midBorrowApr: normalize(midBorrowApr),
330
- highBorrowApr: normalize(highBorrowApr),
310
+ // midBorrowApr: normalize(midBorrowApr),
311
+ borrowAprOnHighKink: normalize(borrowAprOnHighKink),
331
312
  maxBorrowApr: normalize(maxBorrowApr),
332
- borrowApr: normalize(currentBorrowApr),
313
+ supplyApr: normalize(supplyApr),
314
+ utilizationRate: normalize(utilizationRate),
333
315
  },
334
316
  };
335
317
  }
@@ -339,7 +321,7 @@ export class DeepBookMarginPool {
339
321
  * @param coinKey - Asset key.
340
322
  * @returns Interest configuration data including kinks and APRs.
341
323
  */
342
- async #getInterestConfig(coinKey: string) {
324
+ async #getInterestConfig(coinKey: string, interestRate: number) {
343
325
  const { address } = this.dbConfig.getMarginPool(coinKey);
344
326
  const response = await this.suiClient.getObject({
345
327
  id: address,
@@ -353,10 +335,11 @@ export class DeepBookMarginPool {
353
335
  const interestConfig = config.interest_config.fields as RawInterestConfig;
354
336
  const marginPoolConfig = config.margin_pool_config.fields as RawMarginPoolConfig;
355
337
  const statePoolConfig = fields.state.fields as RawStatePoolConfig;
356
- const { normalized } = this.#calculateKinksAndBorrowApr(
338
+ const { normalized } = this.#calculateKinksAndRate(
357
339
  interestConfig,
358
340
  marginPoolConfig,
359
- statePoolConfig
341
+ statePoolConfig,
342
+ interestRate
360
343
  );
361
344
  return normalized;
362
345
  }
@@ -426,11 +409,14 @@ export class DeepBookMarginPool {
426
409
  sender: this.dbConfig.address,
427
410
  });
428
411
 
429
- const interestData = await this.#getInterestConfig(coinKey);
430
412
  const formattedResult = this.#formatResult(
431
413
  this.#parseInspectResultToBcsStructs(inspectResult, allKeys),
432
414
  coinKey
433
415
  );
416
+ const interestData = await this.#getInterestConfig(
417
+ coinKey,
418
+ formattedResult.interestRate * FLOAT_SCALAR
419
+ );
434
420
 
435
421
  return {
436
422
  ...formattedResult,
@@ -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: {
@@ -179,10 +144,17 @@ export class DeepBookMarginToolkit {
179
144
  async createSupplierCap(): Promise<string | null> {
180
145
  try {
181
146
  const tx = new Transaction();
147
+ tx.setSender(this.address);
148
+
149
+ // Direct moveCall to get the returned object reference
150
+ // Based on SDK source: margin_pool::mint_supplier_cap
151
+ const supplierCap = tx.moveCall({
152
+ target: `${this.supplierCapPackageId}::margin_pool::mint_supplier_cap`,
153
+ arguments: [tx.object(this.dbConfig.MARGIN_REGISTRY_ID), tx.object.clock()],
154
+ });
182
155
 
183
- // Use MarginPoolContract to create Supplier Cap | 使用 MarginPoolContract 創建 Supplier Cap
184
- const cap = this.marginPoolContract.mintSupplierCap()(tx);
185
- tx.transferObjects([cap], this.address);
156
+ // Transfer the created Supplier Cap to the sender
157
+ tx.transferObjects([supplierCap], tx.pure.address(this.address));
186
158
 
187
159
  const result = await this.suiClient.signAndExecuteTransaction({
188
160
  signer: this.keypair,
@@ -222,9 +194,25 @@ export class DeepBookMarginToolkit {
222
194
  async createSupplyReferral(coin: MarginCoinType): Promise<string | null> {
223
195
  try {
224
196
  const tx = new Transaction();
197
+ tx.setSender(this.address);
198
+
199
+ // Get margin pool configuration
200
+ const marginPool = this.dbConfig.getMarginPool(coin);
201
+ if (!marginPool) {
202
+ throw new Error(`Margin pool configuration not found for coin: ${coin}`);
203
+ }
225
204
 
226
- // Use MarginPoolContract to create supply Referral | 使用 MarginPoolContract 創建供應 Referral
227
- this.marginPoolContract.mintSupplyReferral(coin)(tx);
205
+ // Use the initialVersion from config as the initial_shared_version
206
+ // Margin pools are shared objects on Sui
207
+ tx.moveCall({
208
+ target: `${this.dbConfig.MARGIN_PACKAGE_ID}::margin_pool::mint_supply_referral`,
209
+ arguments: [
210
+ tx.object(marginPool.address),
211
+ tx.object(this.dbConfig.MARGIN_REGISTRY_ID),
212
+ tx.object.clock(),
213
+ ],
214
+ typeArguments: [marginPool.type],
215
+ });
228
216
 
229
217
  const result = await this.suiClient.signAndExecuteTransaction({
230
218
  signer: this.keypair,
@@ -411,19 +399,19 @@ export class DeepBookMarginToolkit {
411
399
  if (supplyData && supplyData[0]) {
412
400
  const rawAmount = Buffer.from(supplyData[0]).readBigUInt64LE();
413
401
  // Convert from smallest unit to human-readable | 從最小單位轉換為人類可讀
414
- const scalar = coin === 'SUI' ? TESTNET_COINS.SUI.scalar : TESTNET_COINS.DBUSDC.scalar;
402
+ const scalar = this.dbConfig.getCoin(coin).scalar;
415
403
  userSupplyAmount = Number(rawAmount) / scalar;
416
404
  }
417
405
  }
418
406
 
419
407
  // Query wallet balance | 查詢錢包餘額
420
- const coinType = coin === 'SUI' ? TESTNET_COINS.SUI.type : TESTNET_COINS.DBUSDC.type;
408
+ const coinType = this.dbConfig.getCoin(coin).type;
421
409
  const balance = await this.suiClient.getBalance({
422
410
  owner: this.address,
423
411
  coinType,
424
412
  });
425
413
 
426
- const scalar = coin === 'SUI' ? TESTNET_COINS.SUI.scalar : TESTNET_COINS.DBUSDC.scalar;
414
+ const scalar = this.dbConfig.getCoin(coin).scalar;
427
415
  const walletBalance = Number(balance.totalBalance) / scalar;
428
416
 
429
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
  /**
@@ -71,3 +78,56 @@ export interface ReferralInfo {
71
78
  /** Owner address | 擁有者地址 */
72
79
  owner: string;
73
80
  }
81
+
82
+ // ============================================================================
83
+ // Margin Pool Parameter Types | Margin Pool 參數類型
84
+ // ============================================================================
85
+
86
+ /**
87
+ * Margin pool configuration parameters | Margin pool 配置參數
88
+ */
89
+ export interface MarginPoolConfig {
90
+ /** Pool address | 池子地址 */
91
+ address: string;
92
+
93
+ /** Coin type | 幣種類型 */
94
+ coinType: string;
95
+
96
+ /** Initial version | 初始版本 */
97
+ initialVersion: number;
98
+
99
+ /** Supply cap | 供應上限 */
100
+ supplyCap: number;
101
+
102
+ /** Max utilization rate | 最大使用率 */
103
+ maxUtilizationRate: number;
104
+
105
+ /** Referral spread | Referral spread */
106
+ referralSpread: number;
107
+
108
+ /** Min borrow amount | 最小借款額 */
109
+ minBorrow: number;
110
+ }
111
+
112
+ /**
113
+ * Risk parameters for a trading pair | 交易對的風險參數
114
+ */
115
+ export interface RiskParameters {
116
+ /** Min withdraw risk ratio | 最小提款風險比率 */
117
+ minWithdrawRiskRatio: number;
118
+
119
+ /** Min borrow risk ratio | 最小借款風險比率 */
120
+ minBorrowRiskRatio: number;
121
+
122
+ /** Liquidation risk ratio | 清算風險比率 */
123
+ liquidationRiskRatio: number;
124
+
125
+ /** Target liquidation risk ratio | 目標清算風險比率 */
126
+ targetLiquidationRiskRatio: number;
127
+
128
+ /** User liquidation reward | 用戶清算獎勵 */
129
+ userLiquidationReward: number;
130
+
131
+ /** Pool liquidation reward | 池子清算獎勵 */
132
+ poolLiquidationReward: number;
133
+ }
@@ -0,0 +1,4 @@
1
+ import { FLOAT_SCALAR } from '@mysten/deepbook-v3';
2
+
3
+ export const mul = (a: bigint, b: bigint): bigint => (a * b) / BigInt(FLOAT_SCALAR);
4
+ export const normalize = (value: bigint): number => Number(value) / FLOAT_SCALAR; // e.g. 0.12 = 12%
@@ -1,229 +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
- * Last updated | 最後更新: 2025-11-11
5
- */
6
-
7
- // ============================================================================
8
- // Package IDs
9
- // ============================================================================
10
-
11
- export const TESTNET_PACKAGES = {
12
- DEEPBOOK_PACKAGE_ID: '0xfb28c4cbc6865bd1c897d26aecbe1f8792d1509a20ffec692c800660cbec6982',
13
- REGISTRY_ID: '0x7c256edbda983a2cd6f946655f4bf3f00a41043993781f8674a7046e8c0e11d1',
14
- DEEP_TREASURY_ID: '0x69fffdae0075f8f71f4fa793549c11079266910e8905169845af1f5d00e09dcb',
15
- MARGIN_PACKAGE_ID: '0x7f2d8f15343f210e813595a8798d6197d152061d0a35be938372f4b1cd66f209',
16
- MARGIN_INITIAL_PACKAGE_ID: '0x7f2d8f15343f210e813595a8798d6197d152061d0a35be938372f4b1cd66f209', // for events
17
- MARGIN_REGISTRY_ID: '0x31b9086767e9b5925cb29414ea623a7705b5600d9594d88c17c7a011cb499ab4',
18
- } as const;
19
-
20
- // ============================================================================
21
- // Coin Configuration | 代幣配置
22
- // ============================================================================
23
-
24
- export const TESTNET_COINS = {
25
- DEEP: {
26
- address: '0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8',
27
- type: '0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8::deep::DEEP',
28
- scalar: 1_000_000,
29
- decimals: 6,
30
- },
31
- SUI: {
32
- address: '0x0000000000000000000000000000000000000000000000000000000000000002',
33
- type: '0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI',
34
- scalar: 1_000_000_000,
35
- decimals: 9,
36
- },
37
- DBUSDC: {
38
- address: '0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7',
39
- type: '0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC',
40
- scalar: 1_000_000,
41
- decimals: 6,
42
- },
43
- DBUSDT: {
44
- address: '0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7',
45
- type: '0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDT::DBUSDT',
46
- scalar: 1_000_000,
47
- decimals: 6,
48
- },
49
- WAL: {
50
- address: '0x9ef7676a9f81937a52ae4b2af8d511a28a0b080477c0c2db40b0ab8882240d76',
51
- type: '0x9ef7676a9f81937a52ae4b2af8d511a28a0b080477c0c2db40b0ab8882240d76::wal::WAL',
52
- scalar: 1_000_000_000,
53
- decimals: 9,
54
- },
55
- } as const;
56
-
57
- // ============================================================================
58
- // Trading Pool Configuration | 交易池配置
59
- // ============================================================================
60
-
61
- export const TESTNET_POOLS = {
62
- DEEP_SUI: {
63
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
64
- address: '0x48c95963e9eac37a316b7ae04a0deb761bcdcc2b67912374d6036e7f0e9bae9f',
65
- baseCoin: 'DEEP',
66
- quoteCoin: 'SUI',
67
- lotSize: 1_000_000, // 1 DEEP (6 decimals) | 1 DEEP(6 位小數)
68
- tickSize: 1_000_000, // 0.001 SUI (9 decimals) | 0.001 SUI(9 位小數)
69
- },
70
- SUI_DBUSDC: {
71
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
72
- address: '0x1c19362ca52b8ffd7a33cee805a67d40f31e6ba303753fd3a4cfdfacea7163a5',
73
- baseCoin: 'SUI',
74
- quoteCoin: 'DBUSDC',
75
- lotSize: 1_000_000, // 0.001 SUI (9 decimals) | 0.001 SUI(9 位小數)
76
- tickSize: 1_000, // 0.001 DBUSDC (6 decimals) | 0.001 DBUSDC(6 位小數)
77
- },
78
- DEEP_DBUSDC: {
79
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
80
- address: '0xe86b991f8632217505fd859445f9803967ac84a9d4a1219065bf191fcb74b622',
81
- baseCoin: 'DEEP',
82
- quoteCoin: 'DBUSDC',
83
- lotSize: 1_000_000, // 1 DEEP (6 decimals) | 1 DEEP(6 位小數)
84
- tickSize: 1_000, // 0.001 DBUSDC (6 decimals) | 0.001 DBUSDC(6 位小數)
85
- },
86
- DBUSDT_DBUSDC: {
87
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
88
- address: '0x83970bb02e3636efdff8c141ab06af5e3c9a22e2f74d7f02a9c3430d0d10c1ca',
89
- baseCoin: 'DBUSDT',
90
- quoteCoin: 'DBUSDC',
91
- lotSize: 1_000_000, // 1 DBUSDT (6 decimals) | 1 DBUSDT(6 位小數)
92
- tickSize: 1_000, // 0.001 DBUSDC (6 decimals) | 0.001 DBUSDC(6 位小數)
93
- },
94
- WAL_DBUSDC: {
95
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
96
- address: '0xeb524b6aea0ec4b494878582e0b78924208339d360b62aec4a8ecd4031520dbb',
97
- baseCoin: 'WAL',
98
- quoteCoin: 'DBUSDC',
99
- lotSize: 1_000_000, // 0.001 WAL (9 decimals) | 0.001 WAL(9 位小數)
100
- tickSize: 1_000, // 0.001 DBUSDC (6 decimals) | 0.001 DBUSDC(6 位小數)
101
- },
102
- WAL_SUI: {
103
- // Actual pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Pool 地址(2025-11-11 驗證)
104
- address: '0x8c1c1b186c4fddab1ebd53e0895a36c1d1b3b9a77cd34e607bef49a38af0150a',
105
- baseCoin: 'WAL',
106
- quoteCoin: 'SUI',
107
- lotSize: 1_000_000, // 0.001 WAL (9 decimals) | 0.001 WAL(9 位小數)
108
- tickSize: 1_000_000, // 0.001 SUI (9 decimals) | 0.001 SUI(9 位小數)
109
- },
110
- } as const;
111
-
112
- // ============================================================================
113
- // Margin Pool Configuration | Margin 池配置
114
- // ============================================================================
115
-
116
- export const TESTNET_MARGIN_POOLS = {
117
- SUI: {
118
- // Actual margin pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Margin Pool 地址(2025-11-11 驗證)
119
- address: '0xe620d6a5390e57e88baff18af89383130d4210eb496a024edcd62f270a655af7',
120
- coinType: '0x2::sui::SUI',
121
- },
122
- DBUSDC: {
123
- // Actual margin pool address on testnet (verified 2025-11-11) | 實際存在於 testnet 上的 Margin Pool 地址(2025-11-11 驗證)
124
- address: '0xfd0dc290a120ad6c534507614d4dc0b2e78baab649c35bfacbaec2ce18140b69',
125
- coinType: '0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC',
126
- },
127
- } as const;
128
-
129
- // ============================================================================
130
- // Pyth Oracle Configuration | Pyth Oracle 配置
131
- // ============================================================================
132
-
133
- export const TESTNET_PYTH = {
134
- pythStateId: '0x243759059f4c3111179da5878c12f68d612c21a8d54d85edc86164bb18be1c7c',
135
- wormholeStateId: '0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790',
136
- } as const;
137
-
138
- // ============================================================================
139
- // Pool Keys (SDK shorthand) | Pool Keys(SDK 使用的簡寫)
140
- // ============================================================================
141
-
142
- export const TESTNET_POOL_KEYS = [
143
- 'DEEP_SUI',
144
- 'SUI_DBUSDC',
145
- 'DEEP_DBUSDC',
146
- 'DBUSDT_DBUSDC',
147
- 'WAL_DBUSDC',
148
- 'WAL_SUI',
149
- ] as const;
150
-
151
- export type TestnetPoolKey = (typeof TESTNET_POOL_KEYS)[number];
152
-
153
- // ============================================================================
154
- // Margin Pool Contract Parameter Keys | Margin Pool 合約參數鍵
155
- // ============================================================================
156
-
157
- export const MARGIN_POOL_PARAM_KEYS = [
158
- 'supplyCap',
159
- 'maxUtilizationRate',
160
- 'protocolSpread',
161
- 'minBorrow',
162
- 'interestRate',
163
- 'totalSupply',
164
- 'supplyShares',
165
- 'totalBorrow',
166
- 'borrowShares',
167
- 'lastUpdateTimestamp',
168
- ] as const;
169
-
170
- export const MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS = [
171
- 'userSupplyShares',
172
- 'userSupplyAmount',
173
- ] as const;
174
-
175
- export const MARGIN_POOL_PARAM_KEY_STRUCT_MAP = {
176
- supplyCap: 'U64',
177
- maxUtilizationRate: 'U64',
178
- protocolSpread: 'U64',
179
- minBorrow: 'U64',
180
- interestRate: 'U64',
181
- totalSupply: 'U64',
182
- supplyShares: 'U64',
183
- totalBorrow: 'U64',
184
- borrowShares: 'U64',
185
- lastUpdateTimestamp: 'U64',
186
- userSupplyShares: 'U64',
187
- userSupplyAmount: 'U64',
188
- } as Record<
189
- (typeof MARGIN_POOL_PARAM_KEYS)[number] | (typeof MARGIN_POOL_W_SUPPLIER_CAP_PARAM_KEYS)[number],
190
- 'U64'
191
- >;
192
-
193
- // ============================================================================
194
- // Utility Functions | 輔助函數
195
- // ============================================================================
196
-
197
- /**
198
- * Get full pool configuration | 取得 Pool 的完整配置
199
- */
200
- export function getPoolConfig(poolKey: TestnetPoolKey) {
201
- return TESTNET_POOLS[poolKey];
202
- }
203
-
204
- /**
205
- * Get full coin configuration | 取得代幣的完整配置
206
- */
207
- export function getCoinConfig(coinSymbol: keyof typeof TESTNET_COINS) {
208
- return TESTNET_COINS[coinSymbol];
209
- }
210
-
211
- /**
212
- * Format coin amount (convert from smallest unit to human-readable) | 格式化代幣數量(從最小單位轉換為人類可讀)
213
- */
214
- export function formatCoinAmount(
215
- amount: number | bigint,
216
- coinSymbol: keyof typeof TESTNET_COINS
217
- ): string {
218
- const config = TESTNET_COINS[coinSymbol];
219
- const value = Number(amount) / config.scalar;
220
- return value.toFixed(config.decimals);
221
- }
222
-
223
- /**
224
- * Parse coin amount (convert from human-readable to smallest unit) | 解析代幣數量(從人類可讀轉換為最小單位)
225
- */
226
- export function parseCoinAmount(amount: number, coinSymbol: keyof typeof TESTNET_COINS): number {
227
- const config = TESTNET_COINS[coinSymbol];
228
- return Math.floor(amount * config.scalar);
229
- }