@thru/programs 0.2.29 → 0.2.31
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/amm/index.cjs +6424 -0
- package/dist/amm/index.cjs.map +1 -0
- package/dist/amm/index.d.cts +1424 -0
- package/dist/amm/index.d.ts +1424 -0
- package/dist/amm/index.js +6360 -0
- package/dist/amm/index.js.map +1 -0
- package/dist/chunk-P5OABVJI.js +28 -0
- package/dist/chunk-P5OABVJI.js.map +1 -0
- package/dist/multicall/index.cjs +1 -1
- package/dist/multicall/index.cjs.map +1 -1
- package/dist/multicall/index.d.cts +2 -2
- package/dist/multicall/index.d.ts +2 -2
- package/dist/multicall/index.js +1 -1
- package/dist/multicall/index.js.map +1 -1
- package/dist/passkey-manager/index.cjs +33 -27
- package/dist/passkey-manager/index.cjs.map +1 -1
- package/dist/passkey-manager/index.d.cts +4 -3
- package/dist/passkey-manager/index.d.ts +4 -3
- package/dist/passkey-manager/index.js +9 -28
- package/dist/passkey-manager/index.js.map +1 -1
- package/dist/token/index.cjs +1760 -275
- package/dist/token/index.cjs.map +1 -1
- package/dist/token/index.d.cts +124 -20
- package/dist/token/index.d.ts +124 -20
- package/dist/token/index.js +1760 -275
- package/dist/token/index.js.map +1 -1
- package/package.json +9 -2
- package/src/amm/abi/thru/common/primitives/types.ts +2269 -0
- package/src/amm/abi/thru/program/amm/types.ts +5621 -0
- package/src/amm/index.test.ts +255 -0
- package/src/amm/index.ts +434 -0
- package/src/helpers/bytes.ts +25 -0
- package/src/multicall/abi/thru/common/primitives/types.ts +5 -6
- package/src/multicall/abi/thru/program/multicall/types.ts +1 -2
- package/src/passkey-manager/abi/thru/blockchain/state_proof/types.ts +0 -1
- package/src/passkey-manager/abi/thru/common/primitives/types.ts +0 -1
- package/src/passkey-manager/abi/thru/program/passkey_manager/types.ts +4 -5
- package/src/passkey-manager/encoding.ts +2 -26
- package/src/token/abi/thru/blockchain/state_proof/types.ts +101 -21
- package/src/token/abi/thru/common/primitives/types.ts +1295 -167
- package/src/token/abi/thru/program/token/types.ts +647 -237
- package/tsup.config.ts +1 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Pubkey, deriveAddress, deriveProgramAddress } from '@thru/sdk';
|
|
3
|
+
import type { Thru } from '@thru/sdk/client';
|
|
4
|
+
import {
|
|
5
|
+
AMM_PROGRAM_ADDRESS,
|
|
6
|
+
AMM_INSTRUCTION_ADD_LIQUIDITY,
|
|
7
|
+
AMM_INSTRUCTION_INIT_POOL,
|
|
8
|
+
AMM_INSTRUCTION_SWAP,
|
|
9
|
+
AMM_POOL_METADATA_SIZE,
|
|
10
|
+
createAddLiquidityInstruction,
|
|
11
|
+
createInitPoolInstruction,
|
|
12
|
+
createSwapInstruction,
|
|
13
|
+
deriveAmmPoolAddresses,
|
|
14
|
+
parseAmmPoolMetadata,
|
|
15
|
+
quoteAmmSwapExactIn,
|
|
16
|
+
sortAmmMints,
|
|
17
|
+
} from './index';
|
|
18
|
+
|
|
19
|
+
const thru = {
|
|
20
|
+
helpers: {
|
|
21
|
+
deriveAddress,
|
|
22
|
+
deriveProgramAddress,
|
|
23
|
+
},
|
|
24
|
+
} as unknown as Thru;
|
|
25
|
+
|
|
26
|
+
function key(id: number): Uint8Array {
|
|
27
|
+
const bytes = new Uint8Array(32);
|
|
28
|
+
bytes[0] = id;
|
|
29
|
+
return bytes;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function context(indexes: Record<string, number>) {
|
|
33
|
+
return {
|
|
34
|
+
getAccountIndex(pubkey: Uint8Array): number {
|
|
35
|
+
const index = indexes[bytesToHex(pubkey)];
|
|
36
|
+
if (index === undefined) throw new Error(`missing account ${bytesToHex(pubkey)}`);
|
|
37
|
+
return index;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeU64(target: Uint8Array, offset: number, value: bigint): void {
|
|
43
|
+
new DataView(target.buffer, target.byteOffset, target.byteLength)
|
|
44
|
+
.setBigUint64(offset, value, true);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('amm helpers', () => {
|
|
48
|
+
it('exports the standardized AMM program address', () => {
|
|
49
|
+
expect(AMM_PROGRAM_ADDRESS).toBe(
|
|
50
|
+
deriveProgramAddress({
|
|
51
|
+
programAddress: 'taNz_xi2ZJcAkg3nIbD0Tgy6-P4ckJGUUwLQPPqobPQbsc',
|
|
52
|
+
seed: 'amm',
|
|
53
|
+
}).address
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('sorts mint addresses lexicographically', () => {
|
|
58
|
+
const sorted = sortAmmMints(key(9), key(2));
|
|
59
|
+
expect(sorted.inputOrder).toBe('swapped');
|
|
60
|
+
expect(sorted.mintOneBytes[0]).toBe(2);
|
|
61
|
+
expect(sorted.mintTwoBytes[0]).toBe(9);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('derives pool, LP mint, and vault addresses deterministically', () => {
|
|
65
|
+
const addresses = deriveAmmPoolAddresses(thru, {
|
|
66
|
+
ammProgramAddress: deriveProgramAddress({
|
|
67
|
+
programAddress: key(1),
|
|
68
|
+
seed: key(2),
|
|
69
|
+
}).address,
|
|
70
|
+
mintAAddress: deriveProgramAddress({
|
|
71
|
+
programAddress: key(5),
|
|
72
|
+
seed: key(6),
|
|
73
|
+
}).address,
|
|
74
|
+
mintBAddress: deriveProgramAddress({
|
|
75
|
+
programAddress: key(7),
|
|
76
|
+
seed: key(8),
|
|
77
|
+
}).address,
|
|
78
|
+
swapFeeBps: 30,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const again = deriveAmmPoolAddresses(thru, {
|
|
82
|
+
ammProgramAddress: deriveProgramAddress({
|
|
83
|
+
programAddress: key(1),
|
|
84
|
+
seed: key(2),
|
|
85
|
+
}).address,
|
|
86
|
+
mintAAddress: deriveProgramAddress({
|
|
87
|
+
programAddress: key(5),
|
|
88
|
+
seed: key(6),
|
|
89
|
+
}).address,
|
|
90
|
+
mintBAddress: deriveProgramAddress({
|
|
91
|
+
programAddress: key(7),
|
|
92
|
+
seed: key(8),
|
|
93
|
+
}).address,
|
|
94
|
+
swapFeeBps: 30,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(addresses.poolAddress).toBe(again.poolAddress);
|
|
98
|
+
expect(addresses.lpMintSeed).toEqual(again.lpMintSeed);
|
|
99
|
+
expect(addresses.vaultOneSeed[0]).toBe(1);
|
|
100
|
+
expect(addresses.vaultTwoSeed[0]).toBe(2);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('packs init_pool as the C header layout', async () => {
|
|
104
|
+
const seed = new Uint8Array(32).fill(0xaa);
|
|
105
|
+
const poolProof = new Uint8Array([0x10, 0x11]);
|
|
106
|
+
const lpProof = new Uint8Array([0x20]);
|
|
107
|
+
const vaultOneProof = new Uint8Array([0x30, 0x31, 0x32]);
|
|
108
|
+
const vaultTwoProof = new Uint8Array([0x40]);
|
|
109
|
+
const accounts = {
|
|
110
|
+
payer: key(1),
|
|
111
|
+
pool: key(2),
|
|
112
|
+
lpMint: key(3),
|
|
113
|
+
vaultOne: key(4),
|
|
114
|
+
vaultTwo: key(5),
|
|
115
|
+
mintOne: key(6),
|
|
116
|
+
mintTwo: key(7),
|
|
117
|
+
tokenProgram: key(8),
|
|
118
|
+
};
|
|
119
|
+
const instruction = await createInitPoolInstruction({
|
|
120
|
+
payerAccountBytes: accounts.payer,
|
|
121
|
+
poolAccountBytes: accounts.pool,
|
|
122
|
+
lpMintAccountBytes: accounts.lpMint,
|
|
123
|
+
vaultOneAccountBytes: accounts.vaultOne,
|
|
124
|
+
vaultTwoAccountBytes: accounts.vaultTwo,
|
|
125
|
+
mintOneAccountBytes: accounts.mintOne,
|
|
126
|
+
mintTwoAccountBytes: accounts.mintTwo,
|
|
127
|
+
tokenProgramAccountBytes: accounts.tokenProgram,
|
|
128
|
+
swapFeeBps: 30,
|
|
129
|
+
lpMintSeed: seed,
|
|
130
|
+
poolStateProof: poolProof,
|
|
131
|
+
lpMintStateProof: lpProof,
|
|
132
|
+
vaultOneStateProof: vaultOneProof,
|
|
133
|
+
vaultTwoStateProof: vaultTwoProof,
|
|
134
|
+
})(context({
|
|
135
|
+
[bytesToHex(accounts.payer)]: 0,
|
|
136
|
+
[bytesToHex(accounts.pool)]: 2,
|
|
137
|
+
[bytesToHex(accounts.lpMint)]: 3,
|
|
138
|
+
[bytesToHex(accounts.vaultOne)]: 4,
|
|
139
|
+
[bytesToHex(accounts.vaultTwo)]: 5,
|
|
140
|
+
[bytesToHex(accounts.mintOne)]: 6,
|
|
141
|
+
[bytesToHex(accounts.mintTwo)]: 7,
|
|
142
|
+
[bytesToHex(accounts.tokenProgram)]: 8,
|
|
143
|
+
}));
|
|
144
|
+
|
|
145
|
+
expect(instruction.length).toBe(4 + 82 + 7);
|
|
146
|
+
expect(Array.from(instruction.slice(0, 22))).toEqual([
|
|
147
|
+
AMM_INSTRUCTION_INIT_POOL, 0, 0, 0,
|
|
148
|
+
0, 0,
|
|
149
|
+
2, 0,
|
|
150
|
+
3, 0,
|
|
151
|
+
4, 0,
|
|
152
|
+
5, 0,
|
|
153
|
+
6, 0,
|
|
154
|
+
7, 0,
|
|
155
|
+
8, 0,
|
|
156
|
+
30, 0,
|
|
157
|
+
]);
|
|
158
|
+
expect(instruction.slice(22, 54)).toEqual(seed);
|
|
159
|
+
expect(Array.from(instruction.slice(54, 86))).toEqual([
|
|
160
|
+
2, 0, 0, 0, 0, 0, 0, 0,
|
|
161
|
+
1, 0, 0, 0, 0, 0, 0, 0,
|
|
162
|
+
3, 0, 0, 0, 0, 0, 0, 0,
|
|
163
|
+
1, 0, 0, 0, 0, 0, 0, 0,
|
|
164
|
+
]);
|
|
165
|
+
expect(Array.from(instruction.slice(86))).toEqual([
|
|
166
|
+
...poolProof,
|
|
167
|
+
...lpProof,
|
|
168
|
+
...vaultOneProof,
|
|
169
|
+
...vaultTwoProof,
|
|
170
|
+
]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('packs add_liquidity and swap instructions', async () => {
|
|
174
|
+
const accounts = Array.from({ length: 10 }, (_, idx) => key(idx + 1));
|
|
175
|
+
const indexes = Object.fromEntries(accounts.map((account, idx) => [bytesToHex(account), idx + 2]));
|
|
176
|
+
const addLiquidity = await createAddLiquidityInstruction({
|
|
177
|
+
poolAccountBytes: accounts[0],
|
|
178
|
+
depositorAccountBytes: accounts[1],
|
|
179
|
+
depositorTokenOneAccountBytes: accounts[2],
|
|
180
|
+
depositorTokenTwoAccountBytes: accounts[3],
|
|
181
|
+
depositorLpAccountBytes: accounts[4],
|
|
182
|
+
vaultOneAccountBytes: accounts[5],
|
|
183
|
+
vaultTwoAccountBytes: accounts[6],
|
|
184
|
+
lpMintAccountBytes: accounts[7],
|
|
185
|
+
tokenProgramAccountBytes: accounts[8],
|
|
186
|
+
maxAmountMintOne: 5_000_000n,
|
|
187
|
+
maxAmountMintTwo: 10_000_000n,
|
|
188
|
+
})(context(indexes));
|
|
189
|
+
const swap = await createSwapInstruction({
|
|
190
|
+
poolAccountBytes: accounts[0],
|
|
191
|
+
userTransferAuthorityBytes: accounts[1],
|
|
192
|
+
userInputAccountBytes: accounts[2],
|
|
193
|
+
userOutputAccountBytes: accounts[3],
|
|
194
|
+
vaultInputAccountBytes: accounts[4],
|
|
195
|
+
vaultOutputAccountBytes: accounts[5],
|
|
196
|
+
lpMintAccountBytes: accounts[6],
|
|
197
|
+
tokenProgramAccountBytes: accounts[7],
|
|
198
|
+
amountIn: 1_234_567n,
|
|
199
|
+
})(context(indexes));
|
|
200
|
+
|
|
201
|
+
expect(addLiquidity.length).toBe(38);
|
|
202
|
+
expect(swap.length).toBe(28);
|
|
203
|
+
expect(Array.from(addLiquidity.slice(0, 4))).toEqual([
|
|
204
|
+
AMM_INSTRUCTION_ADD_LIQUIDITY, 0, 0, 0,
|
|
205
|
+
]);
|
|
206
|
+
expect(Array.from(swap.slice(0, 4))).toEqual([
|
|
207
|
+
AMM_INSTRUCTION_SWAP, 0, 0, 0,
|
|
208
|
+
]);
|
|
209
|
+
expect(new DataView(addLiquidity.buffer).getBigUint64(22, true)).toBe(5_000_000n);
|
|
210
|
+
expect(new DataView(addLiquidity.buffer).getBigUint64(30, true)).toBe(10_000_000n);
|
|
211
|
+
expect(new DataView(swap.buffer).getBigUint64(20, true)).toBe(1_234_567n);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('parses pool metadata', () => {
|
|
215
|
+
const data = new Uint8Array(AMM_POOL_METADATA_SIZE);
|
|
216
|
+
const view = new DataView(data.buffer);
|
|
217
|
+
view.setUint8(0, 1);
|
|
218
|
+
view.setBigUint64(1, 1_000_000_000n, true);
|
|
219
|
+
view.setUint16(9, 30, true);
|
|
220
|
+
data.set(key(11), 11);
|
|
221
|
+
data.set(key(12), 43);
|
|
222
|
+
data.set(key(13), 75);
|
|
223
|
+
data.set(key(14), 107);
|
|
224
|
+
data.set(key(15), 139);
|
|
225
|
+
data.set(key(16), 171);
|
|
226
|
+
|
|
227
|
+
const parsed = parseAmmPoolMetadata(data);
|
|
228
|
+
expect(parsed.isInitialized).toBe(true);
|
|
229
|
+
expect(parsed.lockedLpSupply).toBe(1_000_000_000n);
|
|
230
|
+
expect(parsed.swapFeeBps).toBe(30);
|
|
231
|
+
expect(parsed.swapPoolAuthority).toBe(encodeAddressForTest(key(11)));
|
|
232
|
+
expect(parsed.lpMint).toBe(encodeAddressForTest(key(16)));
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('quotes exact-in swaps with fee math matching the C program', () => {
|
|
236
|
+
const quote = quoteAmmSwapExactIn({
|
|
237
|
+
amountIn: 10_000_000n,
|
|
238
|
+
reserveIn: 1_000_000_000n,
|
|
239
|
+
reserveOut: 10_000_000n,
|
|
240
|
+
swapFeeBps: 30,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
expect(quote.feeAmount).toBe(30_000n);
|
|
244
|
+
expect(quote.amountInAfterFee).toBe(9_970_000n);
|
|
245
|
+
expect(quote.amountOut).toBe(98_715n);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
function encodeAddressForTest(bytes: Uint8Array): string {
|
|
250
|
+
return Pubkey.from(bytes).toThruFmt();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function bytesToHex(bytes: Uint8Array): string {
|
|
254
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
255
|
+
}
|
package/src/amm/index.ts
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { Pubkey } from '@thru/sdk';
|
|
2
|
+
import { encodeAddress } from '@thru/sdk/helpers';
|
|
3
|
+
import type { Account } from '@thru/sdk';
|
|
4
|
+
import type { Thru } from '@thru/sdk/client';
|
|
5
|
+
import { compareBytes } from '../helpers/bytes';
|
|
6
|
+
import {
|
|
7
|
+
AmmAddLiquidityInstructionBuilder,
|
|
8
|
+
AmmInitPoolInstructionBuilder,
|
|
9
|
+
AmmInstructionBuilder,
|
|
10
|
+
AmmPoolMetadata as AmmPoolMetadataView,
|
|
11
|
+
AmmSwapInstructionBuilder,
|
|
12
|
+
AmmWithdrawLiquidityInstructionBuilder,
|
|
13
|
+
} from './abi/thru/program/amm/types';
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
AmmAddLiquidityInstruction,
|
|
17
|
+
AmmAddLiquidityInstructionBuilder,
|
|
18
|
+
AmmError,
|
|
19
|
+
AmmErrorBuilder,
|
|
20
|
+
AmmEvent,
|
|
21
|
+
AmmEventBuilder,
|
|
22
|
+
AmmInitPoolInstruction,
|
|
23
|
+
AmmInitPoolInstructionBuilder,
|
|
24
|
+
AmmInstruction,
|
|
25
|
+
AmmInstructionBuilder,
|
|
26
|
+
AmmPoolMetadata as AmmPoolMetadataView,
|
|
27
|
+
AmmPoolMetadataBuilder,
|
|
28
|
+
AmmSwapInstruction,
|
|
29
|
+
AmmSwapInstructionBuilder,
|
|
30
|
+
AmmWithdrawLiquidityInstruction,
|
|
31
|
+
AmmWithdrawLiquidityInstructionBuilder,
|
|
32
|
+
BurnEventData,
|
|
33
|
+
BurnEventDataBuilder,
|
|
34
|
+
MintEventData,
|
|
35
|
+
MintEventDataBuilder,
|
|
36
|
+
PoolInitEventData,
|
|
37
|
+
PoolInitEventDataBuilder,
|
|
38
|
+
Seed32,
|
|
39
|
+
SwapEventData,
|
|
40
|
+
SwapEventDataBuilder,
|
|
41
|
+
SyncEventData,
|
|
42
|
+
SyncEventDataBuilder,
|
|
43
|
+
} from './abi/thru/program/amm/types';
|
|
44
|
+
|
|
45
|
+
export const AMM_INSTRUCTION_INIT_POOL = 0;
|
|
46
|
+
export const AMM_INSTRUCTION_ADD_LIQUIDITY = 1;
|
|
47
|
+
export const AMM_INSTRUCTION_WITHDRAW_LIQUIDITY = 2;
|
|
48
|
+
export const AMM_INSTRUCTION_SWAP = 3;
|
|
49
|
+
|
|
50
|
+
export const AMM_EVENT_POOL_INIT = 0;
|
|
51
|
+
export const AMM_EVENT_MINT = 1;
|
|
52
|
+
export const AMM_EVENT_BURN = 2;
|
|
53
|
+
export const AMM_EVENT_SWAP = 3;
|
|
54
|
+
export const AMM_EVENT_SYNC = 4;
|
|
55
|
+
|
|
56
|
+
export const AMM_BPS_DENOMINATOR = 10_000;
|
|
57
|
+
export const AMM_DEFAULT_SWAP_FEE_BPS = 30;
|
|
58
|
+
export const AMM_MAX_SWAP_FEE_BPS = 500;
|
|
59
|
+
export const AMM_MINIMUM_LIQUIDITY = 1_000n;
|
|
60
|
+
export const AMM_LP_DECIMALS = 6;
|
|
61
|
+
export const AMM_LP_SCALE = 1_000_000n;
|
|
62
|
+
export const AMM_POOL_METADATA_SIZE = 203;
|
|
63
|
+
export const AMM_PROGRAM_ADDRESS =
|
|
64
|
+
'taCnhMCcBZSJ8MDLGYBnTIRgmWxZx_jNjoNwOwzk1g4ST1';
|
|
65
|
+
|
|
66
|
+
type AmmInstructionVariant = 'init_pool' | 'add_liquidity' | 'withdraw_liquidity' | 'swap';
|
|
67
|
+
|
|
68
|
+
export const AMM_ERROR_LABELS: Record<number, string> = {
|
|
69
|
+
1: 'Invalid instruction data size',
|
|
70
|
+
2: 'Invalid instruction',
|
|
71
|
+
3: 'Account already initialized',
|
|
72
|
+
4: 'Account not initialized',
|
|
73
|
+
5: 'Invalid account index',
|
|
74
|
+
6: 'Invalid mint ordering',
|
|
75
|
+
7: 'Unauthorized operation',
|
|
76
|
+
8: 'Pool create failed',
|
|
77
|
+
9: 'LP mint create failed',
|
|
78
|
+
10: 'Vault one create failed',
|
|
79
|
+
11: 'Vault two create failed',
|
|
80
|
+
12: 'Account resize failed',
|
|
81
|
+
13: 'Account set writable failed',
|
|
82
|
+
14: 'LP mint init failed',
|
|
83
|
+
15: 'Vault one init failed',
|
|
84
|
+
16: 'Vault two init failed',
|
|
85
|
+
17: 'Liquidity bounds',
|
|
86
|
+
18: 'Vault mismatch',
|
|
87
|
+
19: 'LP mint mismatch',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const PUBKEY_LENGTH = 32;
|
|
91
|
+
const LP_MINT_SEED_LABEL = asciiBytes('lp_mint');
|
|
92
|
+
export const AMM_VAULT_ONE_SEED = oneByteSeed(0x01);
|
|
93
|
+
export const AMM_VAULT_TWO_SEED = oneByteSeed(0x02);
|
|
94
|
+
|
|
95
|
+
export type AccountLookupContext = {
|
|
96
|
+
getAccountIndex: (pubkey: Uint8Array) => number;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type InstructionData = (context: AccountLookupContext) => Promise<Uint8Array>;
|
|
100
|
+
|
|
101
|
+
export interface SortedAmmMints {
|
|
102
|
+
mintOneBytes: Uint8Array;
|
|
103
|
+
mintTwoBytes: Uint8Array;
|
|
104
|
+
mintOneAddress: string;
|
|
105
|
+
mintTwoAddress: string;
|
|
106
|
+
inputOrder: 'already-sorted' | 'swapped';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface AmmPoolAddresses {
|
|
110
|
+
mintOneAddress: string;
|
|
111
|
+
mintTwoAddress: string;
|
|
112
|
+
mintOneBytes: Uint8Array;
|
|
113
|
+
mintTwoBytes: Uint8Array;
|
|
114
|
+
poolAddress: string;
|
|
115
|
+
poolBytes: Uint8Array;
|
|
116
|
+
poolSeed: Uint8Array;
|
|
117
|
+
lpMintSeed: Uint8Array;
|
|
118
|
+
vaultOneSeed: Uint8Array;
|
|
119
|
+
vaultTwoSeed: Uint8Array;
|
|
120
|
+
swapFeeBps: number;
|
|
121
|
+
inputOrder: 'already-sorted' | 'swapped';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface AmmPoolMetadata {
|
|
125
|
+
isInitialized: boolean;
|
|
126
|
+
lockedLpSupply: bigint;
|
|
127
|
+
swapFeeBps: number;
|
|
128
|
+
swapPoolAuthority: string;
|
|
129
|
+
mintOne: string;
|
|
130
|
+
mintTwo: string;
|
|
131
|
+
vaultOne: string;
|
|
132
|
+
vaultTwo: string;
|
|
133
|
+
lpMint: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface AmmQuoteExactIn {
|
|
137
|
+
amountIn: bigint;
|
|
138
|
+
amountInAfterFee: bigint;
|
|
139
|
+
feeAmount: bigint;
|
|
140
|
+
amountOut: bigint;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface InitPoolArgs {
|
|
144
|
+
payerAccountBytes: Uint8Array;
|
|
145
|
+
poolAccountBytes: Uint8Array;
|
|
146
|
+
lpMintAccountBytes: Uint8Array;
|
|
147
|
+
vaultOneAccountBytes: Uint8Array;
|
|
148
|
+
vaultTwoAccountBytes: Uint8Array;
|
|
149
|
+
mintOneAccountBytes: Uint8Array;
|
|
150
|
+
mintTwoAccountBytes: Uint8Array;
|
|
151
|
+
tokenProgramAccountBytes: Uint8Array;
|
|
152
|
+
swapFeeBps: number;
|
|
153
|
+
lpMintSeed: Uint8Array;
|
|
154
|
+
poolStateProof: Uint8Array;
|
|
155
|
+
lpMintStateProof: Uint8Array;
|
|
156
|
+
vaultOneStateProof: Uint8Array;
|
|
157
|
+
vaultTwoStateProof: Uint8Array;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface AddLiquidityArgs {
|
|
161
|
+
poolAccountBytes: Uint8Array;
|
|
162
|
+
depositorAccountBytes: Uint8Array;
|
|
163
|
+
depositorTokenOneAccountBytes: Uint8Array;
|
|
164
|
+
depositorTokenTwoAccountBytes: Uint8Array;
|
|
165
|
+
depositorLpAccountBytes: Uint8Array;
|
|
166
|
+
vaultOneAccountBytes: Uint8Array;
|
|
167
|
+
vaultTwoAccountBytes: Uint8Array;
|
|
168
|
+
lpMintAccountBytes: Uint8Array;
|
|
169
|
+
tokenProgramAccountBytes: Uint8Array;
|
|
170
|
+
maxAmountMintOne: bigint;
|
|
171
|
+
maxAmountMintTwo: bigint;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface WithdrawLiquidityArgs {
|
|
175
|
+
poolAccountBytes: Uint8Array;
|
|
176
|
+
withdrawerAccountBytes: Uint8Array;
|
|
177
|
+
withdrawerTokenOneAccountBytes: Uint8Array;
|
|
178
|
+
withdrawerTokenTwoAccountBytes: Uint8Array;
|
|
179
|
+
withdrawerLpAccountBytes: Uint8Array;
|
|
180
|
+
vaultOneAccountBytes: Uint8Array;
|
|
181
|
+
vaultTwoAccountBytes: Uint8Array;
|
|
182
|
+
lpMintAccountBytes: Uint8Array;
|
|
183
|
+
tokenProgramAccountBytes: Uint8Array;
|
|
184
|
+
lpAmount: bigint;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface SwapArgs {
|
|
188
|
+
poolAccountBytes: Uint8Array;
|
|
189
|
+
userTransferAuthorityBytes: Uint8Array;
|
|
190
|
+
userInputAccountBytes: Uint8Array;
|
|
191
|
+
userOutputAccountBytes: Uint8Array;
|
|
192
|
+
vaultInputAccountBytes: Uint8Array;
|
|
193
|
+
vaultOutputAccountBytes: Uint8Array;
|
|
194
|
+
lpMintAccountBytes: Uint8Array;
|
|
195
|
+
tokenProgramAccountBytes: Uint8Array;
|
|
196
|
+
amountIn: bigint;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function sortAmmMints(
|
|
200
|
+
mintA: Uint8Array | string,
|
|
201
|
+
mintB: Uint8Array | string
|
|
202
|
+
): SortedAmmMints {
|
|
203
|
+
const mintABytes = toPubkeyBytes(mintA);
|
|
204
|
+
const mintBBytes = toPubkeyBytes(mintB);
|
|
205
|
+
const comparison = compareBytes(mintABytes, mintBBytes);
|
|
206
|
+
if (comparison === 0) throw new Error('AMM mints must be distinct');
|
|
207
|
+
const alreadySorted = comparison < 0;
|
|
208
|
+
const mintOneBytes = alreadySorted ? mintABytes : mintBBytes;
|
|
209
|
+
const mintTwoBytes = alreadySorted ? mintBBytes : mintABytes;
|
|
210
|
+
return {
|
|
211
|
+
mintOneBytes,
|
|
212
|
+
mintTwoBytes,
|
|
213
|
+
mintOneAddress: encodeAddress(mintOneBytes),
|
|
214
|
+
mintTwoAddress: encodeAddress(mintTwoBytes),
|
|
215
|
+
inputOrder: alreadySorted ? 'already-sorted' : 'swapped',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function deriveAmmLpMintSeed(thru: Thru, poolBytes: Uint8Array | string): Uint8Array {
|
|
220
|
+
return thru.helpers.deriveAddress([
|
|
221
|
+
toPubkeyBytes(poolBytes),
|
|
222
|
+
LP_MINT_SEED_LABEL,
|
|
223
|
+
]).bytes;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function deriveAmmPoolAddresses(
|
|
227
|
+
thru: Thru,
|
|
228
|
+
args: {
|
|
229
|
+
ammProgramAddress: string;
|
|
230
|
+
mintAAddress: string;
|
|
231
|
+
mintBAddress: string;
|
|
232
|
+
swapFeeBps?: number;
|
|
233
|
+
}
|
|
234
|
+
): AmmPoolAddresses {
|
|
235
|
+
const swapFeeBps = args.swapFeeBps ?? AMM_DEFAULT_SWAP_FEE_BPS;
|
|
236
|
+
assertU16(swapFeeBps, 'swapFeeBps');
|
|
237
|
+
if (swapFeeBps <= 0 || swapFeeBps > AMM_MAX_SWAP_FEE_BPS) {
|
|
238
|
+
throw new Error(`swapFeeBps must be 1-${AMM_MAX_SWAP_FEE_BPS}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const sorted = sortAmmMints(args.mintAAddress, args.mintBAddress);
|
|
242
|
+
const feeBytes = new Uint8Array(2);
|
|
243
|
+
new DataView(feeBytes.buffer).setUint16(0, swapFeeBps, true);
|
|
244
|
+
const poolSeed = thru.helpers.deriveAddress([
|
|
245
|
+
sorted.mintOneBytes,
|
|
246
|
+
sorted.mintTwoBytes,
|
|
247
|
+
feeBytes,
|
|
248
|
+
]).bytes;
|
|
249
|
+
const pool = thru.helpers.deriveProgramAddress({
|
|
250
|
+
programAddress: args.ammProgramAddress,
|
|
251
|
+
seed: poolSeed,
|
|
252
|
+
ephemeral: false,
|
|
253
|
+
});
|
|
254
|
+
const lpMintSeed = deriveAmmLpMintSeed(thru, pool.bytes);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
...sorted,
|
|
258
|
+
poolAddress: pool.address,
|
|
259
|
+
poolBytes: pool.bytes,
|
|
260
|
+
poolSeed,
|
|
261
|
+
lpMintSeed,
|
|
262
|
+
vaultOneSeed: AMM_VAULT_ONE_SEED.slice(),
|
|
263
|
+
vaultTwoSeed: AMM_VAULT_TWO_SEED.slice(),
|
|
264
|
+
swapFeeBps,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function createInitPoolInstruction(args: InitPoolArgs): InstructionData {
|
|
269
|
+
assertU16(args.swapFeeBps, 'swapFeeBps');
|
|
270
|
+
const lpMintSeed = Pubkey.from(args.lpMintSeed).toBytes();
|
|
271
|
+
return async (context: AccountLookupContext): Promise<Uint8Array> => {
|
|
272
|
+
const builder = new AmmInitPoolInstructionBuilder()
|
|
273
|
+
.set_payer_account_idx(accountIndex(context, args.payerAccountBytes))
|
|
274
|
+
.set_pool_account_idx(accountIndex(context, args.poolAccountBytes))
|
|
275
|
+
.set_lp_mint_account_idx(accountIndex(context, args.lpMintAccountBytes))
|
|
276
|
+
.set_vault_one_account_idx(accountIndex(context, args.vaultOneAccountBytes))
|
|
277
|
+
.set_vault_two_account_idx(accountIndex(context, args.vaultTwoAccountBytes))
|
|
278
|
+
.set_mint_one_account_idx(accountIndex(context, args.mintOneAccountBytes))
|
|
279
|
+
.set_mint_two_account_idx(accountIndex(context, args.mintTwoAccountBytes))
|
|
280
|
+
.set_token_program_account_idx(accountIndex(context, args.tokenProgramAccountBytes))
|
|
281
|
+
.set_swap_fee_bps(args.swapFeeBps)
|
|
282
|
+
.set_lp_mint_seed(lpMintSeed);
|
|
283
|
+
builder.pool_proof().write(args.poolStateProof).finish();
|
|
284
|
+
builder.lp_mint_proof().write(args.lpMintStateProof).finish();
|
|
285
|
+
builder.vault_one_proof().write(args.vaultOneStateProof).finish();
|
|
286
|
+
builder.vault_two_proof().write(args.vaultTwoStateProof).finish();
|
|
287
|
+
return buildAmmInstruction('init_pool', builder.build());
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function createAddLiquidityInstruction(args: AddLiquidityArgs): InstructionData {
|
|
292
|
+
return async (context: AccountLookupContext): Promise<Uint8Array> => {
|
|
293
|
+
assertU64(args.maxAmountMintOne, 'maxAmountMintOne');
|
|
294
|
+
assertU64(args.maxAmountMintTwo, 'maxAmountMintTwo');
|
|
295
|
+
const payload = new AmmAddLiquidityInstructionBuilder()
|
|
296
|
+
.set_pool_account_idx(accountIndex(context, args.poolAccountBytes))
|
|
297
|
+
.set_depositor_account_idx(accountIndex(context, args.depositorAccountBytes))
|
|
298
|
+
.set_depositor_token_one_account_idx(accountIndex(context, args.depositorTokenOneAccountBytes))
|
|
299
|
+
.set_depositor_token_two_account_idx(accountIndex(context, args.depositorTokenTwoAccountBytes))
|
|
300
|
+
.set_depositor_lp_account_idx(accountIndex(context, args.depositorLpAccountBytes))
|
|
301
|
+
.set_vault_one_account_idx(accountIndex(context, args.vaultOneAccountBytes))
|
|
302
|
+
.set_vault_two_account_idx(accountIndex(context, args.vaultTwoAccountBytes))
|
|
303
|
+
.set_lp_mint_account_idx(accountIndex(context, args.lpMintAccountBytes))
|
|
304
|
+
.set_token_program_account_idx(accountIndex(context, args.tokenProgramAccountBytes))
|
|
305
|
+
.set_max_amount_mint_one(args.maxAmountMintOne)
|
|
306
|
+
.set_max_amount_mint_two(args.maxAmountMintTwo)
|
|
307
|
+
.build();
|
|
308
|
+
return buildAmmInstruction('add_liquidity', payload);
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function createWithdrawLiquidityInstruction(args: WithdrawLiquidityArgs): InstructionData {
|
|
313
|
+
return async (context: AccountLookupContext): Promise<Uint8Array> => {
|
|
314
|
+
assertU64(args.lpAmount, 'lpAmount');
|
|
315
|
+
const payload = new AmmWithdrawLiquidityInstructionBuilder()
|
|
316
|
+
.set_pool_account_idx(accountIndex(context, args.poolAccountBytes))
|
|
317
|
+
.set_withdrawer_account_idx(accountIndex(context, args.withdrawerAccountBytes))
|
|
318
|
+
.set_withdrawer_token_one_account_idx(accountIndex(context, args.withdrawerTokenOneAccountBytes))
|
|
319
|
+
.set_withdrawer_token_two_account_idx(accountIndex(context, args.withdrawerTokenTwoAccountBytes))
|
|
320
|
+
.set_withdrawer_lp_account_idx(accountIndex(context, args.withdrawerLpAccountBytes))
|
|
321
|
+
.set_vault_one_account_idx(accountIndex(context, args.vaultOneAccountBytes))
|
|
322
|
+
.set_vault_two_account_idx(accountIndex(context, args.vaultTwoAccountBytes))
|
|
323
|
+
.set_lp_mint_account_idx(accountIndex(context, args.lpMintAccountBytes))
|
|
324
|
+
.set_token_program_account_idx(accountIndex(context, args.tokenProgramAccountBytes))
|
|
325
|
+
.set_lp_amount(args.lpAmount)
|
|
326
|
+
.build();
|
|
327
|
+
return buildAmmInstruction('withdraw_liquidity', payload);
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function createSwapInstruction(args: SwapArgs): InstructionData {
|
|
332
|
+
return async (context: AccountLookupContext): Promise<Uint8Array> => {
|
|
333
|
+
assertU64(args.amountIn, 'amountIn');
|
|
334
|
+
const payload = new AmmSwapInstructionBuilder()
|
|
335
|
+
.set_pool_account_idx(accountIndex(context, args.poolAccountBytes))
|
|
336
|
+
.set_user_transfer_authority_idx(accountIndex(context, args.userTransferAuthorityBytes))
|
|
337
|
+
.set_user_input_account_idx(accountIndex(context, args.userInputAccountBytes))
|
|
338
|
+
.set_user_output_account_idx(accountIndex(context, args.userOutputAccountBytes))
|
|
339
|
+
.set_vault_input_account_idx(accountIndex(context, args.vaultInputAccountBytes))
|
|
340
|
+
.set_vault_output_account_idx(accountIndex(context, args.vaultOutputAccountBytes))
|
|
341
|
+
.set_lp_mint_account_idx(accountIndex(context, args.lpMintAccountBytes))
|
|
342
|
+
.set_token_program_account_idx(accountIndex(context, args.tokenProgramAccountBytes))
|
|
343
|
+
.set_amount_in(args.amountIn)
|
|
344
|
+
.build();
|
|
345
|
+
return buildAmmInstruction('swap', payload);
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function parseAmmPoolMetadata(accountOrData: Account | Uint8Array): AmmPoolMetadata {
|
|
350
|
+
const data = accountOrData instanceof Uint8Array ? accountOrData : accountOrData.data?.data;
|
|
351
|
+
if (!data) throw new Error('AMM pool account data is missing');
|
|
352
|
+
if (data.length < AMM_POOL_METADATA_SIZE) throw new Error('AMM pool account data is malformed');
|
|
353
|
+
|
|
354
|
+
const metadata = AmmPoolMetadataView.from_array(data);
|
|
355
|
+
if (!metadata) throw new Error('AMM pool account data is malformed');
|
|
356
|
+
return {
|
|
357
|
+
isInitialized: metadata.get_is_initialized() !== 0,
|
|
358
|
+
lockedLpSupply: metadata.get_locked_lp_supply(),
|
|
359
|
+
swapFeeBps: metadata.get_swap_fee_bps(),
|
|
360
|
+
swapPoolAuthority: pubkeyViewToAddress(metadata.get_swap_pool_authority()),
|
|
361
|
+
mintOne: pubkeyViewToAddress(metadata.get_mint_one()),
|
|
362
|
+
mintTwo: pubkeyViewToAddress(metadata.get_mint_two()),
|
|
363
|
+
vaultOne: pubkeyViewToAddress(metadata.get_vault_one()),
|
|
364
|
+
vaultTwo: pubkeyViewToAddress(metadata.get_vault_two()),
|
|
365
|
+
lpMint: pubkeyViewToAddress(metadata.get_lp_mint()),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function quoteAmmSwapExactIn(args: {
|
|
370
|
+
amountIn: bigint;
|
|
371
|
+
reserveIn: bigint;
|
|
372
|
+
reserveOut: bigint;
|
|
373
|
+
swapFeeBps: number;
|
|
374
|
+
}): AmmQuoteExactIn {
|
|
375
|
+
if (args.amountIn <= 0n) throw new Error('amountIn must be positive');
|
|
376
|
+
if (args.reserveIn <= 0n || args.reserveOut <= 0n) {
|
|
377
|
+
throw new Error('reserves must be positive');
|
|
378
|
+
}
|
|
379
|
+
assertU16(args.swapFeeBps, 'swapFeeBps');
|
|
380
|
+
const feeAmount = (args.amountIn * BigInt(args.swapFeeBps)) / BigInt(AMM_BPS_DENOMINATOR);
|
|
381
|
+
const amountInAfterFee = args.amountIn - feeAmount;
|
|
382
|
+
if (amountInAfterFee <= 0n) throw new Error('amountIn is fully consumed by fees');
|
|
383
|
+
const amountOut = (amountInAfterFee * args.reserveOut) / (args.reserveIn + amountInAfterFee);
|
|
384
|
+
return {
|
|
385
|
+
amountIn: args.amountIn,
|
|
386
|
+
amountInAfterFee,
|
|
387
|
+
feeAmount,
|
|
388
|
+
amountOut,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function toPubkeyBytes(value: Uint8Array | string): Uint8Array {
|
|
393
|
+
return Pubkey.from(value).toBytes();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function assertU16(value: number, label: string): void {
|
|
397
|
+
if (!Number.isInteger(value) || value < 0 || value > 0xffff) {
|
|
398
|
+
throw new Error(`${label} must be an integer between 0 and 65535`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function assertU64(value: bigint, label: string): void {
|
|
403
|
+
if (value < 0n || value > 0xffffffffffffffffn) {
|
|
404
|
+
throw new Error(`${label} must be between 0 and 18446744073709551615`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function accountIndex(context: AccountLookupContext, pubkey: Uint8Array): number {
|
|
409
|
+
const index = context.getAccountIndex(pubkey);
|
|
410
|
+
assertU16(index, 'account index');
|
|
411
|
+
return index;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function buildAmmInstruction(variant: AmmInstructionVariant, payload: Uint8Array): Uint8Array {
|
|
415
|
+
const builder = new AmmInstructionBuilder();
|
|
416
|
+
builder.payload().select(variant).writePayload(payload).finish();
|
|
417
|
+
return builder.build();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function pubkeyViewToAddress(pubkey: unknown): string {
|
|
421
|
+
const buffer = (pubkey as { buffer?: Uint8Array }).buffer;
|
|
422
|
+
if (!buffer) throw new Error('generated Pubkey view did not expose a buffer');
|
|
423
|
+
return encodeAddress(buffer);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function asciiBytes(value: string): Uint8Array {
|
|
427
|
+
return new TextEncoder().encode(value);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function oneByteSeed(value: number): Uint8Array {
|
|
431
|
+
const seed = new Uint8Array(PUBKEY_LENGTH);
|
|
432
|
+
seed[0] = value;
|
|
433
|
+
return seed;
|
|
434
|
+
}
|