@dvmkit/sdk 0.3.4-rc.10 → 0.5.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-2KGG5ORW.js → chunk-7ZUKL4IV.js} +3 -3
- package/dist/{chunk-KXZUCCEY.js → chunk-A4YFWZ7R.js} +4 -55
- package/dist/{chunk-C2AGARLM.js → chunk-B2IROPQF.js} +450 -83
- package/dist/{chunk-MKI6OVW4.js → chunk-CZULM42S.js} +0 -51
- package/dist/{chunk-UZC4A4DM.js → chunk-EYTHFFFU.js} +2489 -213
- package/dist/{chunk-M4QDXD2A.js → chunk-FYRR2UEZ.js} +1 -1
- package/dist/{chunk-4UZN2GP5.js → chunk-GAOAYHZA.js} +209 -33
- package/dist/{chunk-YYQG4R7T.js → chunk-GFY5FRX3.js} +6 -4
- package/dist/{chunk-Z4BNLUZF.js → chunk-IWQHUSMO.js} +3 -105
- package/dist/{chunk-4J32DTLT.js → chunk-IXR5RFK7.js} +82 -4
- package/dist/{chunk-2SD2W2BG.js → chunk-JH3SX3LY.js} +252 -26
- package/dist/chunk-KA6VN5FK.js +245 -0
- package/dist/{chunk-YRDA7EWN.js → chunk-LE74UEV7.js} +61 -16
- package/dist/chunk-LIRAHP7B.js +53 -0
- package/dist/{chunk-MG67KXU7.js → chunk-MZRJR2CW.js} +4 -2
- package/dist/chunk-OFVV37XC.js +57 -0
- package/dist/{chunk-A4CM2BD3.js → chunk-PAIM66YA.js} +91 -2
- package/dist/{chunk-JDFLRNAZ.js → chunk-PB4FYZET.js} +67 -7
- package/dist/{chunk-BQ2NMWKE.js → chunk-PLG4XER6.js} +1 -1
- package/dist/chunk-RLTSCKRI.js +109 -0
- package/dist/{chunk-VAMSJRJT.js → chunk-TUA7PYH7.js} +27 -105
- package/dist/{chunk-O3QDFPKM.js → chunk-TZ2WKCDD.js} +1 -1
- package/dist/{chunk-Z3EHBW2G.js → chunk-UOCZXO2Z.js} +7 -5
- package/dist/{chunk-EAMZZ2B4.js → chunk-X7EZ5CLI.js} +59 -0
- package/dist/{chunk-CE7XEHYC.js → chunk-Z4KLECAC.js} +1 -1
- package/dist/{chunk-7X6UDVCQ.js → chunk-Z4ZT5LNT.js} +66 -2
- package/dist/{credit-ledger-A35WH2TI.js → credit-ledger-MI2H7NEC.js} +3 -1
- package/dist/{credit-menu-0-UtJ3hh.d.ts → credit-menu-CAfqe8IY.d.ts} +133 -7
- package/dist/{fx-DhyT1Cjh.d.ts → fx-D9nsN3n4.d.ts} +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +4 -3
- package/dist/internal/caller.d.ts +80 -13
- package/dist/internal/caller.js +56 -54
- package/dist/internal/index.js +1 -1
- package/dist/internal/server.d.ts +11 -85
- package/dist/internal/server.js +21 -18
- package/dist/{job-store-D3CcZp_g.d.ts → job-store-Bx-VFRjg.d.ts} +1 -1
- package/dist/{lightning-backend-BhsyrBLa.d.ts → lightning-backend-BAx2Y8pP.d.ts} +1 -1
- package/dist/{memory-credit-ledger-2LBZVMKJ.js → memory-credit-ledger-W2BAUYJP.js} +4 -2
- package/dist/{mpp-setup-7CGBKNUP.js → mpp-setup-RF5FXGGV.js} +5 -1
- package/dist/{payout-reporter-ZMOA37GN.js → payout-reporter-3Y2U55ZX.js} +1 -1
- package/dist/{postgres-job-store-MSD2HZOI.js → postgres-job-store-VLXLWXUQ.js} +1 -1
- package/dist/{revenue-reporter-EKJOGQYR.js → revenue-reporter-MAERPYGK.js} +1 -1
- package/dist/server/index.d.ts +14 -10
- package/dist/server/index.js +57 -19
- package/dist/{step-cache-v2yhyhSZ.d.ts → step-cache-QG_h5-3G.d.ts} +1769 -1549
- package/dist/{tempo-charge-store-4WX7FPOV.js → tempo-charge-store-Q3D7KYMH.js} +1 -1
- package/dist/tempo-lifecycle-ZBBLFBMH.js +15 -0
- package/dist/{tempo-session-store-LCSDHB7G.js → tempo-session-store-ZRNCOX7P.js} +1 -1
- package/dist/{tempo-wallet-RCGBBAJH.js → tempo-wallet-B5XXV3YH.js} +4 -3
- package/dist/testing/index.d.ts +3 -3
- package/dist/{usd-Cu9G6xv2.d.ts → usd-EwRqCPF-.d.ts} +1 -1
- package/dist/{x402-S47Q77UE.js → x402-R6HPBY5D.js} +18 -14
- package/package.json +1 -1
- package/dist/chunk-YHN3WMMF.js +0 -122
- package/dist/tempo-lifecycle-TJ62A7UX.js +0 -13
|
@@ -4,6 +4,9 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
applyRetryToPool
|
|
6
6
|
} from "./chunk-P6QON7ER.js";
|
|
7
|
+
import {
|
|
8
|
+
x402NetworkByCaip2
|
|
9
|
+
} from "./chunk-RLTSCKRI.js";
|
|
7
10
|
import {
|
|
8
11
|
diagnosticConsole
|
|
9
12
|
} from "./chunk-ETXDTUJO.js";
|
|
@@ -62,6 +65,7 @@ function lotSats(lot, micro) {
|
|
|
62
65
|
// src/sdk/server/x402-channel-store.ts
|
|
63
66
|
import { AsyncLocalStorage } from "async_hooks";
|
|
64
67
|
import { isDeepStrictEqual } from "util";
|
|
68
|
+
import { BATCH_SETTLEMENT_ADDRESS } from "@x402/evm";
|
|
65
69
|
var X402SettlementRaceError = class extends Error {
|
|
66
70
|
};
|
|
67
71
|
var X402RelaySubmissionLockError = class extends Error {
|
|
@@ -99,21 +103,45 @@ var PostgresX402ChannelStorage = class {
|
|
|
99
103
|
this.pool = pool;
|
|
100
104
|
this.relayLockTimeoutMs = opts?.relaySubmissionLockTimeoutMs ?? RELAY_SUBMISSION_LOCK_TIMEOUT_MS;
|
|
101
105
|
this.terminalizeUnbackedCredit = opts?.terminalizeUnbackedCredit;
|
|
106
|
+
this.hostedNetwork = opts?.hostedNetwork;
|
|
107
|
+
if (this.hostedNetwork && !x402NetworkByCaip2(this.hostedNetwork)) {
|
|
108
|
+
throw new Error("Hosted x402 batch channel network is unsupported");
|
|
109
|
+
}
|
|
102
110
|
if (!Number.isInteger(this.relayLockTimeoutMs) || this.relayLockTimeoutMs <= 0) {
|
|
103
111
|
throw new Error("relaySubmissionLockTimeoutMs must be a positive integer number of ms");
|
|
104
112
|
}
|
|
105
113
|
}
|
|
106
114
|
pool;
|
|
115
|
+
hostingCleanupFence;
|
|
116
|
+
useHostingCleanupFence(fence) {
|
|
117
|
+
this.hostingCleanupFence = fence;
|
|
118
|
+
}
|
|
119
|
+
async assertHostingFundingReservation(reservation) {
|
|
120
|
+
if (!this.hostingCleanupFence) return;
|
|
121
|
+
if (!reservation) throw new Error("Hosted x402 funding requires an original admission");
|
|
122
|
+
await this.hostingCleanupFence.withNewIntent({
|
|
123
|
+
admissionId: reservation.admissionId,
|
|
124
|
+
creditId: reservation.creditId,
|
|
125
|
+
fundId: reservation.fundId,
|
|
126
|
+
callerPubkey: reservation.callerPubkey,
|
|
127
|
+
fundingCommitment: reservation.commitment
|
|
128
|
+
}, (tx) => this.hostingCleanupFence.assertFundingReservation(tx, reservation));
|
|
129
|
+
}
|
|
107
130
|
transaction = new AsyncLocalStorage();
|
|
131
|
+
hostedFunding = /* @__PURE__ */ new WeakMap();
|
|
108
132
|
refundBase = new AsyncLocalStorage();
|
|
109
133
|
settlementPool;
|
|
110
134
|
fleetLock = Promise.resolve();
|
|
111
135
|
fleetLockQueueDepth = 0;
|
|
112
136
|
relayLockTimeoutMs;
|
|
113
137
|
terminalizeUnbackedCredit;
|
|
138
|
+
hostedNetwork;
|
|
114
139
|
async init() {
|
|
115
140
|
this.settlementPool ??= createSettlementPool(this.pool);
|
|
116
141
|
await withSdkInitLock(this.pool, async () => {
|
|
142
|
+
const before = await this.pool.query(
|
|
143
|
+
"SELECT to_regclass('x402_batch_channels') IS NOT NULL AS existed"
|
|
144
|
+
);
|
|
117
145
|
await this.pool.query(`
|
|
118
146
|
CREATE TABLE IF NOT EXISTS x402_batch_channels (
|
|
119
147
|
channel_id TEXT PRIMARY KEY,
|
|
@@ -127,9 +155,35 @@ var PostgresX402ChannelStorage = class {
|
|
|
127
155
|
refund_nonce BIGINT NOT NULL,
|
|
128
156
|
onchain_synced_at BIGINT,
|
|
129
157
|
last_request_timestamp BIGINT NOT NULL,
|
|
130
|
-
pending_request JSONB
|
|
158
|
+
pending_request JSONB,
|
|
159
|
+
hosting_binding JSONB
|
|
160
|
+
)
|
|
161
|
+
`);
|
|
162
|
+
await this.pool.query(
|
|
163
|
+
`ALTER TABLE x402_batch_channels ADD COLUMN IF NOT EXISTS hosting_binding JSONB`
|
|
164
|
+
);
|
|
165
|
+
await this.pool.query(`
|
|
166
|
+
CREATE TABLE IF NOT EXISTS x402_batch_channel_tombstones (
|
|
167
|
+
channel_id TEXT PRIMARY KEY,
|
|
168
|
+
hosting_binding JSONB,
|
|
169
|
+
charged_cumulative_amount NUMERIC(78,0) NOT NULL,
|
|
170
|
+
signed_max_claimable NUMERIC(78,0) NOT NULL,
|
|
171
|
+
refund_nonce BIGINT NOT NULL,
|
|
172
|
+
pending_request JSONB,
|
|
173
|
+
deleted_at_ms BIGINT NOT NULL
|
|
131
174
|
)
|
|
132
175
|
`);
|
|
176
|
+
await this.pool.query(`
|
|
177
|
+
CREATE TABLE IF NOT EXISTS x402_batch_channel_history (
|
|
178
|
+
singleton BOOLEAN PRIMARY KEY CHECK (singleton),
|
|
179
|
+
complete BOOLEAN NOT NULL
|
|
180
|
+
)
|
|
181
|
+
`);
|
|
182
|
+
await this.pool.query(
|
|
183
|
+
`INSERT INTO x402_batch_channel_history (singleton,complete) VALUES (TRUE,$1)
|
|
184
|
+
ON CONFLICT (singleton) DO NOTHING`,
|
|
185
|
+
[!before.rows[0].existed]
|
|
186
|
+
);
|
|
133
187
|
await this.pool.query(
|
|
134
188
|
`CREATE INDEX IF NOT EXISTS idx_x402_batch_channels_withdrawal
|
|
135
189
|
ON x402_batch_channels(withdraw_requested_at)
|
|
@@ -379,9 +433,9 @@ var PostgresX402ChannelStorage = class {
|
|
|
379
433
|
return existing ? { intent: existing, written: false } : void 0;
|
|
380
434
|
}
|
|
381
435
|
/** Persist the reconciliation identity, refreshing a prepared retry's transient reservation. */
|
|
382
|
-
async prepareSettlement(intent) {
|
|
436
|
+
async prepareSettlement(intent, hostingFunding) {
|
|
383
437
|
const settlements = await this.settlementWriter();
|
|
384
|
-
|
|
438
|
+
const insert = (q) => q.query(
|
|
385
439
|
`INSERT INTO x402_batch_settlements (
|
|
386
440
|
settlement_id, channel_id, operation, effect_id, payment_id,
|
|
387
441
|
payment_payload, payment_requirements, channel_before, pending_id, status
|
|
@@ -399,6 +453,27 @@ var PostgresX402ChannelStorage = class {
|
|
|
399
453
|
intent.pendingId ?? null
|
|
400
454
|
]
|
|
401
455
|
);
|
|
456
|
+
if (this.hostingCleanupFence && intent.operation === "fund") {
|
|
457
|
+
if (!hostingFunding || intent.effectId !== `${hostingFunding.creditId}:${hostingFunding.fundId}`) {
|
|
458
|
+
throw new Error("Hosted x402 settlement has no matching original funding admission");
|
|
459
|
+
}
|
|
460
|
+
await this.hostingCleanupFence.withNewIntentOn(
|
|
461
|
+
await this.settlementPoolRef(),
|
|
462
|
+
{
|
|
463
|
+
admissionId: hostingFunding.admissionId,
|
|
464
|
+
creditId: hostingFunding.creditId,
|
|
465
|
+
fundId: hostingFunding.fundId,
|
|
466
|
+
callerPubkey: hostingFunding.callerPubkey,
|
|
467
|
+
fundingCommitment: hostingFunding.commitment
|
|
468
|
+
},
|
|
469
|
+
async (tx) => {
|
|
470
|
+
await this.hostingCleanupFence.assertFundingReservation(tx, hostingFunding);
|
|
471
|
+
await insert(tx);
|
|
472
|
+
}
|
|
473
|
+
);
|
|
474
|
+
} else {
|
|
475
|
+
await insert(settlements);
|
|
476
|
+
}
|
|
402
477
|
const { rows } = await settlements.query(
|
|
403
478
|
`SELECT * FROM x402_batch_settlements
|
|
404
479
|
WHERE settlement_id = $1
|
|
@@ -597,10 +672,14 @@ var PostgresX402ChannelStorage = class {
|
|
|
597
672
|
withSanctionedRefundBase(base, operation) {
|
|
598
673
|
return this.refundBase.run(base, operation);
|
|
599
674
|
}
|
|
600
|
-
async withChannelTransaction(channelId, operation) {
|
|
675
|
+
async withChannelTransaction(channelId, operation, hostingFunding) {
|
|
601
676
|
const client = await this.pool.connect();
|
|
602
677
|
try {
|
|
603
678
|
await client.query("BEGIN");
|
|
679
|
+
if (this.hostingCleanupFence && hostingFunding) {
|
|
680
|
+
await this.hostingCleanupFence.assertFundingReservation(client, hostingFunding);
|
|
681
|
+
this.hostedFunding.set(client, hostingFunding);
|
|
682
|
+
}
|
|
604
683
|
await lockChannel(client, channelId);
|
|
605
684
|
const result = await this.transaction.run(client, () => operation(client));
|
|
606
685
|
await client.query("COMMIT");
|
|
@@ -608,6 +687,22 @@ var PostgresX402ChannelStorage = class {
|
|
|
608
687
|
} catch (error) {
|
|
609
688
|
await client.query("ROLLBACK").catch(() => void 0);
|
|
610
689
|
throw error;
|
|
690
|
+
} finally {
|
|
691
|
+
this.hostedFunding.delete(client);
|
|
692
|
+
client.release();
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
/** Durable effect bookkeeping reached while a caller already holds a host-pool client. */
|
|
696
|
+
async withSettlementTransaction(operation) {
|
|
697
|
+
const client = await (await this.settlementPoolRef()).connect();
|
|
698
|
+
try {
|
|
699
|
+
await client.query("BEGIN");
|
|
700
|
+
const result = await operation(client);
|
|
701
|
+
await client.query("COMMIT");
|
|
702
|
+
return result;
|
|
703
|
+
} catch (error) {
|
|
704
|
+
await client.query("ROLLBACK").catch(() => void 0);
|
|
705
|
+
throw error;
|
|
611
706
|
} finally {
|
|
612
707
|
client.release();
|
|
613
708
|
}
|
|
@@ -758,10 +853,36 @@ var PostgresX402ChannelStorage = class {
|
|
|
758
853
|
const next = update(current);
|
|
759
854
|
if (next === void 0) {
|
|
760
855
|
if (!current) return { channel: void 0, status: "unchanged" };
|
|
856
|
+
await client.query(
|
|
857
|
+
`INSERT INTO x402_batch_channel_tombstones
|
|
858
|
+
(channel_id,hosting_binding,charged_cumulative_amount,signed_max_claimable,refund_nonce,pending_request,deleted_at_ms)
|
|
859
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7)
|
|
860
|
+
ON CONFLICT (channel_id) DO UPDATE SET
|
|
861
|
+
charged_cumulative_amount=GREATEST(x402_batch_channel_tombstones.charged_cumulative_amount,
|
|
862
|
+
EXCLUDED.charged_cumulative_amount),
|
|
863
|
+
signed_max_claimable=GREATEST(x402_batch_channel_tombstones.signed_max_claimable,
|
|
864
|
+
EXCLUDED.signed_max_claimable),
|
|
865
|
+
refund_nonce=GREATEST(x402_batch_channel_tombstones.refund_nonce,EXCLUDED.refund_nonce),
|
|
866
|
+
pending_request=COALESCE(x402_batch_channel_tombstones.pending_request,EXCLUDED.pending_request)`,
|
|
867
|
+
[
|
|
868
|
+
normalized,
|
|
869
|
+
rows[0].hosting_binding,
|
|
870
|
+
rows[0].charged_cumulative_amount,
|
|
871
|
+
rows[0].signed_max_claimable,
|
|
872
|
+
rows[0].refund_nonce,
|
|
873
|
+
rows[0].pending_request,
|
|
874
|
+
Date.now()
|
|
875
|
+
]
|
|
876
|
+
);
|
|
761
877
|
await client.query(`DELETE FROM x402_batch_channels WHERE channel_id = $1`, [normalized]);
|
|
762
878
|
return { channel: void 0, status: "deleted" };
|
|
763
879
|
}
|
|
764
880
|
assertChannelTransition(current, next, normalized, this.refundBase.getStore());
|
|
881
|
+
if (this.hostingCleanupFence && (!current || BigInt(next.chargedCumulativeAmount) > BigInt(current.chargedCumulativeAmount) || BigInt(next.signedMaxClaimable) > BigInt(current.signedMaxClaimable))) {
|
|
882
|
+
const funding = this.hostedFunding.get(client);
|
|
883
|
+
if (funding) await this.hostingCleanupFence.assertFundingReservation(client, funding);
|
|
884
|
+
else await this.hostingCleanupFence.assertNewIntent(client);
|
|
885
|
+
}
|
|
765
886
|
if (this.terminalizeUnbackedCredit && next.onchainSyncedAt !== void 0 && next.onchainSyncedAt !== current?.onchainSyncedAt && next.withdrawRequestedAt === 0 && BigInt(next.balance) === BigInt(next.totalClaimed)) {
|
|
766
887
|
await this.terminalizeUnbackedCredit(
|
|
767
888
|
{
|
|
@@ -776,7 +897,17 @@ var PostgresX402ChannelStorage = class {
|
|
|
776
897
|
if (current && sameChannel(current, next)) {
|
|
777
898
|
return { channel: current, status: "unchanged" };
|
|
778
899
|
}
|
|
779
|
-
await
|
|
900
|
+
const prior = !current && this.hostedNetwork ? await client.query(
|
|
901
|
+
"SELECT hosting_binding FROM x402_batch_channel_tombstones WHERE channel_id=$1",
|
|
902
|
+
[normalized]
|
|
903
|
+
) : void 0;
|
|
904
|
+
await writeChannel(
|
|
905
|
+
client,
|
|
906
|
+
normalized,
|
|
907
|
+
next,
|
|
908
|
+
this.hostedNetwork,
|
|
909
|
+
prior?.rows[0] ? prior.rows[0].hosting_binding : void 0
|
|
910
|
+
);
|
|
780
911
|
return { channel: cloneChannel(next), status: "updated" };
|
|
781
912
|
}
|
|
782
913
|
};
|
|
@@ -893,13 +1024,20 @@ async function lockChannel(client, channelId) {
|
|
|
893
1024
|
normalizeChannelId(channelId)
|
|
894
1025
|
]);
|
|
895
1026
|
}
|
|
896
|
-
async function writeChannel(client, channelId, channel) {
|
|
1027
|
+
async function writeChannel(client, channelId, channel, hostedNetwork, priorBinding) {
|
|
1028
|
+
const binding = priorBinding !== void 0 ? priorBinding : hostedNetwork ? {
|
|
1029
|
+
network: hostedNetwork,
|
|
1030
|
+
chainId: Number(x402NetworkByCaip2(hostedNetwork)?.caip2.split(":")[1]),
|
|
1031
|
+
contract: BATCH_SETTLEMENT_ADDRESS.toLowerCase(),
|
|
1032
|
+
version: "x402-batch-settlement-v1",
|
|
1033
|
+
channelId
|
|
1034
|
+
} : null;
|
|
897
1035
|
await client.query(
|
|
898
1036
|
`INSERT INTO x402_batch_channels (
|
|
899
1037
|
channel_id, channel_config, charged_cumulative_amount, signed_max_claimable,
|
|
900
1038
|
signature, balance, total_claimed, withdraw_requested_at, refund_nonce,
|
|
901
|
-
onchain_synced_at, last_request_timestamp, pending_request
|
|
902
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
1039
|
+
onchain_synced_at, last_request_timestamp, pending_request, hosting_binding
|
|
1040
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
|
903
1041
|
ON CONFLICT (channel_id) DO UPDATE SET
|
|
904
1042
|
channel_config = EXCLUDED.channel_config,
|
|
905
1043
|
charged_cumulative_amount = EXCLUDED.charged_cumulative_amount,
|
|
@@ -924,7 +1062,8 @@ async function writeChannel(client, channelId, channel) {
|
|
|
924
1062
|
channel.refundNonce,
|
|
925
1063
|
channel.onchainSyncedAt ?? null,
|
|
926
1064
|
channel.lastRequestTimestamp,
|
|
927
|
-
channel.pendingRequest ?? null
|
|
1065
|
+
channel.pendingRequest ?? null,
|
|
1066
|
+
binding
|
|
928
1067
|
]
|
|
929
1068
|
);
|
|
930
1069
|
}
|
|
@@ -1003,6 +1142,8 @@ var CreditLedger = class {
|
|
|
1003
1142
|
durable = true;
|
|
1004
1143
|
x402Settlements;
|
|
1005
1144
|
expiryReleaseOutbox;
|
|
1145
|
+
hostingCleanupFence;
|
|
1146
|
+
invoiceSettlementClients = /* @__PURE__ */ new WeakSet();
|
|
1006
1147
|
/**
|
|
1007
1148
|
* Bound after construction because the settlement store shares this ledger and is
|
|
1008
1149
|
* initialized separately. Ledger effects and route preflights use the same refund gate.
|
|
@@ -1018,6 +1159,9 @@ var CreditLedger = class {
|
|
|
1018
1159
|
useCreditExpiryReleaseOutbox(outbox) {
|
|
1019
1160
|
this.expiryReleaseOutbox = outbox;
|
|
1020
1161
|
}
|
|
1162
|
+
useHostingCleanupFence(fence) {
|
|
1163
|
+
this.hostingCleanupFence = fence;
|
|
1164
|
+
}
|
|
1021
1165
|
async init() {
|
|
1022
1166
|
await withSdkInitLock(this.pool, () => this.createTables());
|
|
1023
1167
|
}
|
|
@@ -1248,6 +1392,8 @@ var CreditLedger = class {
|
|
|
1248
1392
|
expires_at BIGINT NOT NULL,
|
|
1249
1393
|
created_at BIGINT NOT NULL,
|
|
1250
1394
|
settled_at BIGINT,
|
|
1395
|
+
hosting_admission_id TEXT,
|
|
1396
|
+
created_at_db_us BIGINT NOT NULL DEFAULT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000000)::bigint,
|
|
1251
1397
|
PRIMARY KEY (credit_id, fund_id)
|
|
1252
1398
|
);
|
|
1253
1399
|
`);
|
|
@@ -1266,6 +1412,13 @@ var CreditLedger = class {
|
|
|
1266
1412
|
await this.pool.query(
|
|
1267
1413
|
`ALTER TABLE credit_invoices ADD COLUMN IF NOT EXISTS write_off_note TEXT`
|
|
1268
1414
|
);
|
|
1415
|
+
await this.pool.query(
|
|
1416
|
+
`ALTER TABLE credit_invoices ADD COLUMN IF NOT EXISTS hosting_admission_id TEXT`
|
|
1417
|
+
);
|
|
1418
|
+
await this.pool.query(
|
|
1419
|
+
`ALTER TABLE credit_invoices ADD COLUMN IF NOT EXISTS created_at_db_us BIGINT NOT NULL
|
|
1420
|
+
DEFAULT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000000)::bigint`
|
|
1421
|
+
);
|
|
1269
1422
|
await this.pool.query(
|
|
1270
1423
|
`CREATE UNIQUE INDEX IF NOT EXISTS credit_invoices_hash_uidx
|
|
1271
1424
|
ON credit_invoices(payment_hash);`
|
|
@@ -1379,6 +1532,20 @@ var CreditLedger = class {
|
|
|
1379
1532
|
}
|
|
1380
1533
|
}
|
|
1381
1534
|
async fundOn(q, args, b, nowMs, creditId) {
|
|
1535
|
+
if (this.hostingCleanupFence && (args.hostingInvoicePaymentHash || args.hostingInvoiceRepairPaymentHash) && !this.invoiceSettlementClients.has(q)) {
|
|
1536
|
+
throw new Error("Hosted invoice settlement must use its original locked transaction");
|
|
1537
|
+
}
|
|
1538
|
+
await this.hostingCleanupFence?.assertNewIntent(q, {
|
|
1539
|
+
admissionId: args.hostingAdmissionId,
|
|
1540
|
+
jobId: args.hostingJobId,
|
|
1541
|
+
paymentSeq: args.hostingPaymentSeq,
|
|
1542
|
+
creditId: args.hostingJobId ? void 0 : creditId,
|
|
1543
|
+
fundId: args.hostingFundId,
|
|
1544
|
+
callerPubkey: args.hostingJobId ? void 0 : args.callerPubkey,
|
|
1545
|
+
invoicePaymentHash: args.hostingInvoicePaymentHash,
|
|
1546
|
+
invoiceRepairPaymentHash: args.hostingInvoiceRepairPaymentHash,
|
|
1547
|
+
amountMicro: args.amountMicro
|
|
1548
|
+
});
|
|
1382
1549
|
const wedged = await this.blockingX402Refund(
|
|
1383
1550
|
b.x402Channel?.channelId,
|
|
1384
1551
|
X402_SPEND_BLOCKING_SETTLEMENT_STATUSES
|
|
@@ -1734,6 +1901,11 @@ var CreditLedger = class {
|
|
|
1734
1901
|
});
|
|
1735
1902
|
const ledgerSeq = toSafeInt(credit.last_ledger_seq) + 1;
|
|
1736
1903
|
const balanceAfterMicro = available - args.amountMicro;
|
|
1904
|
+
await this.hostingCleanupFence?.assertNewIntent(client, {
|
|
1905
|
+
admissionId: args.hostingAdmissionId,
|
|
1906
|
+
jobId: args.jobId,
|
|
1907
|
+
paymentSeq: args.hostingPaymentSeq
|
|
1908
|
+
});
|
|
1737
1909
|
await client.query(`UPDATE credits SET last_ledger_seq = $2 WHERE credit_id = $1`, [
|
|
1738
1910
|
args.creditId,
|
|
1739
1911
|
ledgerSeq
|
|
@@ -2008,7 +2180,34 @@ var CreditLedger = class {
|
|
|
2008
2180
|
*/
|
|
2009
2181
|
async recordFunding(args) {
|
|
2010
2182
|
assertAmount(args.amountMicro, { min: 1 });
|
|
2183
|
+
if (this.hostingCleanupFence && (args.hostingInvoicePaymentHash || args.hostingInvoiceRepairPaymentHash) && (!args.tx || !this.invoiceSettlementClients.has(args.tx))) {
|
|
2184
|
+
throw new Error("Hosted invoice settlement must use its original locked transaction");
|
|
2185
|
+
}
|
|
2186
|
+
if (this.hostingCleanupFence && !args.tx) {
|
|
2187
|
+
return this.hostingCleanupFence.withNewIntent({
|
|
2188
|
+
admissionId: args.hostingAdmissionId,
|
|
2189
|
+
jobId: args.hostingJobId,
|
|
2190
|
+
paymentSeq: args.hostingPaymentSeq,
|
|
2191
|
+
creditId: args.hostingJobId ? void 0 : args.creditId,
|
|
2192
|
+
fundId: args.hostingJobId ? void 0 : args.fundId,
|
|
2193
|
+
callerPubkey: args.hostingJobId ? void 0 : args.hostingCallerPubkey,
|
|
2194
|
+
invoicePaymentHash: args.hostingInvoicePaymentHash,
|
|
2195
|
+
amountMicro: args.amountMicro,
|
|
2196
|
+
invoiceRepairPaymentHash: args.hostingInvoiceRepairPaymentHash
|
|
2197
|
+
}, (tx) => this.recordFunding({ ...args, tx }));
|
|
2198
|
+
}
|
|
2011
2199
|
const q = args.tx ?? this.pool;
|
|
2200
|
+
if (args.tx) await this.hostingCleanupFence?.assertNewIntent(args.tx, {
|
|
2201
|
+
admissionId: args.hostingAdmissionId,
|
|
2202
|
+
jobId: args.hostingJobId,
|
|
2203
|
+
paymentSeq: args.hostingPaymentSeq,
|
|
2204
|
+
creditId: args.hostingJobId ? void 0 : args.creditId,
|
|
2205
|
+
fundId: args.hostingJobId ? void 0 : args.fundId,
|
|
2206
|
+
callerPubkey: args.hostingJobId ? void 0 : args.hostingCallerPubkey,
|
|
2207
|
+
invoicePaymentHash: args.hostingInvoicePaymentHash,
|
|
2208
|
+
amountMicro: args.amountMicro,
|
|
2209
|
+
invoiceRepairPaymentHash: args.hostingInvoiceRepairPaymentHash
|
|
2210
|
+
});
|
|
2012
2211
|
const nowMs = args.nowMs ?? Date.now();
|
|
2013
2212
|
const { rows } = await q.query(
|
|
2014
2213
|
`INSERT INTO credit_fundings (credit_id, fund_id, amount_micro, rail, created_at)
|
|
@@ -2089,25 +2288,40 @@ var CreditLedger = class {
|
|
|
2089
2288
|
const nowMs = args.nowMs ?? Date.now();
|
|
2090
2289
|
let rows;
|
|
2091
2290
|
try {
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'pending', $9, $10)
|
|
2096
|
-
ON CONFLICT (credit_id, fund_id) DO NOTHING
|
|
2097
|
-
RETURNING *`,
|
|
2098
|
-
[
|
|
2291
|
+
const insert = async (q) => {
|
|
2292
|
+
const hostingAdmissionId = this.hostingCleanupFence ? await this.hostingCleanupFence.fundingAdmission(
|
|
2293
|
+
q,
|
|
2099
2294
|
args.creditId,
|
|
2100
2295
|
args.fundId,
|
|
2101
|
-
args.callerPubkey
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2296
|
+
args.callerPubkey
|
|
2297
|
+
) : null;
|
|
2298
|
+
return q.query(
|
|
2299
|
+
`INSERT INTO credit_invoices (credit_id, fund_id, caller_pubkey, currency, amount_micro,
|
|
2300
|
+
amount_msats, payment_hash, bolt11, status, expires_at, created_at,
|
|
2301
|
+
hosting_admission_id)
|
|
2302
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'pending', $9, $10, $11)
|
|
2303
|
+
ON CONFLICT (credit_id, fund_id) DO NOTHING
|
|
2304
|
+
RETURNING *`,
|
|
2305
|
+
[
|
|
2306
|
+
args.creditId,
|
|
2307
|
+
args.fundId,
|
|
2308
|
+
args.callerPubkey,
|
|
2309
|
+
args.currency,
|
|
2310
|
+
args.amountMicro,
|
|
2311
|
+
args.amountMsats,
|
|
2312
|
+
args.paymentHash,
|
|
2313
|
+
args.bolt11,
|
|
2314
|
+
args.expiresAt,
|
|
2315
|
+
nowMs,
|
|
2316
|
+
hostingAdmissionId
|
|
2317
|
+
]
|
|
2318
|
+
);
|
|
2319
|
+
};
|
|
2320
|
+
({ rows } = this.hostingCleanupFence ? await this.hostingCleanupFence.withNewIntent({
|
|
2321
|
+
creditId: args.creditId,
|
|
2322
|
+
fundId: args.fundId,
|
|
2323
|
+
callerPubkey: args.callerPubkey
|
|
2324
|
+
}, insert) : await insert(this.pool));
|
|
2111
2325
|
} catch (err) {
|
|
2112
2326
|
if (isUniqueViolation(err)) {
|
|
2113
2327
|
throw new CreditLedgerError(
|
|
@@ -2184,11 +2398,14 @@ var CreditLedger = class {
|
|
|
2184
2398
|
]);
|
|
2185
2399
|
return { invoice, funding: funding2, credit: credit2, replayed: true };
|
|
2186
2400
|
}
|
|
2401
|
+
this.invoiceSettlementClients.add(client);
|
|
2187
2402
|
const funding = await this.recordFunding({
|
|
2188
2403
|
creditId: invoice.creditId,
|
|
2189
2404
|
fundId: invoice.fundId,
|
|
2190
2405
|
amountMicro: invoice.amountMicro,
|
|
2191
2406
|
rail: "lightning",
|
|
2407
|
+
hostingInvoicePaymentHash: invoice.paymentHash,
|
|
2408
|
+
hostingCallerPubkey: invoice.callerPubkey,
|
|
2192
2409
|
tx: client,
|
|
2193
2410
|
nowMs
|
|
2194
2411
|
});
|
|
@@ -2199,6 +2416,8 @@ var CreditLedger = class {
|
|
|
2199
2416
|
amountMicro: invoice.amountMicro,
|
|
2200
2417
|
expiryMs: args.expiryMs,
|
|
2201
2418
|
basis: args.basis,
|
|
2419
|
+
hostingInvoicePaymentHash: invoice.paymentHash,
|
|
2420
|
+
hostingFundId: invoice.fundId,
|
|
2202
2421
|
tx: client,
|
|
2203
2422
|
nowMs
|
|
2204
2423
|
});
|
|
@@ -2233,6 +2452,7 @@ var CreditLedger = class {
|
|
|
2233
2452
|
}
|
|
2234
2453
|
throw err;
|
|
2235
2454
|
} finally {
|
|
2455
|
+
this.invoiceSettlementClients.delete(client);
|
|
2236
2456
|
client.release();
|
|
2237
2457
|
}
|
|
2238
2458
|
}
|
|
@@ -2338,11 +2558,14 @@ var CreditLedger = class {
|
|
|
2338
2558
|
`invoice ${args.paymentHash} is ${invoice.status}; only a blocked or written-off invoice can be reconciled`
|
|
2339
2559
|
);
|
|
2340
2560
|
}
|
|
2561
|
+
this.invoiceSettlementClients.add(client);
|
|
2341
2562
|
const funding = await this.recordFunding({
|
|
2342
2563
|
creditId: args.targetCreditId,
|
|
2343
2564
|
fundId: invoice.paymentHash,
|
|
2344
2565
|
amountMicro: invoice.amountMicro,
|
|
2345
2566
|
rail: "lightning",
|
|
2567
|
+
hostingInvoiceRepairPaymentHash: invoice.paymentHash,
|
|
2568
|
+
hostingCallerPubkey: invoice.callerPubkey,
|
|
2346
2569
|
tx: client,
|
|
2347
2570
|
nowMs
|
|
2348
2571
|
});
|
|
@@ -2353,6 +2576,8 @@ var CreditLedger = class {
|
|
|
2353
2576
|
amountMicro: invoice.amountMicro,
|
|
2354
2577
|
expiryMs: args.expiryMs,
|
|
2355
2578
|
basis: args.basis,
|
|
2579
|
+
hostingInvoiceRepairPaymentHash: invoice.paymentHash,
|
|
2580
|
+
hostingFundId: invoice.paymentHash,
|
|
2356
2581
|
tx: client,
|
|
2357
2582
|
nowMs
|
|
2358
2583
|
});
|
|
@@ -2398,6 +2623,7 @@ var CreditLedger = class {
|
|
|
2398
2623
|
}
|
|
2399
2624
|
throw err;
|
|
2400
2625
|
} finally {
|
|
2626
|
+
this.invoiceSettlementClients.delete(client);
|
|
2401
2627
|
client.release();
|
|
2402
2628
|
}
|
|
2403
2629
|
}
|