@dhedge/v2-sdk 2.2.0 → 2.2.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/README.md +4 -1
- package/dist/services/ondo/index.d.ts +5 -0
- package/dist/types.d.ts +2 -1
- package/dist/v2-sdk.cjs.development.js +266 -28
- package/dist/v2-sdk.cjs.development.js.map +1 -1
- package/dist/v2-sdk.cjs.production.min.js +1 -1
- package/dist/v2-sdk.cjs.production.min.js.map +1 -1
- package/dist/v2-sdk.esm.js +266 -28
- package/dist/v2-sdk.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/abi/ondo/IOndoGMSwap.json +30 -0
- package/src/config.ts +2 -1
- package/src/entities/pool.ts +10 -0
- package/src/services/ondo/index.ts +142 -0
- package/src/test/ondo.onchain.test.ts +132 -0
- package/src/types.ts +2 -1
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"inputs": [
|
|
4
|
+
{ "name": "tokenIn", "type": "address" },
|
|
5
|
+
{ "name": "amountIn", "type": "uint256" },
|
|
6
|
+
{ "name": "tokenOut", "type": "address" },
|
|
7
|
+
{ "name": "amountOutMinimum", "type": "uint256" },
|
|
8
|
+
{ "name": "attestationSignature", "type": "bytes" },
|
|
9
|
+
{
|
|
10
|
+
"components": [
|
|
11
|
+
{ "name": "chainId", "type": "uint256" },
|
|
12
|
+
{ "name": "attestationId", "type": "uint256" },
|
|
13
|
+
{ "name": "userId", "type": "bytes32" },
|
|
14
|
+
{ "name": "asset", "type": "address" },
|
|
15
|
+
{ "name": "price", "type": "uint256" },
|
|
16
|
+
{ "name": "quantity", "type": "uint256" },
|
|
17
|
+
{ "name": "expiration", "type": "uint256" },
|
|
18
|
+
{ "name": "side", "type": "uint8" },
|
|
19
|
+
{ "name": "additionalData", "type": "bytes32" }
|
|
20
|
+
],
|
|
21
|
+
"name": "quote",
|
|
22
|
+
"type": "tuple"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"name": "swapExactInWithAttestation",
|
|
26
|
+
"outputs": [],
|
|
27
|
+
"stateMutability": "nonpayable",
|
|
28
|
+
"type": "function"
|
|
29
|
+
}
|
|
30
|
+
]
|
package/src/config.ts
CHANGED
|
@@ -77,7 +77,8 @@ export const routerAddress: AddressDappNetworkMap = {
|
|
|
77
77
|
[Dapp.ODOS]: "0x0D05a7D3448512B78fa8A9e46c4872C88C4a0D05",
|
|
78
78
|
[Dapp.PENDLE]: "0x888888888889758F76e7103c6CbF23ABbF58F946",
|
|
79
79
|
[Dapp.ONEINCH]: "0x111111125421ca6dc452d289314280a0f8842a65",
|
|
80
|
-
[Dapp.KYBERSWAP]: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5"
|
|
80
|
+
[Dapp.KYBERSWAP]: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5",
|
|
81
|
+
[Dapp.ONDO]: "0xde41399145F23936b03dD1474eC16c1519c0DC2a"
|
|
81
82
|
},
|
|
82
83
|
[Network.PLASMA]: {
|
|
83
84
|
[Dapp.AAVEV3]: "0x925a2A7214Ed92428B5b1B090F80b25700095e12",
|
package/src/entities/pool.ts
CHANGED
|
@@ -87,6 +87,7 @@ import {
|
|
|
87
87
|
} from "../services/toros/limitOrder";
|
|
88
88
|
import { getKyberSwapTxData } from "../services/kyberSwap";
|
|
89
89
|
import { getCowSwapTxData } from "../services/cowSwap";
|
|
90
|
+
import { getOndoSwapTxData } from "../services/ondo";
|
|
90
91
|
import {
|
|
91
92
|
getClosePositionHyperliquidTxData,
|
|
92
93
|
getDepositHyperliquidTxData,
|
|
@@ -451,6 +452,15 @@ export class Pool {
|
|
|
451
452
|
slippage
|
|
452
453
|
));
|
|
453
454
|
break;
|
|
455
|
+
case Dapp.ONDO:
|
|
456
|
+
({ swapTxData, minAmountOut } = await getOndoSwapTxData(
|
|
457
|
+
this,
|
|
458
|
+
assetFrom,
|
|
459
|
+
assetTo,
|
|
460
|
+
amountIn.toString(),
|
|
461
|
+
slippage
|
|
462
|
+
));
|
|
463
|
+
break;
|
|
454
464
|
case Dapp.COWSWAP: {
|
|
455
465
|
const cowSwapEstimateGas = isSdkOptionsBoolean(sdkOptions)
|
|
456
466
|
? sdkOptions
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { ethers } from "ethers";
|
|
3
|
+
import BigNumber from "bignumber.js";
|
|
4
|
+
import { ApiError, Pool } from "../..";
|
|
5
|
+
import IOndoGMSwap from "../../abi/ondo/IOndoGMSwap.json";
|
|
6
|
+
|
|
7
|
+
const ONDO_API_URL = "https://api.gm.ondo.finance/v1/attestations";
|
|
8
|
+
|
|
9
|
+
// Ethereum mainnet USDC — Ondo is Ethereum-only
|
|
10
|
+
const USDC_ETHEREUM = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
|
|
11
|
+
|
|
12
|
+
type OndoAttestation = {
|
|
13
|
+
attestationId: string;
|
|
14
|
+
userId: string;
|
|
15
|
+
chainId: string;
|
|
16
|
+
assetAddress: string;
|
|
17
|
+
side: string;
|
|
18
|
+
tokenAmount: string;
|
|
19
|
+
price: string;
|
|
20
|
+
expiration: number;
|
|
21
|
+
signature: string;
|
|
22
|
+
additionalData: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const iface = new ethers.utils.Interface(IOndoGMSwap);
|
|
26
|
+
|
|
27
|
+
// Ondo returns userId as a left-aligned 32-byte hex (significant bytes first,
|
|
28
|
+
// e.g. 0x474d...0000), so right-pad to preserve byte order and fix it to 32 bytes.
|
|
29
|
+
function toBytes32(s: string): string {
|
|
30
|
+
const hex = s.startsWith("0x") ? s.slice(2) : s;
|
|
31
|
+
return "0x" + hex.padEnd(64, "0").slice(0, 64);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function postOndoAttestation(
|
|
35
|
+
symbol: string,
|
|
36
|
+
side: "buy" | "sell",
|
|
37
|
+
amount: { notionalValue: string } | { tokenAmount: string },
|
|
38
|
+
apiKey: string
|
|
39
|
+
): Promise<OndoAttestation> {
|
|
40
|
+
try {
|
|
41
|
+
const { data } = await axios.post(
|
|
42
|
+
ONDO_API_URL,
|
|
43
|
+
{ chainId: "ethereum-1", symbol, side, ...amount, duration: "short" },
|
|
44
|
+
{ headers: { "x-api-key": apiKey } }
|
|
45
|
+
);
|
|
46
|
+
return data as OndoAttestation;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// Surface Ondo's structured error (e.g. ASSET_NOT_FOUND, MARKET_CLOSED)
|
|
49
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
50
|
+
const { code, message } = error.response.data ?? {};
|
|
51
|
+
throw new ApiError(
|
|
52
|
+
`Ondo attestation request failed (${error.response.status}): ${code ??
|
|
53
|
+
""} ${message ?? JSON.stringify(error.response.data)}`.trim()
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
throw new ApiError(
|
|
57
|
+
`Ondo attestation request failed: ${
|
|
58
|
+
error instanceof Error ? error.message : String(error)
|
|
59
|
+
}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function getOndoSwapTxData(
|
|
65
|
+
pool: Pool,
|
|
66
|
+
tokenIn: string,
|
|
67
|
+
tokenOut: string,
|
|
68
|
+
amountIn: string,
|
|
69
|
+
slippage: number
|
|
70
|
+
): Promise<{ swapTxData: string; minAmountOut: string }> {
|
|
71
|
+
const apiKey = process.env.ONDO_API_KEY;
|
|
72
|
+
if (!apiKey) throw new Error("ONDO_API_KEY environment variable is not set");
|
|
73
|
+
|
|
74
|
+
const amount = new BigNumber(amountIn);
|
|
75
|
+
const isMint = tokenIn.toLowerCase() === USDC_ETHEREUM.toLowerCase();
|
|
76
|
+
const gmToken = isMint ? tokenOut : tokenIn;
|
|
77
|
+
const slippageBps = Math.round(slippage * 100);
|
|
78
|
+
|
|
79
|
+
const tokenContract = new ethers.Contract(
|
|
80
|
+
gmToken,
|
|
81
|
+
["function symbol() view returns (string)"],
|
|
82
|
+
pool.signer
|
|
83
|
+
);
|
|
84
|
+
const symbol: string = await tokenContract.symbol();
|
|
85
|
+
|
|
86
|
+
// For mint: notionalValue is the USD amount to subscribe. USDonManager values
|
|
87
|
+
// USDC at par (1 USDC = 1 USDon), so pass the USDC amount as-is (6dp -> decimal).
|
|
88
|
+
// Discounting by a USDC market price would strand the gap as USDon in the swapper
|
|
89
|
+
// (and revert via ExcessiveAmountIn once it exceeds the 1 USDC tolerance).
|
|
90
|
+
// For redeem: pass the GM token amount directly as tokenAmount.
|
|
91
|
+
const attestationAmount = isMint
|
|
92
|
+
? { notionalValue: amount.div(1e6).toFixed(18) }
|
|
93
|
+
: { tokenAmount: amount.div(1e18).toFixed(18) };
|
|
94
|
+
|
|
95
|
+
const attestation = await postOndoAttestation(
|
|
96
|
+
symbol,
|
|
97
|
+
isMint ? "buy" : "sell",
|
|
98
|
+
attestationAmount,
|
|
99
|
+
apiKey
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const signature =
|
|
103
|
+
"0x" + Buffer.from(attestation.signature, "base64").toString("hex");
|
|
104
|
+
const additionalData = ethers.utils.hexZeroPad(
|
|
105
|
+
"0x" + Buffer.from(attestation.additionalData, "base64").toString("hex"),
|
|
106
|
+
32
|
|
107
|
+
);
|
|
108
|
+
const quantity = new BigNumber(attestation.tokenAmount);
|
|
109
|
+
const priceD18 = new BigNumber(attestation.price);
|
|
110
|
+
|
|
111
|
+
let minAmountOut: BigNumber;
|
|
112
|
+
if (isMint) {
|
|
113
|
+
minAmountOut = quantity.times(10000 - slippageBps).div(10000);
|
|
114
|
+
} else {
|
|
115
|
+
// USDC out (6 dec) = quantity_D18 * price_D18 / 1e30
|
|
116
|
+
const usdcOut = quantity.times(priceD18).div(1e30);
|
|
117
|
+
minAmountOut = usdcOut.times(10000 - slippageBps).div(10000);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const quote = {
|
|
121
|
+
chainId: 1,
|
|
122
|
+
attestationId: attestation.attestationId,
|
|
123
|
+
userId: toBytes32(attestation.userId),
|
|
124
|
+
asset: attestation.assetAddress,
|
|
125
|
+
price: priceD18.toFixed(0),
|
|
126
|
+
quantity: quantity.toFixed(0),
|
|
127
|
+
expiration: attestation.expiration,
|
|
128
|
+
side: Number(attestation.side),
|
|
129
|
+
additionalData
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const swapTxData = iface.encodeFunctionData("swapExactInWithAttestation", [
|
|
133
|
+
tokenIn,
|
|
134
|
+
amount.toFixed(0),
|
|
135
|
+
tokenOut,
|
|
136
|
+
minAmountOut.toFixed(0),
|
|
137
|
+
signature,
|
|
138
|
+
quote
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
return { swapTxData, minAmountOut: minAmountOut.toFixed(0) };
|
|
142
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ondo Global Markets on-chain tests for minting and redeeming GM tokens.
|
|
5
|
+
* These require a live chain connection (onFork: false) because each mint/redeem
|
|
6
|
+
* needs a fresh attestation signed by the Ondo API against current chain state,
|
|
7
|
+
* which cannot be reproduced on a Hardhat fork.
|
|
8
|
+
*
|
|
9
|
+
* Prerequisites:
|
|
10
|
+
* - PRIVATE_KEY in .env (must be the pool manager or trader)
|
|
11
|
+
* - ETHEREUM_URL in .env
|
|
12
|
+
* - ONDO_API_KEY in .env
|
|
13
|
+
* - The test pool must hold USDC to mint (and SPYon, from the mint, to redeem)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { ethers } from "ethers";
|
|
17
|
+
import { Dhedge, Pool } from "..";
|
|
18
|
+
|
|
19
|
+
import { Dapp, Network } from "../types";
|
|
20
|
+
import { CONTRACT_ADDRESS, MAX_AMOUNT } from "./constants";
|
|
21
|
+
import { TestingRunParams, testingHelper } from "./utils/testingHelper";
|
|
22
|
+
|
|
23
|
+
import { getTxOptions } from "./txOptions";
|
|
24
|
+
import { balanceDelta } from "./utils/token";
|
|
25
|
+
|
|
26
|
+
import { routerAddress } from "../config";
|
|
27
|
+
|
|
28
|
+
const testOndo = ({ wallet, network }: TestingRunParams) => {
|
|
29
|
+
const USDC = CONTRACT_ADDRESS[network].USDC;
|
|
30
|
+
const SPYon = "0xfedc5f4a6c38211c1338aa411018dfaf26612c08";
|
|
31
|
+
|
|
32
|
+
let dhedge: Dhedge;
|
|
33
|
+
let pool: Pool;
|
|
34
|
+
jest.setTimeout(100000);
|
|
35
|
+
|
|
36
|
+
describe(`pool on ${network}`, () => {
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
dhedge = new Dhedge(wallet, network);
|
|
39
|
+
pool = await dhedge.loadPool(
|
|
40
|
+
"0x9f647b85A514b1e60F8E8E956E636a50dA406279"
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("approves unlimited USDC on Ondo", async () => {
|
|
45
|
+
const tx = await pool.approve(Dapp.ONDO, USDC, MAX_AMOUNT);
|
|
46
|
+
await tx.wait();
|
|
47
|
+
const iERC20 = new ethers.Contract(
|
|
48
|
+
USDC,
|
|
49
|
+
["function allowance(address,address) view returns (uint256)"],
|
|
50
|
+
pool.signer
|
|
51
|
+
);
|
|
52
|
+
const allowance = await iERC20.allowance(
|
|
53
|
+
pool.address,
|
|
54
|
+
routerAddress[network][Dapp.ONDO]!
|
|
55
|
+
);
|
|
56
|
+
expect(allowance.gt(0)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("gets gas estimation for 40 USDC into SPYon on Ondo", async () => {
|
|
60
|
+
const gasEstimate = await pool.trade(
|
|
61
|
+
Dapp.ONDO,
|
|
62
|
+
USDC,
|
|
63
|
+
SPYon,
|
|
64
|
+
"40000000",
|
|
65
|
+
0.1,
|
|
66
|
+
await getTxOptions(network),
|
|
67
|
+
true
|
|
68
|
+
);
|
|
69
|
+
expect(gasEstimate.gasEstimationError).toBeNull();
|
|
70
|
+
expect(gasEstimate.gas.gt(0)).toBe(true);
|
|
71
|
+
expect(gasEstimate.minAmountOut).not.toBeNull();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("trades 40 USDC into SPYon on Ondo", async () => {
|
|
75
|
+
const tx = await pool.trade(
|
|
76
|
+
Dapp.ONDO,
|
|
77
|
+
USDC,
|
|
78
|
+
SPYon,
|
|
79
|
+
"40000000",
|
|
80
|
+
0.1,
|
|
81
|
+
await getTxOptions(network)
|
|
82
|
+
);
|
|
83
|
+
await tx.wait();
|
|
84
|
+
const spBalanceDelta = await balanceDelta(
|
|
85
|
+
pool.address,
|
|
86
|
+
SPYon,
|
|
87
|
+
pool.signer
|
|
88
|
+
);
|
|
89
|
+
expect(spBalanceDelta.gt(0)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("approves unlimited SPYon on Ondo", async () => {
|
|
93
|
+
const tx = await pool.approve(Dapp.ONDO, SPYon, MAX_AMOUNT);
|
|
94
|
+
await tx.wait();
|
|
95
|
+
const iERC20 = new ethers.Contract(
|
|
96
|
+
SPYon,
|
|
97
|
+
["function allowance(address,address) view returns (uint256)"],
|
|
98
|
+
pool.signer
|
|
99
|
+
);
|
|
100
|
+
const allowance = await iERC20.allowance(
|
|
101
|
+
pool.address,
|
|
102
|
+
routerAddress[network][Dapp.ONDO]!
|
|
103
|
+
);
|
|
104
|
+
expect(allowance.gt(0)).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("sells SPYon balance on Ondo", async () => {
|
|
108
|
+
const spyonBalance = await pool.utils.getBalance(SPYon, pool.address);
|
|
109
|
+
const tx = await pool.trade(
|
|
110
|
+
Dapp.ONDO,
|
|
111
|
+
SPYon,
|
|
112
|
+
USDC,
|
|
113
|
+
spyonBalance,
|
|
114
|
+
0.1,
|
|
115
|
+
await getTxOptions(network)
|
|
116
|
+
);
|
|
117
|
+
await tx.wait();
|
|
118
|
+
const usdcBalanceDelta = await balanceDelta(
|
|
119
|
+
pool.address,
|
|
120
|
+
USDC,
|
|
121
|
+
pool.signer
|
|
122
|
+
);
|
|
123
|
+
expect(usdcBalanceDelta.gt(0)).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
testingHelper({
|
|
129
|
+
network: Network.ETHEREUM,
|
|
130
|
+
testingRun: testOndo,
|
|
131
|
+
onFork: false
|
|
132
|
+
});
|
package/src/types.ts
CHANGED