@infantmen-labs/harbor-sdk 0.5.0 → 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 +5 -2
- package/dist/src/channels.d.ts +59 -0
- package/dist/src/channels.js +85 -0
- package/dist/src/harbor-ix.d.ts +6 -0
- package/dist/src/harbor-ix.js +16 -0
- package/dist/src/pda.d.ts +2 -0
- package/dist/src/pda.js +5 -0
- package/dist/src/receipt.d.ts +14 -0
- package/dist/src/receipt.js +25 -1
- package/dist/test/vectors.test.js +90 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,8 +35,11 @@ complete third-party monitor built on SDK + web3.js only.
|
|
|
35
35
|
## Buyer patterns (demand-side integration)
|
|
36
36
|
|
|
37
37
|
Upstream channel builders (`openChannelIx` / `topUpIx` / `settleIx`,
|
|
38
|
-
`deriveChannel
|
|
39
|
-
|
|
38
|
+
`deriveChannel`, plus the close lifecycle `requestCloseIx` / `sealIx` /
|
|
39
|
+
`withdrawPayerIx` / `distributeIx` / `reclaimIx`) live here so buyers
|
|
40
|
+
never reimplement consensus-critical bytes by copy-paste. `vaultAta`,
|
|
41
|
+
`decodeBinding`, and receipt-expiry readers round out the buyer surface.
|
|
42
|
+
The proven demand-side flow:
|
|
40
43
|
|
|
41
44
|
1. **Gate on collateral.** Derive `bondPda(merchant, mint)` offline,
|
|
42
45
|
`decodeBond` it, refuse when `amount − reserved` is below your
|
package/dist/src/channels.d.ts
CHANGED
|
@@ -26,6 +26,65 @@ export declare function topUpIx(args: {
|
|
|
26
26
|
mint: PublicKey;
|
|
27
27
|
amount: bigint;
|
|
28
28
|
}): TransactionInstruction;
|
|
29
|
+
/**
|
|
30
|
+
* Upstream forced-close lifecycle (buyer reclaim path). All no-arg beyond
|
|
31
|
+
* the discriminator; discriminators from the pinned upstream IDL
|
|
32
|
+
* (requestClose 5, seal 6, withdrawPayer 8, reclaim 9).
|
|
33
|
+
*
|
|
34
|
+
* Reclaim flow: `requestClose` (payer starts grace) → wait past grace →
|
|
35
|
+
* `seal` → `withdrawPayer` (unspent remainder back to the payer) →
|
|
36
|
+
* `reclaim` (deallocate channel, rent to rentPayer). Cooperative
|
|
37
|
+
* alternative: `settleAndSeal` (needs payee signature — not built here).
|
|
38
|
+
*/
|
|
39
|
+
export declare function requestCloseIx(args: {
|
|
40
|
+
programId: PublicKey;
|
|
41
|
+
payer: PublicKey;
|
|
42
|
+
channel: PublicKey;
|
|
43
|
+
}): TransactionInstruction;
|
|
44
|
+
export declare function sealIx(args: {
|
|
45
|
+
programId: PublicKey;
|
|
46
|
+
channel: PublicKey;
|
|
47
|
+
}): TransactionInstruction;
|
|
48
|
+
export declare function withdrawPayerIx(args: {
|
|
49
|
+
programId: PublicKey;
|
|
50
|
+
payer: PublicKey;
|
|
51
|
+
channel: PublicKey;
|
|
52
|
+
channelAta: PublicKey;
|
|
53
|
+
payerAta: PublicKey;
|
|
54
|
+
mint: PublicKey;
|
|
55
|
+
}): TransactionInstruction;
|
|
56
|
+
export declare function reclaimIx(args: {
|
|
57
|
+
programId: PublicKey;
|
|
58
|
+
channel: PublicKey;
|
|
59
|
+
rentPayer: PublicKey;
|
|
60
|
+
}): TransactionInstruction;
|
|
61
|
+
/**
|
|
62
|
+
* Upstream `distribute` with an explicit recipient list (empty = payee
|
|
63
|
+
* remainder only). Data: disc(7) | count(u32 LE) | entries. The onchain
|
|
64
|
+
* program rehashes the revealed plan and compares against the commitment
|
|
65
|
+
* stored at `open` — agent-opened channels commit to the empty plan
|
|
66
|
+
* (count 0), so empty args verify for them.
|
|
67
|
+
*
|
|
68
|
+
* Fixed accounts: channel(mut) | payer | rentPayer(mut) | channelAta(mut)
|
|
69
|
+
* | payerAta(mut) | payeeAta(mut) | treasuryAta(mut) | mint |
|
|
70
|
+
* tokenProgram | eventAuthority | selfProgram, then recipient ATAs in plan
|
|
71
|
+
* order. Permissionless crank: payer is deliberately NOT marked signer
|
|
72
|
+
* (the upstream docs require payer-side signatures only for topUp,
|
|
73
|
+
* requestClose and withdrawPayer — the fee payer signs the tx itself).
|
|
74
|
+
*/
|
|
75
|
+
export declare function distributeIx(args: {
|
|
76
|
+
programId: PublicKey;
|
|
77
|
+
channel: PublicKey;
|
|
78
|
+
payer: PublicKey;
|
|
79
|
+
rentPayer: PublicKey;
|
|
80
|
+
channelAta: PublicKey;
|
|
81
|
+
payerAta: PublicKey;
|
|
82
|
+
payeeAta: PublicKey;
|
|
83
|
+
treasuryAta: PublicKey;
|
|
84
|
+
mint: PublicKey;
|
|
85
|
+
eventAuthority: PublicKey;
|
|
86
|
+
recipientAtas?: PublicKey[];
|
|
87
|
+
}): TransactionInstruction;
|
|
29
88
|
export declare function deriveChannel(programId: PublicKey, payer: PublicKey, payee: PublicKey, mint: PublicKey, authorizedSigner: PublicKey, salt: bigint, openSlot: bigint): {
|
|
30
89
|
channel: PublicKey;
|
|
31
90
|
};
|
package/dist/src/channels.js
CHANGED
|
@@ -3,6 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.openChannelIx = openChannelIx;
|
|
4
4
|
exports.settleIx = settleIx;
|
|
5
5
|
exports.topUpIx = topUpIx;
|
|
6
|
+
exports.requestCloseIx = requestCloseIx;
|
|
7
|
+
exports.sealIx = sealIx;
|
|
8
|
+
exports.withdrawPayerIx = withdrawPayerIx;
|
|
9
|
+
exports.reclaimIx = reclaimIx;
|
|
10
|
+
exports.distributeIx = distributeIx;
|
|
6
11
|
exports.deriveChannel = deriveChannel;
|
|
7
12
|
const web3_js_1 = require("@solana/web3.js");
|
|
8
13
|
const ids_1 = require("./ids");
|
|
@@ -75,6 +80,86 @@ function topUpIx(args) {
|
|
|
75
80
|
data: Buffer.concat([Buffer.from([3]), (0, u64_1.u64le)(args.amount)]),
|
|
76
81
|
});
|
|
77
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Upstream forced-close lifecycle (buyer reclaim path). All no-arg beyond
|
|
85
|
+
* the discriminator; discriminators from the pinned upstream IDL
|
|
86
|
+
* (requestClose 5, seal 6, withdrawPayer 8, reclaim 9).
|
|
87
|
+
*
|
|
88
|
+
* Reclaim flow: `requestClose` (payer starts grace) → wait past grace →
|
|
89
|
+
* `seal` → `withdrawPayer` (unspent remainder back to the payer) →
|
|
90
|
+
* `reclaim` (deallocate channel, rent to rentPayer). Cooperative
|
|
91
|
+
* alternative: `settleAndSeal` (needs payee signature — not built here).
|
|
92
|
+
*/
|
|
93
|
+
function requestCloseIx(args) {
|
|
94
|
+
return new web3_js_1.TransactionInstruction({
|
|
95
|
+
programId: args.programId,
|
|
96
|
+
keys: [m(args.payer, false, true), m(args.channel, true, false)],
|
|
97
|
+
data: Buffer.from([5]),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function sealIx(args) {
|
|
101
|
+
return new web3_js_1.TransactionInstruction({
|
|
102
|
+
programId: args.programId,
|
|
103
|
+
keys: [m(args.channel, true, false)],
|
|
104
|
+
data: Buffer.from([6]),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function withdrawPayerIx(args) {
|
|
108
|
+
return new web3_js_1.TransactionInstruction({
|
|
109
|
+
programId: args.programId,
|
|
110
|
+
keys: [
|
|
111
|
+
m(args.payer, false, true),
|
|
112
|
+
m(args.channel, true, false),
|
|
113
|
+
m(args.channelAta, true, false),
|
|
114
|
+
m(args.payerAta, true, false),
|
|
115
|
+
m(args.mint, false, false),
|
|
116
|
+
m(ids_1.TOKEN_PROGRAM_ID, false, false),
|
|
117
|
+
],
|
|
118
|
+
data: Buffer.from([8]),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function reclaimIx(args) {
|
|
122
|
+
return new web3_js_1.TransactionInstruction({
|
|
123
|
+
programId: args.programId,
|
|
124
|
+
keys: [m(args.channel, true, false), m(args.rentPayer, true, false)],
|
|
125
|
+
data: Buffer.from([9]),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Upstream `distribute` with an explicit recipient list (empty = payee
|
|
130
|
+
* remainder only). Data: disc(7) | count(u32 LE) | entries. The onchain
|
|
131
|
+
* program rehashes the revealed plan and compares against the commitment
|
|
132
|
+
* stored at `open` — agent-opened channels commit to the empty plan
|
|
133
|
+
* (count 0), so empty args verify for them.
|
|
134
|
+
*
|
|
135
|
+
* Fixed accounts: channel(mut) | payer | rentPayer(mut) | channelAta(mut)
|
|
136
|
+
* | payerAta(mut) | payeeAta(mut) | treasuryAta(mut) | mint |
|
|
137
|
+
* tokenProgram | eventAuthority | selfProgram, then recipient ATAs in plan
|
|
138
|
+
* order. Permissionless crank: payer is deliberately NOT marked signer
|
|
139
|
+
* (the upstream docs require payer-side signatures only for topUp,
|
|
140
|
+
* requestClose and withdrawPayer — the fee payer signs the tx itself).
|
|
141
|
+
*/
|
|
142
|
+
function distributeIx(args) {
|
|
143
|
+
const data = Buffer.concat([Buffer.from([7]), u32(0)]);
|
|
144
|
+
return new web3_js_1.TransactionInstruction({
|
|
145
|
+
programId: args.programId,
|
|
146
|
+
keys: [
|
|
147
|
+
m(args.channel, true, false),
|
|
148
|
+
m(args.payer, false, false),
|
|
149
|
+
m(args.rentPayer, true, false),
|
|
150
|
+
m(args.channelAta, true, false),
|
|
151
|
+
m(args.payerAta, true, false),
|
|
152
|
+
m(args.payeeAta, true, false),
|
|
153
|
+
m(args.treasuryAta, true, false),
|
|
154
|
+
m(args.mint, false, false),
|
|
155
|
+
m(ids_1.TOKEN_PROGRAM_ID, false, false),
|
|
156
|
+
m(args.eventAuthority, false, false),
|
|
157
|
+
m(args.programId, false, false),
|
|
158
|
+
...(args.recipientAtas ?? []).map((a) => m(a, true, false)),
|
|
159
|
+
],
|
|
160
|
+
data,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
78
163
|
function deriveChannel(programId, payer, payee, mint, authorizedSigner, salt, openSlot) {
|
|
79
164
|
const [channel] = (0, pda_1.channelPda)(programId, payer, payee, mint, authorizedSigner, salt, openSlot);
|
|
80
165
|
return { channel };
|
package/dist/src/harbor-ix.d.ts
CHANGED
|
@@ -16,5 +16,11 @@ export interface SubmitReceiptArgs {
|
|
|
16
16
|
}
|
|
17
17
|
export declare function submitReceiptIx(programId: PublicKey, merchant: PublicKey, bond: PublicKey, binding: PublicKey, receipt: PublicKey, args: SubmitReceiptArgs): TransactionInstruction;
|
|
18
18
|
export declare function openDisputeIx(programId: PublicKey, claimant: PublicKey, bond: PublicKey, binding: PublicKey, channel: PublicKey, dispute: PublicKey, claim: PublicKey, mint: PublicKey, claimantAta: PublicKey, vault: PublicKey, nonce: bigint, reason: number, claimSpend: bigint): TransactionInstruction;
|
|
19
|
+
/**
|
|
20
|
+
* Closes an EMPTY bond (amount == 0, no open disputes) and its empty
|
|
21
|
+
* vault. Discriminator already tabled above (method-validated against
|
|
22
|
+
* submit_receipt); accounts mirror RefundUnused in the program.
|
|
23
|
+
*/
|
|
24
|
+
export declare function refundUnusedIx(programId: PublicKey, merchant: PublicKey, bond: PublicKey, mint: PublicKey, vault: PublicKey, merchantAta: PublicKey): TransactionInstruction;
|
|
19
25
|
export declare function haltBindingIx(programId: PublicKey, merchant: PublicKey, binding: PublicKey): TransactionInstruction;
|
|
20
26
|
export declare function resolveTimeoutIx(programId: PublicKey, resolver: PublicKey, bond: PublicKey, mint: PublicKey, binding: PublicKey, dispute: PublicKey, claimant: PublicKey, treasury: PublicKey, vault: PublicKey, treasuryAta: PublicKey, claimantAta: PublicKey, nonce: bigint): TransactionInstruction;
|
package/dist/src/harbor-ix.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.withdrawTreasuryIx = withdrawTreasuryIx;
|
|
|
8
8
|
exports.bindChannelIx = bindChannelIx;
|
|
9
9
|
exports.submitReceiptIx = submitReceiptIx;
|
|
10
10
|
exports.openDisputeIx = openDisputeIx;
|
|
11
|
+
exports.refundUnusedIx = refundUnusedIx;
|
|
11
12
|
exports.haltBindingIx = haltBindingIx;
|
|
12
13
|
exports.resolveTimeoutIx = resolveTimeoutIx;
|
|
13
14
|
const web3_js_1 = require("@solana/web3.js");
|
|
@@ -151,6 +152,21 @@ function openDisputeIx(programId, claimant, bond, binding, channel, dispute, cla
|
|
|
151
152
|
{ key: ids_1.SYSTEM_PROGRAM_ID },
|
|
152
153
|
]);
|
|
153
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Closes an EMPTY bond (amount == 0, no open disputes) and its empty
|
|
157
|
+
* vault. Discriminator already tabled above (method-validated against
|
|
158
|
+
* submit_receipt); accounts mirror RefundUnused in the program.
|
|
159
|
+
*/
|
|
160
|
+
function refundUnusedIx(programId, merchant, bond, mint, vault, merchantAta) {
|
|
161
|
+
return keys(programId, D.refundUnused, Buffer.alloc(0), [
|
|
162
|
+
{ key: merchant, w: true, s: true },
|
|
163
|
+
{ key: bond, w: true },
|
|
164
|
+
{ key: mint },
|
|
165
|
+
{ key: vault, w: true },
|
|
166
|
+
{ key: merchantAta, w: true },
|
|
167
|
+
{ key: ids_1.TOKEN_PROGRAM_ID, w: false },
|
|
168
|
+
]);
|
|
169
|
+
}
|
|
154
170
|
function haltBindingIx(programId, merchant, binding) {
|
|
155
171
|
return keys(programId, D.haltBinding, Buffer.alloc(0), [
|
|
156
172
|
{ key: merchant, w: true, s: true },
|
package/dist/src/pda.d.ts
CHANGED
|
@@ -9,4 +9,6 @@ export declare function claimPda(binding: PublicKey, nonce: bigint): [PublicKey,
|
|
|
9
9
|
export declare function channelPda(channelProgram: PublicKey, payer: PublicKey, payee: PublicKey, mint: PublicKey, authorizedSigner: PublicKey, salt: bigint, openSlot: bigint): [PublicKey, number];
|
|
10
10
|
/** Canonical associated token address for (owner, mint). */
|
|
11
11
|
export declare function ataFor(owner: PublicKey, mint: PublicKey): PublicKey;
|
|
12
|
+
/** Bond vault: the canonical ATA of the bond PDA (holds collateral). */
|
|
13
|
+
export declare function vaultAta(bond: PublicKey, mint: PublicKey): PublicKey;
|
|
12
14
|
export declare function channelAta(channel: PublicKey, tokenProgram: PublicKey, mint: PublicKey, ataProgram: PublicKey): [PublicKey, number];
|
package/dist/src/pda.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.treasuryPda = treasuryPda;
|
|
|
8
8
|
exports.claimPda = claimPda;
|
|
9
9
|
exports.channelPda = channelPda;
|
|
10
10
|
exports.ataFor = ataFor;
|
|
11
|
+
exports.vaultAta = vaultAta;
|
|
11
12
|
exports.channelAta = channelAta;
|
|
12
13
|
const web3_js_1 = require("@solana/web3.js");
|
|
13
14
|
const ids_1 = require("./ids");
|
|
@@ -52,6 +53,10 @@ function channelPda(channelProgram, payer, payee, mint, authorizedSigner, salt,
|
|
|
52
53
|
function ataFor(owner, mint) {
|
|
53
54
|
return web3_js_1.PublicKey.findProgramAddressSync([owner.toBuffer(), ids_2.TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()], ids_2.ATA_PROGRAM_ID)[0];
|
|
54
55
|
}
|
|
56
|
+
/** Bond vault: the canonical ATA of the bond PDA (holds collateral). */
|
|
57
|
+
function vaultAta(bond, mint) {
|
|
58
|
+
return ataFor(bond, mint);
|
|
59
|
+
}
|
|
55
60
|
function channelAta(channel, tokenProgram, mint, ataProgram) {
|
|
56
61
|
return web3_js_1.PublicKey.findProgramAddressSync([channel.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()], ataProgram);
|
|
57
62
|
}
|
package/dist/src/receipt.d.ts
CHANGED
|
@@ -13,3 +13,17 @@ export interface ReceiptFields {
|
|
|
13
13
|
/** Borsh encoding of the onchain ReceiptMessage. Field order is consensus. */
|
|
14
14
|
export declare function receiptMessageBytes(f: ReceiptFields): Buffer;
|
|
15
15
|
export declare const RECEIPT_MESSAGE_LEN = 185;
|
|
16
|
+
/**
|
|
17
|
+
* Byte offset of expirySlot in the 185-byte receipt message
|
|
18
|
+
* (merchant 32 | binding 32 | cumulative 8 | meter 32 | output 32 |
|
|
19
|
+
* status 1 | nonce 8 | expiry 8 | signer 32).
|
|
20
|
+
*/
|
|
21
|
+
export declare const RECEIPT_EXPIRY_OFFSET = 145;
|
|
22
|
+
/** Reads the expiry slot from encoded receipt message bytes. */
|
|
23
|
+
export declare function receiptExpirySlot(message: Uint8Array): bigint;
|
|
24
|
+
/**
|
|
25
|
+
* Buyer-side expiry check: a receipt is expired once the chain passes its
|
|
26
|
+
* expiry slot (mirrors the onchain `slot <= expiry_slot` gate in
|
|
27
|
+
* submit_receipt — expired receipts fail submit with `Expired`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function isReceiptExpired(message: Uint8Array, slot: bigint | number): boolean;
|
package/dist/src/receipt.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RECEIPT_MESSAGE_LEN = void 0;
|
|
3
|
+
exports.RECEIPT_EXPIRY_OFFSET = exports.RECEIPT_MESSAGE_LEN = void 0;
|
|
4
4
|
exports.receiptMessageBytes = receiptMessageBytes;
|
|
5
|
+
exports.receiptExpirySlot = receiptExpirySlot;
|
|
6
|
+
exports.isReceiptExpired = isReceiptExpired;
|
|
5
7
|
const u64_1 = require("./u64");
|
|
6
8
|
function assertLen(b, n, name) {
|
|
7
9
|
if (b.length !== n)
|
|
@@ -34,3 +36,25 @@ function receiptMessageBytes(f) {
|
|
|
34
36
|
return out;
|
|
35
37
|
}
|
|
36
38
|
exports.RECEIPT_MESSAGE_LEN = 185;
|
|
39
|
+
/**
|
|
40
|
+
* Byte offset of expirySlot in the 185-byte receipt message
|
|
41
|
+
* (merchant 32 | binding 32 | cumulative 8 | meter 32 | output 32 |
|
|
42
|
+
* status 1 | nonce 8 | expiry 8 | signer 32).
|
|
43
|
+
*/
|
|
44
|
+
exports.RECEIPT_EXPIRY_OFFSET = 145;
|
|
45
|
+
/** Reads the expiry slot from encoded receipt message bytes. */
|
|
46
|
+
function receiptExpirySlot(message) {
|
|
47
|
+
let v = 0n;
|
|
48
|
+
for (let i = 0; i < 8; i++) {
|
|
49
|
+
v |= BigInt(message[exports.RECEIPT_EXPIRY_OFFSET + i]) << BigInt(i * 8);
|
|
50
|
+
}
|
|
51
|
+
return v;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Buyer-side expiry check: a receipt is expired once the chain passes its
|
|
55
|
+
* expiry slot (mirrors the onchain `slot <= expiry_slot` gate in
|
|
56
|
+
* submit_receipt — expired receipts fail submit with `Expired`).
|
|
57
|
+
*/
|
|
58
|
+
function isReceiptExpired(message, slot) {
|
|
59
|
+
return BigInt(slot) > receiptExpirySlot(message);
|
|
60
|
+
}
|
|
@@ -432,3 +432,93 @@ const P = (n) => new web3_js_1.PublicKey(Buffer.alloc(32, n));
|
|
|
432
432
|
strict_1.default.equal(s.nonce, 9n);
|
|
433
433
|
});
|
|
434
434
|
});
|
|
435
|
+
(0, node_test_1.describe)("upstream close lifecycle builders", () => {
|
|
436
|
+
const PROGRAM = P(1);
|
|
437
|
+
(0, node_test_1.it)("requestClose carries disc 5 with payer signer + channel", () => {
|
|
438
|
+
const ix = (0, index_1.requestCloseIx)({
|
|
439
|
+
programId: PROGRAM,
|
|
440
|
+
payer: P(2),
|
|
441
|
+
channel: P(6),
|
|
442
|
+
});
|
|
443
|
+
strict_1.default.deepEqual(Array.from(ix.data), [5]);
|
|
444
|
+
strict_1.default.equal(ix.keys.length, 2);
|
|
445
|
+
strict_1.default.equal(ix.keys[0]?.isSigner, true);
|
|
446
|
+
});
|
|
447
|
+
(0, node_test_1.it)("seal carries disc 6 with channel only", () => {
|
|
448
|
+
const ix = (0, index_1.sealIx)({ programId: PROGRAM, channel: P(6) });
|
|
449
|
+
strict_1.default.deepEqual(Array.from(ix.data), [6]);
|
|
450
|
+
strict_1.default.equal(ix.keys.length, 1);
|
|
451
|
+
});
|
|
452
|
+
(0, node_test_1.it)("withdrawPayer carries disc 8 with 6 accounts", () => {
|
|
453
|
+
const ix = (0, index_1.withdrawPayerIx)({
|
|
454
|
+
programId: PROGRAM,
|
|
455
|
+
payer: P(2),
|
|
456
|
+
channel: P(6),
|
|
457
|
+
channelAta: P(8),
|
|
458
|
+
payerAta: P(7),
|
|
459
|
+
mint: P(4),
|
|
460
|
+
});
|
|
461
|
+
strict_1.default.deepEqual(Array.from(ix.data), [8]);
|
|
462
|
+
strict_1.default.equal(ix.keys.length, 6);
|
|
463
|
+
strict_1.default.equal(ix.keys[0]?.isSigner, true);
|
|
464
|
+
});
|
|
465
|
+
(0, node_test_1.it)("reclaim carries disc 9 with channel + rentPayer", () => {
|
|
466
|
+
const ix = (0, index_1.reclaimIx)({
|
|
467
|
+
programId: PROGRAM,
|
|
468
|
+
channel: P(6),
|
|
469
|
+
rentPayer: P(2),
|
|
470
|
+
});
|
|
471
|
+
strict_1.default.deepEqual(Array.from(ix.data), [9]);
|
|
472
|
+
strict_1.default.equal(ix.keys.length, 2);
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
(0, node_test_1.describe)("receipt expiry readers", () => {
|
|
476
|
+
const F = {
|
|
477
|
+
merchant: P(1),
|
|
478
|
+
binding: P(2),
|
|
479
|
+
cumulativeSpend: 10000n,
|
|
480
|
+
meterHash: new Uint8Array(32).fill(3),
|
|
481
|
+
outputHash: new Uint8Array(32).fill(4),
|
|
482
|
+
status: 0,
|
|
483
|
+
nonce: 7n,
|
|
484
|
+
expirySlot: 999n,
|
|
485
|
+
signer: P(1),
|
|
486
|
+
};
|
|
487
|
+
(0, node_test_1.it)("reads back the encoded expiry slot", () => {
|
|
488
|
+
const msg = (0, index_1.receiptMessageBytes)(F);
|
|
489
|
+
strict_1.default.equal((0, index_1.receiptExpirySlot)(msg), 999n);
|
|
490
|
+
});
|
|
491
|
+
(0, node_test_1.it)("expires strictly past the slot (mirrors onchain <= gate)", () => {
|
|
492
|
+
const msg = (0, index_1.receiptMessageBytes)(F);
|
|
493
|
+
strict_1.default.equal((0, index_1.isReceiptExpired)(msg, 998), false);
|
|
494
|
+
strict_1.default.equal((0, index_1.isReceiptExpired)(msg, 999), false);
|
|
495
|
+
strict_1.default.equal((0, index_1.isReceiptExpired)(msg, 1000), true);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
(0, node_test_1.describe)("refundUnusedIx", () => {
|
|
499
|
+
(0, node_test_1.it)("carries the tabled discriminator with 6 accounts", () => {
|
|
500
|
+
const ix = (0, index_1.refundUnusedIx)(P(0), P(1), P(2), P(3), P(4), P(5));
|
|
501
|
+
strict_1.default.deepEqual(Array.from(ix.data), [239, 108, 1, 110, 2, 81, 44, 174]);
|
|
502
|
+
strict_1.default.equal(ix.keys.length, 6);
|
|
503
|
+
strict_1.default.equal(ix.keys[0]?.isSigner, true);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
(0, node_test_1.describe)("distributeIx (empty plan)", () => {
|
|
507
|
+
(0, node_test_1.it)("encodes disc 7 + zero count with 11 fixed accounts, no signers", () => {
|
|
508
|
+
const ix = (0, index_1.distributeIx)({
|
|
509
|
+
programId: P(1),
|
|
510
|
+
channel: P(6),
|
|
511
|
+
payer: P(2),
|
|
512
|
+
rentPayer: P(2),
|
|
513
|
+
channelAta: P(8),
|
|
514
|
+
payerAta: P(7),
|
|
515
|
+
payeeAta: P(10),
|
|
516
|
+
treasuryAta: P(11),
|
|
517
|
+
mint: P(4),
|
|
518
|
+
eventAuthority: P(9),
|
|
519
|
+
});
|
|
520
|
+
strict_1.default.deepEqual(Array.from(ix.data), [7, 0, 0, 0, 0]);
|
|
521
|
+
strict_1.default.equal(ix.keys.length, 11);
|
|
522
|
+
strict_1.default.ok(ix.keys.every((k) => k.isSigner === false));
|
|
523
|
+
});
|
|
524
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@infantmen-labs/harbor-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "TypeScript SDK for Harbor bonded refunds on Solana: PDA helpers, instruction builders, receipt codec, ed25519 verify, account decoders.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|