@dexterai/vault 0.4.1 → 0.5.0

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.
@@ -0,0 +1,79 @@
1
+ import { Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
2
+ export { getRpc, kitInstructionsToWeb3 } from '../kit/index.cjs';
3
+
4
+ /**
5
+ * Factoring split math — pure, no chain dependencies.
6
+ *
7
+ * A LockedClaim of `claimAmount` is settled by its holder (the financier).
8
+ * Factoring routes most of that to the SELLER (instant cash now) and keeps a
9
+ * `financierSpread` for the financier — payment-certainty pricing, NOT interest
10
+ * (short claims earn ~nothing on APR; the spread is a clearing/liquidity fee).
11
+ *
12
+ * INVARIANT: sellerReceives + financierSpread === claimAmount, exactly. The two
13
+ * inner SignV2 transfers source from the swig-wallet ATA and together cannot
14
+ * exceed what the claim reserved, so they must sum to the claim amount.
15
+ *
16
+ * The SDK does NOT decide the spread — the caller (operator policy, e.g.
17
+ * dexter-api) supplies it. This keeps the SDK a neutral mechanism.
18
+ */
19
+ interface FactoringSplitParams {
20
+ /** The full LockedClaim amount, atomic units (USDC = 6 decimals). */
21
+ claimAmount: bigint;
22
+ /** The financier's spread, atomic units. 0 ≤ spread ≤ claimAmount. */
23
+ financierSpread: bigint;
24
+ }
25
+ interface FactoringSplit {
26
+ /** What the seller receives now (claimAmount - financierSpread). */
27
+ sellerReceives: bigint;
28
+ /** The financier's spread, echoed back for the transfer builder. */
29
+ financierSpread: bigint;
30
+ }
31
+ declare function computeFactoringSplit(p: FactoringSplitParams): FactoringSplit;
32
+
33
+ /**
34
+ * Instant-payout (factoring) — full atomic transaction assembly.
35
+ *
36
+ * [0] vault::settle_locked_voucher (financier = holder; validates + mutates)
37
+ * [1] swig::SignV2(TransferChecked × {1 or 2}) (sourced from swig_wallet_ata)
38
+ * - sellerReceives → sellerAta
39
+ * - financierSpread → financierAta (omitted when spread === 0)
40
+ *
41
+ * The default `assembleSignV2` wires the real @swig-wallet/kit + @solana-program/token
42
+ * path (mirrors dexter-api buildFinalizeWithdrawExtra). It's injectable so the
43
+ * composition is unit-testable without live swig state.
44
+ */
45
+
46
+ interface InstantTransfer {
47
+ destinationAta: PublicKey;
48
+ amount: bigint;
49
+ }
50
+ interface AssembleSignV2Args {
51
+ connection: Connection;
52
+ swigAddress: PublicKey;
53
+ feePayer: PublicKey;
54
+ /** The single preceding instruction Swig ProgramExec authenticates against. */
55
+ settleIx: TransactionInstruction;
56
+ transfers: InstantTransfer[];
57
+ }
58
+ type AssembleSignV2 = (args: AssembleSignV2Args) => Promise<TransactionInstruction[]>;
59
+ interface InstantPayoutParams {
60
+ connection: Connection;
61
+ swigAddress: PublicKey;
62
+ claimPda: PublicKey;
63
+ vaultPda: PublicKey;
64
+ /** The current claim holder collecting — the financier. Signs settle. */
65
+ financier: PublicKey;
66
+ dexterAuthority: PublicKey;
67
+ claimAmount: bigint;
68
+ /** Operator-supplied spread. 0 ≤ spread ≤ claimAmount. */
69
+ financierSpread: bigint;
70
+ sellerAta: PublicKey;
71
+ financierAta: PublicKey;
72
+ /** Pays ATA rent / tx fees in the SignV2 build. */
73
+ feePayer: PublicKey;
74
+ /** Injectable for unit tests; defaults to the real swig-kit assembler. */
75
+ assembleSignV2?: AssembleSignV2;
76
+ }
77
+ declare function buildInstantPayoutInstructions(p: InstantPayoutParams): Promise<TransactionInstruction[]>;
78
+
79
+ export { type AssembleSignV2, type AssembleSignV2Args, type FactoringSplit, type FactoringSplitParams, type InstantPayoutParams, type InstantTransfer, buildInstantPayoutInstructions, computeFactoringSplit };
@@ -0,0 +1,79 @@
1
+ import { Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
2
+ export { getRpc, kitInstructionsToWeb3 } from '../kit/index.js';
3
+
4
+ /**
5
+ * Factoring split math — pure, no chain dependencies.
6
+ *
7
+ * A LockedClaim of `claimAmount` is settled by its holder (the financier).
8
+ * Factoring routes most of that to the SELLER (instant cash now) and keeps a
9
+ * `financierSpread` for the financier — payment-certainty pricing, NOT interest
10
+ * (short claims earn ~nothing on APR; the spread is a clearing/liquidity fee).
11
+ *
12
+ * INVARIANT: sellerReceives + financierSpread === claimAmount, exactly. The two
13
+ * inner SignV2 transfers source from the swig-wallet ATA and together cannot
14
+ * exceed what the claim reserved, so they must sum to the claim amount.
15
+ *
16
+ * The SDK does NOT decide the spread — the caller (operator policy, e.g.
17
+ * dexter-api) supplies it. This keeps the SDK a neutral mechanism.
18
+ */
19
+ interface FactoringSplitParams {
20
+ /** The full LockedClaim amount, atomic units (USDC = 6 decimals). */
21
+ claimAmount: bigint;
22
+ /** The financier's spread, atomic units. 0 ≤ spread ≤ claimAmount. */
23
+ financierSpread: bigint;
24
+ }
25
+ interface FactoringSplit {
26
+ /** What the seller receives now (claimAmount - financierSpread). */
27
+ sellerReceives: bigint;
28
+ /** The financier's spread, echoed back for the transfer builder. */
29
+ financierSpread: bigint;
30
+ }
31
+ declare function computeFactoringSplit(p: FactoringSplitParams): FactoringSplit;
32
+
33
+ /**
34
+ * Instant-payout (factoring) — full atomic transaction assembly.
35
+ *
36
+ * [0] vault::settle_locked_voucher (financier = holder; validates + mutates)
37
+ * [1] swig::SignV2(TransferChecked × {1 or 2}) (sourced from swig_wallet_ata)
38
+ * - sellerReceives → sellerAta
39
+ * - financierSpread → financierAta (omitted when spread === 0)
40
+ *
41
+ * The default `assembleSignV2` wires the real @swig-wallet/kit + @solana-program/token
42
+ * path (mirrors dexter-api buildFinalizeWithdrawExtra). It's injectable so the
43
+ * composition is unit-testable without live swig state.
44
+ */
45
+
46
+ interface InstantTransfer {
47
+ destinationAta: PublicKey;
48
+ amount: bigint;
49
+ }
50
+ interface AssembleSignV2Args {
51
+ connection: Connection;
52
+ swigAddress: PublicKey;
53
+ feePayer: PublicKey;
54
+ /** The single preceding instruction Swig ProgramExec authenticates against. */
55
+ settleIx: TransactionInstruction;
56
+ transfers: InstantTransfer[];
57
+ }
58
+ type AssembleSignV2 = (args: AssembleSignV2Args) => Promise<TransactionInstruction[]>;
59
+ interface InstantPayoutParams {
60
+ connection: Connection;
61
+ swigAddress: PublicKey;
62
+ claimPda: PublicKey;
63
+ vaultPda: PublicKey;
64
+ /** The current claim holder collecting — the financier. Signs settle. */
65
+ financier: PublicKey;
66
+ dexterAuthority: PublicKey;
67
+ claimAmount: bigint;
68
+ /** Operator-supplied spread. 0 ≤ spread ≤ claimAmount. */
69
+ financierSpread: bigint;
70
+ sellerAta: PublicKey;
71
+ financierAta: PublicKey;
72
+ /** Pays ATA rent / tx fees in the SignV2 build. */
73
+ feePayer: PublicKey;
74
+ /** Injectable for unit tests; defaults to the real swig-kit assembler. */
75
+ assembleSignV2?: AssembleSignV2;
76
+ }
77
+ declare function buildInstantPayoutInstructions(p: InstantPayoutParams): Promise<TransactionInstruction[]>;
78
+
79
+ export { type AssembleSignV2, type AssembleSignV2Args, type FactoringSplit, type FactoringSplitParams, type InstantPayoutParams, type InstantTransfer, buildInstantPayoutInstructions, computeFactoringSplit };
@@ -0,0 +1,220 @@
1
+ // src/factoring/split.ts
2
+ function computeFactoringSplit(p) {
3
+ if (p.claimAmount <= 0n) {
4
+ throw new Error(`factoring: claim amount must be positive, got ${p.claimAmount}`);
5
+ }
6
+ if (p.financierSpread < 0n) {
7
+ throw new Error(`factoring: spread must not be negative, got ${p.financierSpread}`);
8
+ }
9
+ if (p.financierSpread > p.claimAmount) {
10
+ throw new Error(`factoring: spread exceeds claim (${p.financierSpread} > ${p.claimAmount})`);
11
+ }
12
+ return {
13
+ sellerReceives: p.claimAmount - p.financierSpread,
14
+ financierSpread: p.financierSpread
15
+ };
16
+ }
17
+
18
+ // src/factoring/instantPayout.ts
19
+ import { PublicKey as PublicKey5 } from "@solana/web3.js";
20
+ import { fetchSwig, getSignInstructions, getSwigWalletAddress } from "@swig-wallet/kit";
21
+ import { address as kitAddress } from "@solana/kit";
22
+ import { getTransferCheckedInstruction } from "@solana-program/token";
23
+ import { getAssociatedTokenAddressSync } from "@solana/spl-token";
24
+
25
+ // src/instructions/lockedClaim.ts
26
+ import { PublicKey as PublicKey3, SystemProgram, TransactionInstruction as TransactionInstruction2 } from "@solana/web3.js";
27
+
28
+ // src/constants/index.ts
29
+ import { PublicKey } from "@solana/web3.js";
30
+ var DEXTER_VAULT_PROGRAM_ID = new PublicKey(
31
+ "Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc"
32
+ );
33
+ var SWIG_PROGRAM_ID = new PublicKey(
34
+ "swigypWHEksbC64pWKwah1WTeh9JXwx8H1rJHLdbQMB"
35
+ );
36
+ var SECP256R1_PROGRAM_ID = new PublicKey(
37
+ "Secp256r1SigVerify1111111111111111111111111"
38
+ );
39
+ var ED25519_PROGRAM_ID = new PublicKey(
40
+ "Ed25519SigVerify111111111111111111111111111"
41
+ );
42
+ var INSTRUCTIONS_SYSVAR_ID = new PublicKey(
43
+ "Sysvar1nstructions1111111111111111111111111"
44
+ );
45
+ var USDC_MAINNET = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
46
+ var VAULT_SEED_PREFIX = Buffer.from("vault");
47
+ var LOCKED_CLAIM_SEED = Buffer.from("locked-claim");
48
+ var DISCRIMINATORS = Object.freeze({
49
+ initialize_vault: Uint8Array.from([48, 191, 163, 44, 71, 129, 63, 164]),
50
+ set_swig: Uint8Array.from([253, 229, 89, 206, 192, 118, 137, 165]),
51
+ settle_voucher: Uint8Array.from([144, 176, 128, 220, 156, 79, 41, 54]),
52
+ request_withdrawal: Uint8Array.from([251, 85, 121, 205, 56, 201, 12, 177]),
53
+ finalize_withdrawal: Uint8Array.from([178, 87, 206, 68, 201, 186, 164, 232]),
54
+ force_release: Uint8Array.from([122, 190, 243, 252, 54, 202, 208, 234]),
55
+ rotate_passkey: Uint8Array.from([28, 134, 49, 89, 196, 34, 58, 174]),
56
+ rotate_dexter_authority: Uint8Array.from([145, 60, 4, 119, 180, 205, 236, 134]),
57
+ prove_passkey: Uint8Array.from([35, 175, 41, 143, 201, 118, 49, 184]),
58
+ settle_tab_voucher: Uint8Array.from([173, 22, 98, 31, 110, 129, 59, 161]),
59
+ register_session_key: Uint8Array.from([69, 94, 60, 44, 49, 199, 183, 233]),
60
+ revoke_session_key: Uint8Array.from([81, 192, 32, 110, 104, 116, 144, 151]),
61
+ lock_voucher: Uint8Array.from([91, 138, 5, 227, 119, 239, 48, 254]),
62
+ settle_locked_voucher: Uint8Array.from([44, 80, 216, 43, 247, 253, 101, 45]),
63
+ transfer_lock_ownership: Uint8Array.from([193, 13, 131, 134, 95, 25, 229, 157]),
64
+ recover_abandoned_lock: Uint8Array.from([169, 213, 107, 64, 229, 49, 43, 234]),
65
+ open_standby: Uint8Array.from([234, 184, 232, 135, 246, 191, 90, 250]),
66
+ draw_credit: Uint8Array.from([20, 84, 47, 211, 78, 117, 195, 210]),
67
+ repay_credit: Uint8Array.from([38, 113, 240, 182, 109, 179, 154, 245]),
68
+ seize_collateral: Uint8Array.from([40, 250, 7, 243, 168, 184, 116, 154]),
69
+ migrate_v4_to_v5: Uint8Array.from([226, 105, 140, 184, 101, 39, 235, 116])
70
+ });
71
+ var OTS_SESSION_REGISTER_V1_DOMAIN = (() => {
72
+ const buf = new Uint8Array(32);
73
+ buf.set(new TextEncoder().encode("OTS_SESSION_REGISTER_V1"), 0);
74
+ return buf;
75
+ })();
76
+ var OTS_SESSION_REGISTER_V2_DOMAIN = (() => {
77
+ const buf = new Uint8Array(32);
78
+ buf.set(new TextEncoder().encode("OTS_SESSION_REGISTER_V2"), 0);
79
+ return buf;
80
+ })();
81
+ var OTS_SESSION_REVOKE_V1_DOMAIN = (() => {
82
+ const buf = new Uint8Array(32);
83
+ buf.set(new TextEncoder().encode("OTS_SESSION_REVOKE_V1"), 0);
84
+ return buf;
85
+ })();
86
+
87
+ // src/instructions/withdraw.ts
88
+ import {
89
+ PublicKey as PublicKey2,
90
+ TransactionInstruction,
91
+ SYSVAR_INSTRUCTIONS_PUBKEY
92
+ } from "@solana/web3.js";
93
+ function deriveSwigWalletAddress(swigAddress) {
94
+ const [pda] = PublicKey2.findProgramAddressSync(
95
+ [Buffer.from("swig-wallet-address"), swigAddress.toBuffer()],
96
+ SWIG_PROGRAM_ID
97
+ );
98
+ return pda;
99
+ }
100
+
101
+ // src/instructions/lockedClaim.ts
102
+ function buildSettleLockedVoucherInstruction(p) {
103
+ const data = Buffer.from(DISCRIMINATORS.settle_locked_voucher);
104
+ const swigWalletAddress = deriveSwigWalletAddress(p.swigAddress);
105
+ return new TransactionInstruction2({
106
+ programId: DEXTER_VAULT_PROGRAM_ID,
107
+ keys: [
108
+ { pubkey: p.swigAddress, isSigner: false, isWritable: false },
109
+ { pubkey: swigWalletAddress, isSigner: false, isWritable: false },
110
+ { pubkey: p.claimPda, isSigner: false, isWritable: true },
111
+ { pubkey: p.vaultPda, isSigner: false, isWritable: true },
112
+ { pubkey: p.holder, isSigner: true, isWritable: false },
113
+ { pubkey: p.dexterAuthority, isSigner: true, isWritable: false }
114
+ ],
115
+ data
116
+ });
117
+ }
118
+
119
+ // src/kit/index.ts
120
+ import { PublicKey as PublicKey4, TransactionInstruction as TransactionInstruction3 } from "@solana/web3.js";
121
+ import { createSolanaRpc } from "@solana/kit";
122
+ function kitInstructionsToWeb3(kitInstructions) {
123
+ return kitInstructions.map((ix) => {
124
+ const accounts = (ix.accounts ?? []).map((acc) => {
125
+ const role = acc.role;
126
+ const hasBooleanShape = typeof acc.signer === "boolean" || typeof acc.writable === "boolean";
127
+ let isSigner = false;
128
+ let isWritable = false;
129
+ if (hasBooleanShape) {
130
+ isSigner = Boolean(acc.signer);
131
+ isWritable = Boolean(acc.writable);
132
+ } else if (typeof role === "number") {
133
+ isSigner = role >= 2;
134
+ isWritable = role % 2 === 1;
135
+ } else if (typeof role === "string") {
136
+ const r = role.toLowerCase();
137
+ isSigner = r.endsWith("signer");
138
+ isWritable = r.startsWith("writable");
139
+ }
140
+ const addressSource = acc.address ?? acc.publicKey;
141
+ const pubkey = addressSource instanceof PublicKey4 ? addressSource : typeof addressSource === "string" ? new PublicKey4(addressSource) : new PublicKey4(String(addressSource));
142
+ return { pubkey, isSigner, isWritable };
143
+ });
144
+ return new TransactionInstruction3({
145
+ programId: new PublicKey4(ix.programAddress ?? ix.programId),
146
+ keys: accounts,
147
+ data: Buffer.from(ix.data ?? [])
148
+ });
149
+ });
150
+ }
151
+ function getRpc(connection) {
152
+ const endpoint = connection._rpcEndpoint ?? connection.rpcEndpoint;
153
+ if (!endpoint) throw new Error("kit: cannot extract RPC endpoint from connection");
154
+ return createSolanaRpc(endpoint);
155
+ }
156
+
157
+ // src/factoring/instantPayout.ts
158
+ var VAULT_PROGRAM_EXEC_ROLE_ID = 1;
159
+ var USDC_DECIMALS = 6;
160
+ async function buildInstantPayoutInstructions(p) {
161
+ const split = computeFactoringSplit({
162
+ claimAmount: p.claimAmount,
163
+ financierSpread: p.financierSpread
164
+ });
165
+ const settleIx = buildSettleLockedVoucherInstruction({
166
+ swigAddress: p.swigAddress,
167
+ claimPda: p.claimPda,
168
+ vaultPda: p.vaultPda,
169
+ holder: p.financier,
170
+ dexterAuthority: p.dexterAuthority
171
+ });
172
+ const transfers = [
173
+ { destinationAta: p.sellerAta, amount: split.sellerReceives }
174
+ ];
175
+ if (split.financierSpread > 0n) {
176
+ transfers.push({ destinationAta: p.financierAta, amount: split.financierSpread });
177
+ }
178
+ const assemble = p.assembleSignV2 ?? defaultAssembleSignV2;
179
+ const signV2Ixs = await assemble({
180
+ connection: p.connection,
181
+ swigAddress: p.swigAddress,
182
+ feePayer: p.feePayer,
183
+ settleIx,
184
+ transfers
185
+ });
186
+ return [settleIx, ...signV2Ixs];
187
+ }
188
+ var defaultAssembleSignV2 = async (a) => {
189
+ const rpc = getRpc(a.connection);
190
+ const swig = await fetchSwig(rpc, kitAddress(a.swigAddress.toBase58()));
191
+ if (!swig) throw new Error(`factoring: swig not found on-chain: ${a.swigAddress.toBase58()}`);
192
+ const swigWalletKitAddr = await getSwigWalletAddress(swig);
193
+ const swigWalletPda = new PublicKey5(String(swigWalletKitAddr));
194
+ const usdcMint = new PublicKey5(USDC_MAINNET);
195
+ const sourceAta = getAssociatedTokenAddressSync(usdcMint, swigWalletPda, true);
196
+ const transferIxs = a.transfers.map(
197
+ (t) => getTransferCheckedInstruction({
198
+ source: kitAddress(sourceAta.toBase58()),
199
+ mint: kitAddress(usdcMint.toBase58()),
200
+ destination: kitAddress(t.destinationAta.toBase58()),
201
+ authority: swigWalletKitAddr,
202
+ amount: t.amount,
203
+ decimals: USDC_DECIMALS
204
+ })
205
+ );
206
+ const signIx = await getSignInstructions(
207
+ swig,
208
+ VAULT_PROGRAM_EXEC_ROLE_ID,
209
+ transferIxs,
210
+ false,
211
+ { payer: kitAddress(a.feePayer.toBase58()), preInstructions: [a.settleIx] }
212
+ );
213
+ return kitInstructionsToWeb3(signIx);
214
+ };
215
+ export {
216
+ buildInstantPayoutInstructions,
217
+ computeFactoringSplit,
218
+ getRpc,
219
+ kitInstructionsToWeb3
220
+ };