@dexterai/vault 0.4.2 → 0.6.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.
- package/README.md +62 -16
- package/dist/connect/index.cjs +455 -0
- package/dist/connect/index.d.cts +145 -0
- package/dist/connect/index.d.ts +145 -0
- package/dist/connect/index.js +419 -0
- package/dist/constants/index.cjs +13 -1
- package/dist/constants/index.d.cts +11 -1
- package/dist/constants/index.d.ts +11 -1
- package/dist/constants/index.js +12 -1
- package/dist/counterfactual.cjs +12 -1
- package/dist/counterfactual.js +12 -1
- package/dist/factoring/index.cjs +246 -0
- package/dist/factoring/index.d.cts +79 -0
- package/dist/factoring/index.d.ts +79 -0
- package/dist/factoring/index.js +220 -0
- package/dist/idl/dexter_vault.json +976 -1
- package/dist/index.cjs +12 -1
- package/dist/index.js +12 -1
- package/dist/instructions/index.cjs +249 -4400
- package/dist/instructions/index.d.cts +172 -1
- package/dist/instructions/index.d.ts +172 -1
- package/dist/instructions/index.js +235 -4396
- package/dist/kit/index.cjs +67 -0
- package/dist/kit/index.d.cts +13 -0
- package/dist/kit/index.d.ts +13 -0
- package/dist/kit/index.js +41 -0
- package/dist/messages/index.cjs +11 -1
- package/dist/messages/index.js +11 -1
- package/dist/precompile/index.cjs +11 -1
- package/dist/precompile/index.js +11 -1
- package/dist/tab/index.cjs +640 -0
- package/dist/tab/index.d.cts +145 -0
- package/dist/tab/index.d.ts +145 -0
- package/dist/tab/index.js +631 -0
- package/package.json +23 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/factoring/index.ts
|
|
21
|
+
var factoring_exports = {};
|
|
22
|
+
__export(factoring_exports, {
|
|
23
|
+
buildInstantPayoutInstructions: () => buildInstantPayoutInstructions,
|
|
24
|
+
computeFactoringSplit: () => computeFactoringSplit,
|
|
25
|
+
getRpc: () => getRpc,
|
|
26
|
+
kitInstructionsToWeb3: () => kitInstructionsToWeb3
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(factoring_exports);
|
|
29
|
+
|
|
30
|
+
// src/factoring/split.ts
|
|
31
|
+
function computeFactoringSplit(p) {
|
|
32
|
+
if (p.claimAmount <= 0n) {
|
|
33
|
+
throw new Error(`factoring: claim amount must be positive, got ${p.claimAmount}`);
|
|
34
|
+
}
|
|
35
|
+
if (p.financierSpread < 0n) {
|
|
36
|
+
throw new Error(`factoring: spread must not be negative, got ${p.financierSpread}`);
|
|
37
|
+
}
|
|
38
|
+
if (p.financierSpread > p.claimAmount) {
|
|
39
|
+
throw new Error(`factoring: spread exceeds claim (${p.financierSpread} > ${p.claimAmount})`);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
sellerReceives: p.claimAmount - p.financierSpread,
|
|
43
|
+
financierSpread: p.financierSpread
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/factoring/instantPayout.ts
|
|
48
|
+
var import_web35 = require("@solana/web3.js");
|
|
49
|
+
var import_kit3 = require("@swig-wallet/kit");
|
|
50
|
+
var import_kit4 = require("@solana/kit");
|
|
51
|
+
var import_token = require("@solana-program/token");
|
|
52
|
+
var import_spl_token = require("@solana/spl-token");
|
|
53
|
+
|
|
54
|
+
// src/instructions/lockedClaim.ts
|
|
55
|
+
var import_web33 = require("@solana/web3.js");
|
|
56
|
+
|
|
57
|
+
// src/constants/index.ts
|
|
58
|
+
var import_web3 = require("@solana/web3.js");
|
|
59
|
+
var DEXTER_VAULT_PROGRAM_ID = new import_web3.PublicKey(
|
|
60
|
+
"Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc"
|
|
61
|
+
);
|
|
62
|
+
var SWIG_PROGRAM_ID = new import_web3.PublicKey(
|
|
63
|
+
"swigypWHEksbC64pWKwah1WTeh9JXwx8H1rJHLdbQMB"
|
|
64
|
+
);
|
|
65
|
+
var SECP256R1_PROGRAM_ID = new import_web3.PublicKey(
|
|
66
|
+
"Secp256r1SigVerify1111111111111111111111111"
|
|
67
|
+
);
|
|
68
|
+
var ED25519_PROGRAM_ID = new import_web3.PublicKey(
|
|
69
|
+
"Ed25519SigVerify111111111111111111111111111"
|
|
70
|
+
);
|
|
71
|
+
var INSTRUCTIONS_SYSVAR_ID = new import_web3.PublicKey(
|
|
72
|
+
"Sysvar1nstructions1111111111111111111111111"
|
|
73
|
+
);
|
|
74
|
+
var USDC_MAINNET = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
75
|
+
var VAULT_SEED_PREFIX = Buffer.from("vault");
|
|
76
|
+
var LOCKED_CLAIM_SEED = Buffer.from("locked-claim");
|
|
77
|
+
var DISCRIMINATORS = Object.freeze({
|
|
78
|
+
initialize_vault: Uint8Array.from([48, 191, 163, 44, 71, 129, 63, 164]),
|
|
79
|
+
set_swig: Uint8Array.from([253, 229, 89, 206, 192, 118, 137, 165]),
|
|
80
|
+
settle_voucher: Uint8Array.from([144, 176, 128, 220, 156, 79, 41, 54]),
|
|
81
|
+
request_withdrawal: Uint8Array.from([251, 85, 121, 205, 56, 201, 12, 177]),
|
|
82
|
+
finalize_withdrawal: Uint8Array.from([178, 87, 206, 68, 201, 186, 164, 232]),
|
|
83
|
+
force_release: Uint8Array.from([122, 190, 243, 252, 54, 202, 208, 234]),
|
|
84
|
+
rotate_passkey: Uint8Array.from([28, 134, 49, 89, 196, 34, 58, 174]),
|
|
85
|
+
rotate_dexter_authority: Uint8Array.from([145, 60, 4, 119, 180, 205, 236, 134]),
|
|
86
|
+
prove_passkey: Uint8Array.from([35, 175, 41, 143, 201, 118, 49, 184]),
|
|
87
|
+
settle_tab_voucher: Uint8Array.from([173, 22, 98, 31, 110, 129, 59, 161]),
|
|
88
|
+
register_session_key: Uint8Array.from([69, 94, 60, 44, 49, 199, 183, 233]),
|
|
89
|
+
revoke_session_key: Uint8Array.from([81, 192, 32, 110, 104, 116, 144, 151]),
|
|
90
|
+
lock_voucher: Uint8Array.from([91, 138, 5, 227, 119, 239, 48, 254]),
|
|
91
|
+
settle_locked_voucher: Uint8Array.from([44, 80, 216, 43, 247, 253, 101, 45]),
|
|
92
|
+
transfer_lock_ownership: Uint8Array.from([193, 13, 131, 134, 95, 25, 229, 157]),
|
|
93
|
+
recover_abandoned_lock: Uint8Array.from([169, 213, 107, 64, 229, 49, 43, 234]),
|
|
94
|
+
open_standby: Uint8Array.from([234, 184, 232, 135, 246, 191, 90, 250]),
|
|
95
|
+
draw_credit: Uint8Array.from([20, 84, 47, 211, 78, 117, 195, 210]),
|
|
96
|
+
repay_credit: Uint8Array.from([38, 113, 240, 182, 109, 179, 154, 245]),
|
|
97
|
+
seize_collateral: Uint8Array.from([40, 250, 7, 243, 168, 184, 116, 154]),
|
|
98
|
+
migrate_v4_to_v5: Uint8Array.from([226, 105, 140, 184, 101, 39, 235, 116])
|
|
99
|
+
});
|
|
100
|
+
var OTS_SESSION_REGISTER_V1_DOMAIN = (() => {
|
|
101
|
+
const buf = new Uint8Array(32);
|
|
102
|
+
buf.set(new TextEncoder().encode("OTS_SESSION_REGISTER_V1"), 0);
|
|
103
|
+
return buf;
|
|
104
|
+
})();
|
|
105
|
+
var OTS_SESSION_REGISTER_V2_DOMAIN = (() => {
|
|
106
|
+
const buf = new Uint8Array(32);
|
|
107
|
+
buf.set(new TextEncoder().encode("OTS_SESSION_REGISTER_V2"), 0);
|
|
108
|
+
return buf;
|
|
109
|
+
})();
|
|
110
|
+
var OTS_SESSION_REVOKE_V1_DOMAIN = (() => {
|
|
111
|
+
const buf = new Uint8Array(32);
|
|
112
|
+
buf.set(new TextEncoder().encode("OTS_SESSION_REVOKE_V1"), 0);
|
|
113
|
+
return buf;
|
|
114
|
+
})();
|
|
115
|
+
|
|
116
|
+
// src/instructions/withdraw.ts
|
|
117
|
+
var import_web32 = require("@solana/web3.js");
|
|
118
|
+
function deriveSwigWalletAddress(swigAddress) {
|
|
119
|
+
const [pda] = import_web32.PublicKey.findProgramAddressSync(
|
|
120
|
+
[Buffer.from("swig-wallet-address"), swigAddress.toBuffer()],
|
|
121
|
+
SWIG_PROGRAM_ID
|
|
122
|
+
);
|
|
123
|
+
return pda;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/instructions/lockedClaim.ts
|
|
127
|
+
function buildSettleLockedVoucherInstruction(p) {
|
|
128
|
+
const data = Buffer.from(DISCRIMINATORS.settle_locked_voucher);
|
|
129
|
+
const swigWalletAddress = deriveSwigWalletAddress(p.swigAddress);
|
|
130
|
+
return new import_web33.TransactionInstruction({
|
|
131
|
+
programId: DEXTER_VAULT_PROGRAM_ID,
|
|
132
|
+
keys: [
|
|
133
|
+
{ pubkey: p.swigAddress, isSigner: false, isWritable: false },
|
|
134
|
+
{ pubkey: swigWalletAddress, isSigner: false, isWritable: false },
|
|
135
|
+
{ pubkey: p.claimPda, isSigner: false, isWritable: true },
|
|
136
|
+
{ pubkey: p.vaultPda, isSigner: false, isWritable: true },
|
|
137
|
+
{ pubkey: p.holder, isSigner: true, isWritable: false },
|
|
138
|
+
{ pubkey: p.dexterAuthority, isSigner: true, isWritable: false }
|
|
139
|
+
],
|
|
140
|
+
data
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/kit/index.ts
|
|
145
|
+
var import_web34 = require("@solana/web3.js");
|
|
146
|
+
var import_kit = require("@solana/kit");
|
|
147
|
+
function kitInstructionsToWeb3(kitInstructions) {
|
|
148
|
+
return kitInstructions.map((ix) => {
|
|
149
|
+
const accounts = (ix.accounts ?? []).map((acc) => {
|
|
150
|
+
const role = acc.role;
|
|
151
|
+
const hasBooleanShape = typeof acc.signer === "boolean" || typeof acc.writable === "boolean";
|
|
152
|
+
let isSigner = false;
|
|
153
|
+
let isWritable = false;
|
|
154
|
+
if (hasBooleanShape) {
|
|
155
|
+
isSigner = Boolean(acc.signer);
|
|
156
|
+
isWritable = Boolean(acc.writable);
|
|
157
|
+
} else if (typeof role === "number") {
|
|
158
|
+
isSigner = role >= 2;
|
|
159
|
+
isWritable = role % 2 === 1;
|
|
160
|
+
} else if (typeof role === "string") {
|
|
161
|
+
const r = role.toLowerCase();
|
|
162
|
+
isSigner = r.endsWith("signer");
|
|
163
|
+
isWritable = r.startsWith("writable");
|
|
164
|
+
}
|
|
165
|
+
const addressSource = acc.address ?? acc.publicKey;
|
|
166
|
+
const pubkey = addressSource instanceof import_web34.PublicKey ? addressSource : typeof addressSource === "string" ? new import_web34.PublicKey(addressSource) : new import_web34.PublicKey(String(addressSource));
|
|
167
|
+
return { pubkey, isSigner, isWritable };
|
|
168
|
+
});
|
|
169
|
+
return new import_web34.TransactionInstruction({
|
|
170
|
+
programId: new import_web34.PublicKey(ix.programAddress ?? ix.programId),
|
|
171
|
+
keys: accounts,
|
|
172
|
+
data: Buffer.from(ix.data ?? [])
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function getRpc(connection) {
|
|
177
|
+
const endpoint = connection._rpcEndpoint ?? connection.rpcEndpoint;
|
|
178
|
+
if (!endpoint) throw new Error("kit: cannot extract RPC endpoint from connection");
|
|
179
|
+
return (0, import_kit.createSolanaRpc)(endpoint);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/factoring/instantPayout.ts
|
|
183
|
+
var VAULT_PROGRAM_EXEC_ROLE_ID = 1;
|
|
184
|
+
var USDC_DECIMALS = 6;
|
|
185
|
+
async function buildInstantPayoutInstructions(p) {
|
|
186
|
+
const split = computeFactoringSplit({
|
|
187
|
+
claimAmount: p.claimAmount,
|
|
188
|
+
financierSpread: p.financierSpread
|
|
189
|
+
});
|
|
190
|
+
const settleIx = buildSettleLockedVoucherInstruction({
|
|
191
|
+
swigAddress: p.swigAddress,
|
|
192
|
+
claimPda: p.claimPda,
|
|
193
|
+
vaultPda: p.vaultPda,
|
|
194
|
+
holder: p.financier,
|
|
195
|
+
dexterAuthority: p.dexterAuthority
|
|
196
|
+
});
|
|
197
|
+
const transfers = [
|
|
198
|
+
{ destinationAta: p.sellerAta, amount: split.sellerReceives }
|
|
199
|
+
];
|
|
200
|
+
if (split.financierSpread > 0n) {
|
|
201
|
+
transfers.push({ destinationAta: p.financierAta, amount: split.financierSpread });
|
|
202
|
+
}
|
|
203
|
+
const assemble = p.assembleSignV2 ?? defaultAssembleSignV2;
|
|
204
|
+
const signV2Ixs = await assemble({
|
|
205
|
+
connection: p.connection,
|
|
206
|
+
swigAddress: p.swigAddress,
|
|
207
|
+
feePayer: p.feePayer,
|
|
208
|
+
settleIx,
|
|
209
|
+
transfers
|
|
210
|
+
});
|
|
211
|
+
return [settleIx, ...signV2Ixs];
|
|
212
|
+
}
|
|
213
|
+
var defaultAssembleSignV2 = async (a) => {
|
|
214
|
+
const rpc = getRpc(a.connection);
|
|
215
|
+
const swig = await (0, import_kit3.fetchSwig)(rpc, (0, import_kit4.address)(a.swigAddress.toBase58()));
|
|
216
|
+
if (!swig) throw new Error(`factoring: swig not found on-chain: ${a.swigAddress.toBase58()}`);
|
|
217
|
+
const swigWalletKitAddr = await (0, import_kit3.getSwigWalletAddress)(swig);
|
|
218
|
+
const swigWalletPda = new import_web35.PublicKey(String(swigWalletKitAddr));
|
|
219
|
+
const usdcMint = new import_web35.PublicKey(USDC_MAINNET);
|
|
220
|
+
const sourceAta = (0, import_spl_token.getAssociatedTokenAddressSync)(usdcMint, swigWalletPda, true);
|
|
221
|
+
const transferIxs = a.transfers.map(
|
|
222
|
+
(t) => (0, import_token.getTransferCheckedInstruction)({
|
|
223
|
+
source: (0, import_kit4.address)(sourceAta.toBase58()),
|
|
224
|
+
mint: (0, import_kit4.address)(usdcMint.toBase58()),
|
|
225
|
+
destination: (0, import_kit4.address)(t.destinationAta.toBase58()),
|
|
226
|
+
authority: swigWalletKitAddr,
|
|
227
|
+
amount: t.amount,
|
|
228
|
+
decimals: USDC_DECIMALS
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
const signIx = await (0, import_kit3.getSignInstructions)(
|
|
232
|
+
swig,
|
|
233
|
+
VAULT_PROGRAM_EXEC_ROLE_ID,
|
|
234
|
+
transferIxs,
|
|
235
|
+
false,
|
|
236
|
+
{ payer: (0, import_kit4.address)(a.feePayer.toBase58()), preInstructions: [a.settleIx] }
|
|
237
|
+
);
|
|
238
|
+
return kitInstructionsToWeb3(signIx);
|
|
239
|
+
};
|
|
240
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
241
|
+
0 && (module.exports = {
|
|
242
|
+
buildInstantPayoutInstructions,
|
|
243
|
+
computeFactoringSplit,
|
|
244
|
+
getRpc,
|
|
245
|
+
kitInstructionsToWeb3
|
|
246
|
+
});
|
|
@@ -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
|
+
};
|