@sly-rentals/core 5.5.2 → 6.1.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/dist/api.json +3556 -3373
- package/dist/cjs/doctor/contract.js +106 -66
- package/dist/cjs/doctor/contract.js.map +1 -1
- package/dist/cjs/doctor/contract.test.js +73 -2
- package/dist/cjs/doctor/contract.test.js.map +1 -1
- package/dist/cjs/generated/codama/instructions/closeRental.js +12 -0
- package/dist/cjs/generated/codama/instructions/closeRental.js.map +1 -1
- package/dist/cjs/idl/srsly.json +53 -2
- package/dist/cjs/instructions/closeContract.js +27 -41
- package/dist/cjs/instructions/closeContract.js.map +1 -1
- package/dist/cjs/instructions/closeContract.test.js +88 -0
- package/dist/cjs/instructions/closeContract.test.js.map +1 -1
- package/dist/cjs/instructions/closeContractThread.js +4 -4
- package/dist/cjs/instructions/closeContractThread.js.map +1 -1
- package/dist/cjs/instructions/closeRental.js +12 -1
- package/dist/cjs/instructions/closeRental.js.map +1 -1
- package/dist/cjs/instructions/deleteContractThread.js +4 -4
- package/dist/cjs/instructions/deleteContractThread.js.map +1 -1
- package/dist/cjs/legacy/closeContract.test.js +107 -0
- package/dist/cjs/legacy/closeContract.test.js.map +1 -0
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/utils/fibers.js +12 -16
- package/dist/cjs/utils/fibers.js.map +1 -1
- package/dist/cjs/utils/fibers.test.js +21 -28
- package/dist/cjs/utils/fibers.test.js.map +1 -1
- package/dist/cjs/utils/rpc.js +43 -0
- package/dist/cjs/utils/rpc.js.map +1 -1
- package/dist/cjs/utils/rpc.test.js +60 -0
- package/dist/cjs/utils/rpc.test.js.map +1 -0
- package/dist/cjs/version.js +1 -1
- package/dist/esm/doctor/contract.js +107 -67
- package/dist/esm/doctor/contract.js.map +1 -1
- package/dist/esm/doctor/contract.test.js +73 -2
- package/dist/esm/doctor/contract.test.js.map +1 -1
- package/dist/esm/generated/codama/instructions/closeRental.js +12 -0
- package/dist/esm/generated/codama/instructions/closeRental.js.map +1 -1
- package/dist/esm/idl/srsly.json +53 -2
- package/dist/esm/instructions/closeContract.js +29 -43
- package/dist/esm/instructions/closeContract.js.map +1 -1
- package/dist/esm/instructions/closeContract.test.js +88 -0
- package/dist/esm/instructions/closeContract.test.js.map +1 -1
- package/dist/esm/instructions/closeContractThread.js +6 -6
- package/dist/esm/instructions/closeContractThread.js.map +1 -1
- package/dist/esm/instructions/closeRental.js +13 -2
- package/dist/esm/instructions/closeRental.js.map +1 -1
- package/dist/esm/instructions/deleteContractThread.js +6 -6
- package/dist/esm/instructions/deleteContractThread.js.map +1 -1
- package/dist/esm/legacy/closeContract.test.js +105 -0
- package/dist/esm/legacy/closeContract.test.js.map +1 -0
- package/dist/esm/package.json +1 -1
- package/dist/esm/utils/fibers.js +11 -15
- package/dist/esm/utils/fibers.js.map +1 -1
- package/dist/esm/utils/fibers.test.js +22 -29
- package/dist/esm/utils/fibers.test.js.map +1 -1
- package/dist/esm/utils/rpc.js +41 -0
- package/dist/esm/utils/rpc.js.map +1 -1
- package/dist/esm/utils/rpc.test.js +58 -0
- package/dist/esm/utils/rpc.test.js.map +1 -0
- package/dist/esm/version.js +1 -1
- package/dist/idl/srsly.json +53 -2
- package/dist/types/doctor/contract.d.ts +23 -1
- package/dist/types/doctor/contract.d.ts.map +1 -1
- package/dist/types/generated/codama/instructions/closeRental.d.ts +46 -1
- package/dist/types/generated/codama/instructions/closeRental.d.ts.map +1 -1
- package/dist/types/instructions/closeContract.d.ts +6 -3
- package/dist/types/instructions/closeContract.d.ts.map +1 -1
- package/dist/types/instructions/closeContractThread.d.ts.map +1 -1
- package/dist/types/instructions/closeRental.d.ts.map +1 -1
- package/dist/types/instructions/deleteContractThread.d.ts.map +1 -1
- package/dist/types/legacy/closeContract.test.d.ts +2 -0
- package/dist/types/legacy/closeContract.test.d.ts.map +1 -0
- package/dist/types/utils/fibers.d.ts +10 -9
- package/dist/types/utils/fibers.d.ts.map +1 -1
- package/dist/types/utils/rpc.d.ts +18 -0
- package/dist/types/utils/rpc.d.ts.map +1 -1
- package/dist/types/utils/rpc.test.d.ts +2 -0
- package/dist/types/utils/rpc.test.d.ts.map +1 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The legacy subpath only converts: one instruction in, one out, in order. A
|
|
3
|
+
* duplicate here would be invisible at the call site and land two identical
|
|
4
|
+
* `rental_close` instructions in one transaction.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
7
|
+
vi.mock('../utils/contractSnapshot', () => ({
|
|
8
|
+
resolveContractSnapshot: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
vi.mock('../instructions/closeRental', () => ({
|
|
11
|
+
closeRental: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
import { resolveContractSnapshot } from '../utils/contractSnapshot';
|
|
14
|
+
import { closeRental } from '../instructions/closeRental';
|
|
15
|
+
import { closeContract, setSdkConfig } from './index';
|
|
16
|
+
import { CLOSE_CONTRACT_DISCRIMINATOR } from '../generated/codama/instructions/closeContract';
|
|
17
|
+
const SYSTEM = '11111111111111111111111111111111';
|
|
18
|
+
const CONTRACT_ADDR = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
|
|
19
|
+
const ACTIVE_ADDR = 'So11111111111111111111111111111111111111112';
|
|
20
|
+
const OWNER_ADDR = 'BPFLoader2111111111111111111111111111111111';
|
|
21
|
+
const MINT_ADDR = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
|
|
22
|
+
/** Minimal stand-in for web3.js PublicKey — `toLegacy` only stringifies. */
|
|
23
|
+
class StubPublicKey {
|
|
24
|
+
value;
|
|
25
|
+
constructor(value) {
|
|
26
|
+
this.value = String(value);
|
|
27
|
+
}
|
|
28
|
+
toString() {
|
|
29
|
+
return this.value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const OWNER_SIGNER = {
|
|
33
|
+
address: OWNER_ADDR,
|
|
34
|
+
signTransactionMessage: vi.fn(),
|
|
35
|
+
};
|
|
36
|
+
/** Marker for the instruction `closeContract` folds in from `closeRental`. */
|
|
37
|
+
const RENTAL_MARKER = new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd]);
|
|
38
|
+
/** Expired active rental, no queued reservation, thread already gone. */
|
|
39
|
+
function mockExpiredActiveNoThread() {
|
|
40
|
+
vi.mocked(resolveContractSnapshot).mockResolvedValue({
|
|
41
|
+
config: {
|
|
42
|
+
data: { atlasMint: MINT_ADDR, slyvault: MINT_ADDR },
|
|
43
|
+
address: 'CFG',
|
|
44
|
+
},
|
|
45
|
+
contract: {
|
|
46
|
+
data: {
|
|
47
|
+
owner: OWNER_ADDR,
|
|
48
|
+
fleet: MINT_ADDR,
|
|
49
|
+
gameId: MINT_ADDR,
|
|
50
|
+
activeRental: ACTIVE_ADDR,
|
|
51
|
+
queuedRental: SYSTEM,
|
|
52
|
+
thread: SYSTEM,
|
|
53
|
+
managedTokenAccount: MINT_ADDR,
|
|
54
|
+
},
|
|
55
|
+
address: CONTRACT_ADDR,
|
|
56
|
+
},
|
|
57
|
+
activeRental: {
|
|
58
|
+
data: {
|
|
59
|
+
borrower: OWNER_ADDR,
|
|
60
|
+
borrowerProfile: MINT_ADDR,
|
|
61
|
+
endTime: 1n,
|
|
62
|
+
},
|
|
63
|
+
address: ACTIVE_ADDR,
|
|
64
|
+
},
|
|
65
|
+
queuedRental: null,
|
|
66
|
+
nowSeconds: 1_700_000_000,
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
});
|
|
69
|
+
vi.mocked(closeRental).mockResolvedValue({
|
|
70
|
+
instructions: [{ programAddress: CONTRACT_ADDR, accounts: [], data: RENTAL_MARKER }],
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
vi.clearAllMocks();
|
|
76
|
+
setSdkConfig({ PublicKey: StubPublicKey });
|
|
77
|
+
});
|
|
78
|
+
describe('legacy closeContract — conversion does not duplicate', () => {
|
|
79
|
+
it('builds the underlying rental close exactly once', async () => {
|
|
80
|
+
mockExpiredActiveNoThread();
|
|
81
|
+
await closeContract({ owner: OWNER_SIGNER, contract: CONTRACT_ADDR });
|
|
82
|
+
expect(closeRental).toHaveBeenCalledTimes(1);
|
|
83
|
+
});
|
|
84
|
+
it('returns one legacy instruction per kit instruction, in order', async () => {
|
|
85
|
+
mockExpiredActiveNoThread();
|
|
86
|
+
const ixs = await closeContract({ owner: OWNER_SIGNER, contract: CONTRACT_ADDR });
|
|
87
|
+
const startsWith = (data, prefix) => prefix.every((b, i) => data[i] === b);
|
|
88
|
+
expect(ixs).toHaveLength(2);
|
|
89
|
+
expect(startsWith(ixs[0].data, RENTAL_MARKER)).toBe(true);
|
|
90
|
+
expect(startsWith(ixs[1].data, CLOSE_CONTRACT_DISCRIMINATOR)).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it('converts to web3.js shape without cloning entries', async () => {
|
|
93
|
+
mockExpiredActiveNoThread();
|
|
94
|
+
const ixs = await closeContract({ owner: OWNER_SIGNER, contract: CONTRACT_ADDR });
|
|
95
|
+
// Every entry carries a PublicKey-shaped programId and a keys array —
|
|
96
|
+
// the legacy contract — and there are no repeats of the same data buffer.
|
|
97
|
+
const seen = ixs.map(ix => String(ix.programId) + ':' + Array.from(ix.data).join());
|
|
98
|
+
expect(new Set(seen).size).toBe(ixs.length);
|
|
99
|
+
for (const ix of ixs) {
|
|
100
|
+
expect(ix.programId).toBeInstanceOf(StubPublicKey);
|
|
101
|
+
expect(Array.isArray(ix.keys)).toBe(true);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=closeContract.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"closeContract.test.js","sourceRoot":"","sources":["../../../src/legacy/closeContract.test.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/package.json
CHANGED
package/dist/esm/utils/fibers.js
CHANGED
|
@@ -3,33 +3,29 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { AccountRole } from '@solana/kit';
|
|
5
5
|
import { deriveFiber } from '../pda/thread';
|
|
6
|
-
import { createRpc } from './rpc';
|
|
7
6
|
/**
|
|
8
7
|
* The fiber accounts a close instruction should carry, given what a thread
|
|
9
|
-
* tracks
|
|
8
|
+
* tracks: one per entry in `fiber_ids`, whether or not the account it names
|
|
9
|
+
* still exists.
|
|
10
10
|
*
|
|
11
11
|
* `fiber_ids` records the indices a thread *tracks*, and nothing on chain
|
|
12
12
|
* keeps that list in agreement with the accounts it names — a fiber account
|
|
13
13
|
* can be closed out from under a thread that still lists it, which is what
|
|
14
14
|
* the 2026-08-26 rent sweep did to 623 of them.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* Reconciling the two is the thread program's job, not ours. It is the only
|
|
17
|
+
* participant that can see the list and the accounts *and* edit the list;
|
|
18
|
+
* dropping an index here asserts it needs no accounting for, which is not
|
|
19
|
+
* ours to assert and which `thread_close` rejects — it requires every tracked
|
|
20
|
+
* id to be accounted for and fails with `MissingFiberAccounts` otherwise.
|
|
21
|
+
* Passing the account is how an id whose account is gone gets accounted for.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
-
* whole remedy. One `getMultipleAccounts` for the batch.
|
|
23
|
+
* Deriving an address is pure computation, so this needs no RPC.
|
|
24
24
|
*/
|
|
25
|
-
export async function
|
|
25
|
+
export async function trackedFiberAccounts(thread, fiberIds) {
|
|
26
26
|
if (!fiberIds.length)
|
|
27
27
|
return [];
|
|
28
28
|
const addresses = await Promise.all(fiberIds.map(id => deriveFiber(thread, id)));
|
|
29
|
-
|
|
30
|
-
const { value } = await rpc.getMultipleAccounts(addresses, { encoding: 'base64' }).send();
|
|
31
|
-
return addresses
|
|
32
|
-
.filter((_, i) => value[i] !== null)
|
|
33
|
-
.map(address => ({ address, role: AccountRole.WRITABLE }));
|
|
29
|
+
return addresses.map(address => ({ address, role: AccountRole.WRITABLE }));
|
|
34
30
|
}
|
|
35
31
|
//# sourceMappingURL=fibers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fibers.js","sourceRoot":"","sources":["../../../src/utils/fibers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"fibers.js","sourceRoot":"","sources":["../../../src/utils/fibers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAe,EACf,QAAkB;IAElB,IAAI,CAAC,QAAQ,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEhC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAEjF,OAAO,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,QAAiB,EAAE,CAAC,CAAC,CAAC;AACtF,CAAC"}
|
|
@@ -1,49 +1,42 @@
|
|
|
1
1
|
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { AccountRole } from '@solana/kit';
|
|
3
|
-
vi.mock('./rpc', () => ({ createRpc: vi.fn() }));
|
|
4
3
|
vi.mock('../pda/thread', () => ({ deriveFiber: vi.fn() }));
|
|
5
|
-
import { createRpc } from './rpc';
|
|
6
4
|
import { deriveFiber } from '../pda/thread';
|
|
7
|
-
import {
|
|
5
|
+
import { trackedFiberAccounts } from './fibers';
|
|
8
6
|
const THREAD = 'Sysvar1nstructions1111111111111111111111111';
|
|
9
7
|
const FIBER_0 = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
|
|
10
8
|
const FIBER_1 = 'So11111111111111111111111111111111111111112';
|
|
11
|
-
/** `getMultipleAccounts` returns `null` in the slot of an account that does not exist. */
|
|
12
|
-
function mockAccounts(value) {
|
|
13
|
-
const send = vi.fn().mockResolvedValue({ value });
|
|
14
|
-
vi.mocked(createRpc).mockReturnValue({
|
|
15
|
-
getMultipleAccounts: vi.fn().mockReturnValue({ send }),
|
|
16
|
-
});
|
|
17
|
-
return send;
|
|
18
|
-
}
|
|
19
9
|
beforeEach(() => {
|
|
20
10
|
vi.clearAllMocks();
|
|
21
11
|
vi.mocked(deriveFiber).mockImplementation(async (_thread, index) => index === 0 ? FIBER_0 : FIBER_1);
|
|
22
12
|
});
|
|
23
|
-
describe('
|
|
24
|
-
it('
|
|
13
|
+
describe('trackedFiberAccounts', () => {
|
|
14
|
+
it('carries a tracked fiber whose account is gone', async () => {
|
|
25
15
|
// The state the 2026-08-26 rent sweep left behind: the thread still tracks
|
|
26
|
-
// both indices, but fiber 1's account was closed.
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
const accounts = await
|
|
31
|
-
expect(accounts).toEqual([
|
|
16
|
+
// both indices, but fiber 1's account was closed. The id has to be
|
|
17
|
+
// accounted for or `thread_close` fails with `MissingFiberAccounts`, and
|
|
18
|
+
// passing the account is the only way to account for it — dropping the id
|
|
19
|
+
// is the thread program's call, and only it can edit `fiber_ids`.
|
|
20
|
+
const accounts = await trackedFiberAccounts(THREAD, [0, 1]);
|
|
21
|
+
expect(accounts).toEqual([
|
|
22
|
+
{ address: FIBER_0, role: AccountRole.WRITABLE },
|
|
23
|
+
{ address: FIBER_1, role: AccountRole.WRITABLE },
|
|
24
|
+
]);
|
|
32
25
|
});
|
|
33
|
-
it('
|
|
34
|
-
|
|
35
|
-
const accounts = await existingFiberAccounts(THREAD, [0, 1], 'https://rpc.example');
|
|
26
|
+
it('carries every fiber when all of them exist', async () => {
|
|
27
|
+
const accounts = await trackedFiberAccounts(THREAD, [0, 1]);
|
|
36
28
|
expect(accounts.map(a => a.address)).toEqual([FIBER_0, FIBER_1]);
|
|
37
29
|
});
|
|
38
|
-
it('returns nothing when the thread tracks nothing
|
|
39
|
-
const
|
|
40
|
-
const accounts = await existingFiberAccounts(THREAD, [], 'https://rpc.example');
|
|
30
|
+
it('returns nothing when the thread tracks nothing', async () => {
|
|
31
|
+
const accounts = await trackedFiberAccounts(THREAD, []);
|
|
41
32
|
expect(accounts).toEqual([]);
|
|
42
|
-
expect(
|
|
33
|
+
expect(deriveFiber).not.toHaveBeenCalled();
|
|
43
34
|
});
|
|
44
|
-
it('
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
it('reads no account state, so it needs no RPC', async () => {
|
|
36
|
+
// Existence is not consulted at all — the addresses are pure computation
|
|
37
|
+
// from the thread and the ids.
|
|
38
|
+
await trackedFiberAccounts(THREAD, [0, 1]);
|
|
39
|
+
expect(deriveFiber).toHaveBeenCalledTimes(2);
|
|
47
40
|
});
|
|
48
41
|
});
|
|
49
42
|
//# sourceMappingURL=fibers.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fibers.test.js","sourceRoot":"","sources":["../../../src/utils/fibers.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AAExD,EAAE,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"fibers.test.js","sourceRoot":"","sources":["../../../src/utils/fibers.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AAExD,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAEhD,MAAM,MAAM,GAAG,6CAAwD,CAAC;AACxE,MAAM,OAAO,GAAG,8CAAyD,CAAC;AAC1E,MAAM,OAAO,GAAG,6CAAwD,CAAC;AAEzE,UAAU,CAAC,GAAG,EAAE;IACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACnB,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CACjE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAChC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,2EAA2E;QAC3E,mEAAmE;QACnE,yEAAyE;QACzE,0EAA0E;QAC1E,kEAAkE;QAClE,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAE5D,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC;YACvB,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,QAAQ,EAAE;YAChD,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,QAAQ,EAAE;SACjD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAE5D,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAExD,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7B,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,yEAAyE;QACzE,+BAA+B;QAC/B,MAAM,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAE3C,MAAM,CAAC,WAAW,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/esm/utils/rpc.js
CHANGED
|
@@ -49,4 +49,45 @@ export function createRpc(rpcUrl) {
|
|
|
49
49
|
}
|
|
50
50
|
return createSolanaRpc(rpcUrl);
|
|
51
51
|
}
|
|
52
|
+
/** Largest account list `getMultipleAccounts` accepts in one request. */
|
|
53
|
+
export const MAX_MULTIPLE_ACCOUNTS = 100;
|
|
54
|
+
/** Providers signal "slow down" as 429, and the wording varies by host. */
|
|
55
|
+
function isRateLimited(error) {
|
|
56
|
+
const e = error;
|
|
57
|
+
if (e?.statusCode === 429 || e?.status === 429)
|
|
58
|
+
return true;
|
|
59
|
+
const message = String(e?.message ?? error);
|
|
60
|
+
return message.includes('429') || /too many requests/i.test(message);
|
|
61
|
+
}
|
|
62
|
+
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
63
|
+
/**
|
|
64
|
+
* Run an RPC call, backing off and retrying when the provider rate-limits it.
|
|
65
|
+
*
|
|
66
|
+
* A 429 is not a result — it means the question was never asked, so a caller
|
|
67
|
+
* that treats it as one reports on state it never read. Backoff is exponential
|
|
68
|
+
* with jitter so workers that trip the limit together do not retry together.
|
|
69
|
+
*
|
|
70
|
+
* @param send - Performs the call. Invoked again on each retry.
|
|
71
|
+
* @param options.retries - Extra attempts after the first. Defaults to 5.
|
|
72
|
+
* @param options.baseDelayMs - Delay before the first retry. Defaults to 250.
|
|
73
|
+
* @throws The last error, once retries are exhausted or it is not a 429.
|
|
74
|
+
*/
|
|
75
|
+
export async function sendWithRetry(send, options = {}) {
|
|
76
|
+
const retries = options.retries ?? 5;
|
|
77
|
+
const baseDelayMs = options.baseDelayMs ?? 250;
|
|
78
|
+
let lastError;
|
|
79
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
80
|
+
try {
|
|
81
|
+
return await send();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
lastError = error;
|
|
85
|
+
if (!isRateLimited(error) || attempt === retries)
|
|
86
|
+
throw error;
|
|
87
|
+
const backoff = baseDelayMs * 2 ** attempt;
|
|
88
|
+
await delay(backoff + Math.floor(Math.random() * backoff));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw lastError;
|
|
92
|
+
}
|
|
52
93
|
//# sourceMappingURL=rpc.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../../src/utils/rpc.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAA+B,MAAM,aAAa,CAAC;AAE3E,0DAA0D;AAC1D,IAAI,SAAS,GAAG,KAAK,CAAC;AAEtB;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,SAAS,GAAG,OAAO,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,wCAAwC,EAAE;YACpD,GAAG,EAAE,MAAM;YACX,WAAW,EAAE,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;YAC/D,SAAS,EAAE,OAAO,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK;SAC1E,CAAC,CAAC;IACL,CAAC;IACD,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC"}
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../../src/utils/rpc.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAA+B,MAAM,aAAa,CAAC;AAE3E,0DAA0D;AAC1D,IAAI,SAAS,GAAG,KAAK,CAAC;AAEtB;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,SAAS,GAAG,OAAO,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,wCAAwC,EAAE;YACpD,GAAG,EAAE,MAAM;YACX,WAAW,EAAE,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;YAC/D,SAAS,EAAE,OAAO,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK;SAC1E,CAAC,CAAC;IACL,CAAC;IACD,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,yEAAyE;AACzE,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAEzC,2EAA2E;AAC3E,SAAS,aAAa,CAAC,KAAc;IACnC,MAAM,CAAC,GAAG,KAAoE,CAAC;IAC/E,IAAI,CAAC,EAAE,UAAU,KAAK,GAAG,IAAI,CAAC,EAAE,MAAM,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC5D,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAsB,EACtB,UAAsD,EAAE;IAExD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;IACrC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,GAAG,CAAC;IAE/C,IAAI,SAAkB,CAAC;IACvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC;QACpD,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,EAAE,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,GAAG,KAAK,CAAC;YAClB,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,OAAO;gBAAE,MAAM,KAAK,CAAC;YAC9D,MAAM,OAAO,GAAG,WAAW,GAAG,CAAC,IAAI,OAAO,CAAC;YAC3C,MAAM,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IACD,MAAM,SAAS,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** A 429 means the question never reached the provider, not that it answered. */
|
|
2
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import { sendWithRetry, MAX_MULTIPLE_ACCOUNTS } from './rpc';
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.useFakeTimers();
|
|
6
|
+
});
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
vi.useRealTimers();
|
|
9
|
+
});
|
|
10
|
+
/** Drives the fake clock until `promise` settles, so backoff sleeps resolve. */
|
|
11
|
+
async function runToCompletion(promise) {
|
|
12
|
+
const settled = promise.then(value => ({ ok: true, value }), error => ({ ok: false, error }));
|
|
13
|
+
await vi.runAllTimersAsync();
|
|
14
|
+
const outcome = await settled;
|
|
15
|
+
if (!outcome.ok)
|
|
16
|
+
throw outcome.error;
|
|
17
|
+
return outcome.value;
|
|
18
|
+
}
|
|
19
|
+
describe('sendWithRetry', () => {
|
|
20
|
+
it('returns the first result when nothing is rate limited', async () => {
|
|
21
|
+
const send = vi.fn().mockResolvedValue('ok');
|
|
22
|
+
await expect(runToCompletion(sendWithRetry(send))).resolves.toBe('ok');
|
|
23
|
+
expect(send).toHaveBeenCalledTimes(1);
|
|
24
|
+
});
|
|
25
|
+
it('retries a 429 and returns the eventual success', async () => {
|
|
26
|
+
const send = vi
|
|
27
|
+
.fn()
|
|
28
|
+
.mockRejectedValueOnce(new Error('HTTP error (429): Too Many Requests'))
|
|
29
|
+
.mockRejectedValueOnce(new Error('HTTP error (429): Too Many Requests'))
|
|
30
|
+
.mockResolvedValue('ok');
|
|
31
|
+
await expect(runToCompletion(sendWithRetry(send))).resolves.toBe('ok');
|
|
32
|
+
expect(send).toHaveBeenCalledTimes(3);
|
|
33
|
+
});
|
|
34
|
+
it('recognises a rate limit from a status code alone', async () => {
|
|
35
|
+
const err = Object.assign(new Error('rejected'), { statusCode: 429 });
|
|
36
|
+
const send = vi.fn().mockRejectedValueOnce(err).mockResolvedValue('ok');
|
|
37
|
+
await expect(runToCompletion(sendWithRetry(send))).resolves.toBe('ok');
|
|
38
|
+
expect(send).toHaveBeenCalledTimes(2);
|
|
39
|
+
});
|
|
40
|
+
it('does not retry an error that is not a rate limit', async () => {
|
|
41
|
+
// Retrying a decode failure or a bad request just multiplies the damage.
|
|
42
|
+
const send = vi.fn().mockRejectedValue(new Error('Invalid param: bad address'));
|
|
43
|
+
await expect(runToCompletion(sendWithRetry(send))).rejects.toThrow('Invalid param');
|
|
44
|
+
expect(send).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
it('gives up after the retry budget and surfaces the rate limit', async () => {
|
|
47
|
+
// The caller has to be able to tell "still limited" from "answered", so the
|
|
48
|
+
// final failure propagates rather than resolving to something empty.
|
|
49
|
+
const send = vi.fn().mockRejectedValue(new Error('HTTP error (429): Too Many Requests'));
|
|
50
|
+
await expect(runToCompletion(sendWithRetry(send, { retries: 2 }))).rejects.toThrow('429');
|
|
51
|
+
expect(send).toHaveBeenCalledTimes(3);
|
|
52
|
+
});
|
|
53
|
+
it('batches at the getMultipleAccounts ceiling', () => {
|
|
54
|
+
// Asking for more than this in one call is rejected outright by the RPC.
|
|
55
|
+
expect(MAX_MULTIPLE_ACCOUNTS).toBe(100);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=rpc.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.test.js","sourceRoot":"","sources":["../../../src/utils/rpc.test.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/version.js
CHANGED
package/dist/idl/srsly.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"address": "SRSLYxcFnjd5jG2DpJw4as6UEyjwJQK1U4J1TD1hvZH",
|
|
3
3
|
"metadata": {
|
|
4
4
|
"name": "srsly",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.1",
|
|
6
6
|
"spec": "0.1.0",
|
|
7
7
|
"description": "Space Rental from SLY",
|
|
8
8
|
"repository": "https://github.com/wuwei-labs/srsly"
|
|
@@ -1779,10 +1779,61 @@
|
|
|
1779
1779
|
},
|
|
1780
1780
|
{
|
|
1781
1781
|
"name": "rental_state",
|
|
1782
|
-
"
|
|
1782
|
+
"docs": [
|
|
1783
|
+
"Seeds, not `address = contract.active_rental`: `address` is checked",
|
|
1784
|
+
"before the handler runs, so a close arriving after the slot was cleared",
|
|
1785
|
+
"aborted the whole transaction with 2012 rather than no-opping. The",
|
|
1786
|
+
"pointer only ever holds this PDA or the system program, so the seeds",
|
|
1787
|
+
"prove the same ownership without that failure. Paths C and D already",
|
|
1788
|
+
"handle an empty slot."
|
|
1789
|
+
],
|
|
1790
|
+
"writable": true,
|
|
1791
|
+
"pda": {
|
|
1792
|
+
"seeds": [
|
|
1793
|
+
{
|
|
1794
|
+
"kind": "const",
|
|
1795
|
+
"value": [
|
|
1796
|
+
114,
|
|
1797
|
+
101,
|
|
1798
|
+
110,
|
|
1799
|
+
116,
|
|
1800
|
+
97,
|
|
1801
|
+
108,
|
|
1802
|
+
95,
|
|
1803
|
+
115,
|
|
1804
|
+
116,
|
|
1805
|
+
97,
|
|
1806
|
+
116,
|
|
1807
|
+
101
|
|
1808
|
+
]
|
|
1809
|
+
},
|
|
1810
|
+
{
|
|
1811
|
+
"kind": "account",
|
|
1812
|
+
"path": "contract"
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
"kind": "const",
|
|
1816
|
+
"value": [
|
|
1817
|
+
97,
|
|
1818
|
+
99,
|
|
1819
|
+
116,
|
|
1820
|
+
105,
|
|
1821
|
+
118,
|
|
1822
|
+
101
|
|
1823
|
+
]
|
|
1824
|
+
}
|
|
1825
|
+
]
|
|
1826
|
+
}
|
|
1783
1827
|
},
|
|
1784
1828
|
{
|
|
1785
1829
|
"name": "borrower_state",
|
|
1830
|
+
"docs": [
|
|
1831
|
+
"Bound to the rental being closed, but only while there is one. `reset()`",
|
|
1832
|
+
"clears `borrower_state` along with the rest of the slot, so an `address`",
|
|
1833
|
+
"constraint here would reject the empty case the same way the pointer on",
|
|
1834
|
+
"`rental_state` did. Only `process_rental_close` reads this account, and",
|
|
1835
|
+
"that runs solely when the slot is occupied."
|
|
1836
|
+
],
|
|
1786
1837
|
"writable": true
|
|
1787
1838
|
},
|
|
1788
1839
|
{
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { type Address } from '@solana/kit';
|
|
9
9
|
import { type Thread } from '../generated/codama/types/thread';
|
|
10
|
+
import { type ConfigState, type ContractState } from '../generated/codama/accounts';
|
|
10
11
|
/** What is wrong with a contract. */
|
|
11
12
|
export type ContractFindingCode =
|
|
12
13
|
/** `contract.thread` is unset — the contract has no automation at all. */
|
|
@@ -32,7 +33,19 @@ export type ContractFindingCode =
|
|
|
32
33
|
* Settlement still accrues to `owner_claimable`, but nothing can be paid
|
|
33
34
|
* out, and a contract flagged `to_close` cannot finish closing.
|
|
34
35
|
*/
|
|
35
|
-
| 'ownerTokenAccountMissing'
|
|
36
|
+
| 'ownerTokenAccountMissing'
|
|
37
|
+
/**
|
|
38
|
+
* The contract could not be read, so nothing is known about it. Not a
|
|
39
|
+
* statement about its health — it is the absence of one. Usually a rate
|
|
40
|
+
* limit or a transport error during a bulk scan.
|
|
41
|
+
*/
|
|
42
|
+
| 'diagnosisFailed'
|
|
43
|
+
/**
|
|
44
|
+
* `to_close` is latched but no live thread remains to honour it. Only
|
|
45
|
+
* `contract_process` turns the flag into a closed contract, so the contract
|
|
46
|
+
* waits on an owner who has already been told the close was accepted.
|
|
47
|
+
*/
|
|
48
|
+
| 'closeStranded';
|
|
36
49
|
export interface ContractFinding {
|
|
37
50
|
code: ContractFindingCode;
|
|
38
51
|
/** `critical` means the contract cannot make progress on its own. */
|
|
@@ -80,6 +93,15 @@ export interface DiagnoseOptions {
|
|
|
80
93
|
rpcUrl?: string;
|
|
81
94
|
/** Wall clock to judge staleness against. Defaults to now. */
|
|
82
95
|
nowSeconds?: bigint;
|
|
96
|
+
/**
|
|
97
|
+
* Accounts the caller has already read, to skip this diagnosis's own lookup
|
|
98
|
+
* of them. `diagnoseAllContracts` fills this in from its batched prefetch;
|
|
99
|
+
* a single diagnosis has no reason to.
|
|
100
|
+
*/
|
|
101
|
+
prefetched?: {
|
|
102
|
+
contract: ContractState | null;
|
|
103
|
+
config: ConfigState | null;
|
|
104
|
+
};
|
|
83
105
|
}
|
|
84
106
|
/**
|
|
85
107
|
* The contract a thread belongs to.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/doctor/contract.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAuC,MAAM,aAAa,CAAC;AAChF,OAAO,EAAoB,KAAK,MAAM,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/doctor/contract.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAuC,MAAM,aAAa,CAAC;AAChF,OAAO,EAAoB,KAAK,MAAM,EAAE,MAAM,kCAAkC,CAAC;AACjF,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AA+BtC,qCAAqC;AACrC,MAAM,MAAM,mBAAmB;AAC7B,0EAA0E;AACxE,qBAAqB;AACvB,8DAA8D;GAC5D,eAAe;AACjB;;;;GAIG;GACD,cAAc;AAChB,2EAA2E;GACzE,cAAc;AAChB,iDAAiD;GAC/C,mBAAmB;AACrB,sEAAsE;GACpE,eAAe;AACjB,6DAA6D;GAC3D,mBAAmB;AACrB;;;;GAIG;GACD,0BAA0B;AAC5B;;;;GAIG;GACD,iBAAiB;AACnB;;;;GAIG;GACD,eAAe,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAC;IACjC,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kDAAkD;IAClD,cAAc,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,mDAAmD;IACnD,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B;;;OAGG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,0DAA0D;IAC1D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;OAGG;IACH,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;QAC/B,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;KAC5B,CAAC;CACH;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,OAAO,CAEtE;AAcD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,gBAAgB,CACpC,eAAe,EAAE,OAAO,GAAG,MAAM,EACjC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CAgN5B;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,GAAE,eAAe,GAAG;IACzB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C,GACL,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAwE9B"}
|
|
@@ -49,7 +49,22 @@ export type CloseRentalAsyncInput<TAccountPayer extends string = string, TAccoun
|
|
|
49
49
|
payer: TransactionSigner<TAccountPayer>;
|
|
50
50
|
config?: Address<TAccountConfig>;
|
|
51
51
|
contract: Address<TAccountContract>;
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Seeds, not `address = contract.active_rental`: `address` is checked
|
|
54
|
+
* before the handler runs, so a close arriving after the slot was cleared
|
|
55
|
+
* aborted the whole transaction with 2012 rather than no-opping. The
|
|
56
|
+
* pointer only ever holds this PDA or the system program, so the seeds
|
|
57
|
+
* prove the same ownership without that failure. Paths C and D already
|
|
58
|
+
* handle an empty slot.
|
|
59
|
+
*/
|
|
60
|
+
rentalState?: Address<TAccountRentalState>;
|
|
61
|
+
/**
|
|
62
|
+
* Bound to the rental being closed, but only while there is one. `reset()`
|
|
63
|
+
* clears `borrower_state` along with the rest of the slot, so an `address`
|
|
64
|
+
* constraint here would reject the empty case the same way the pointer on
|
|
65
|
+
* `rental_state` did. Only `process_rental_close` reads this account, and
|
|
66
|
+
* that runs solely when the slot is occupied.
|
|
67
|
+
*/
|
|
53
68
|
borrowerState: Address<TAccountBorrowerState>;
|
|
54
69
|
/** Fleet validated by contract */
|
|
55
70
|
fleet: Address<TAccountFleet>;
|
|
@@ -82,7 +97,22 @@ export type CloseRentalInput<TAccountPayer extends string = string, TAccountConf
|
|
|
82
97
|
payer: TransactionSigner<TAccountPayer>;
|
|
83
98
|
config: Address<TAccountConfig>;
|
|
84
99
|
contract: Address<TAccountContract>;
|
|
100
|
+
/**
|
|
101
|
+
* Seeds, not `address = contract.active_rental`: `address` is checked
|
|
102
|
+
* before the handler runs, so a close arriving after the slot was cleared
|
|
103
|
+
* aborted the whole transaction with 2012 rather than no-opping. The
|
|
104
|
+
* pointer only ever holds this PDA or the system program, so the seeds
|
|
105
|
+
* prove the same ownership without that failure. Paths C and D already
|
|
106
|
+
* handle an empty slot.
|
|
107
|
+
*/
|
|
85
108
|
rentalState: Address<TAccountRentalState>;
|
|
109
|
+
/**
|
|
110
|
+
* Bound to the rental being closed, but only while there is one. `reset()`
|
|
111
|
+
* clears `borrower_state` along with the rest of the slot, so an `address`
|
|
112
|
+
* constraint here would reject the empty case the same way the pointer on
|
|
113
|
+
* `rental_state` did. Only `process_rental_close` reads this account, and
|
|
114
|
+
* that runs solely when the slot is occupied.
|
|
115
|
+
*/
|
|
86
116
|
borrowerState: Address<TAccountBorrowerState>;
|
|
87
117
|
/** Fleet validated by contract */
|
|
88
118
|
fleet: Address<TAccountFleet>;
|
|
@@ -117,7 +147,22 @@ export type ParsedCloseRentalInstruction<TProgram extends string = typeof SRSLY_
|
|
|
117
147
|
payer: TAccountMetas[0];
|
|
118
148
|
config: TAccountMetas[1];
|
|
119
149
|
contract: TAccountMetas[2];
|
|
150
|
+
/**
|
|
151
|
+
* Seeds, not `address = contract.active_rental`: `address` is checked
|
|
152
|
+
* before the handler runs, so a close arriving after the slot was cleared
|
|
153
|
+
* aborted the whole transaction with 2012 rather than no-opping. The
|
|
154
|
+
* pointer only ever holds this PDA or the system program, so the seeds
|
|
155
|
+
* prove the same ownership without that failure. Paths C and D already
|
|
156
|
+
* handle an empty slot.
|
|
157
|
+
*/
|
|
120
158
|
rentalState: TAccountMetas[3];
|
|
159
|
+
/**
|
|
160
|
+
* Bound to the rental being closed, but only while there is one. `reset()`
|
|
161
|
+
* clears `borrower_state` along with the rest of the slot, so an `address`
|
|
162
|
+
* constraint here would reject the empty case the same way the pointer on
|
|
163
|
+
* `rental_state` did. Only `process_rental_close` reads this account, and
|
|
164
|
+
* that runs solely when the slot is occupied.
|
|
165
|
+
*/
|
|
121
166
|
borrowerState: TAccountMetas[4];
|
|
122
167
|
/** Fleet validated by contract */
|
|
123
168
|
fleet: TAccountMetas[5];
|