@5ive-tech/sdk 1.1.2
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 +279 -0
- package/dist/FiveSDK.d.ts +336 -0
- package/dist/FiveSDK.js +395 -0
- package/dist/accounts/index.d.ts +254 -0
- package/dist/accounts/index.js +543 -0
- package/dist/assets/vm/dummy.file +0 -0
- package/dist/assets/vm/five_vm_wasm.d.ts +762 -0
- package/dist/assets/vm/five_vm_wasm.js +3754 -0
- package/dist/assets/vm/five_vm_wasm_bg.js +3307 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm.d.ts +247 -0
- package/dist/assets/vm/package.json +11 -0
- package/dist/bin/gen-types.d.ts +2 -0
- package/dist/bin/gen-types.js +35 -0
- package/dist/compiler/BytecodeCompiler.d.ts +83 -0
- package/dist/compiler/BytecodeCompiler.js +379 -0
- package/dist/config/ConfigManager.d.ts +13 -0
- package/dist/config/ConfigManager.js +27 -0
- package/dist/config/ProgramIdResolver.d.ts +62 -0
- package/dist/config/ProgramIdResolver.js +104 -0
- package/dist/crypto/index.d.ts +211 -0
- package/dist/crypto/index.js +451 -0
- package/dist/encoding/ParameterEncoder.d.ts +31 -0
- package/dist/encoding/ParameterEncoder.js +278 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +28 -0
- package/dist/lib/bytecode-encoder.d.ts +62 -0
- package/dist/lib/bytecode-encoder.js +281 -0
- package/dist/logging/index.d.ts +9 -0
- package/dist/logging/index.js +10 -0
- package/dist/metadata/index.d.ts +213 -0
- package/dist/metadata/index.js +296 -0
- package/dist/modules/accounts.d.ts +60 -0
- package/dist/modules/accounts.js +275 -0
- package/dist/modules/deploy.d.ts +90 -0
- package/dist/modules/deploy.js +1118 -0
- package/dist/modules/execute.d.ts +90 -0
- package/dist/modules/execute.js +649 -0
- package/dist/modules/fees.d.ts +14 -0
- package/dist/modules/fees.js +112 -0
- package/dist/modules/namespaces.d.ts +39 -0
- package/dist/modules/namespaces.js +190 -0
- package/dist/modules/state-diff.d.ts +35 -0
- package/dist/modules/state-diff.js +342 -0
- package/dist/modules/vm-state.d.ts +7 -0
- package/dist/modules/vm-state.js +44 -0
- package/dist/program/AccountResolver.d.ts +67 -0
- package/dist/program/AccountResolver.js +134 -0
- package/dist/program/BorshSchemaGenerator.d.ts +8 -0
- package/dist/program/BorshSchemaGenerator.js +57 -0
- package/dist/program/FiveProgram.d.ts +144 -0
- package/dist/program/FiveProgram.js +282 -0
- package/dist/program/FunctionBuilder.d.ts +114 -0
- package/dist/program/FunctionBuilder.js +347 -0
- package/dist/program/ProgramAccount.d.ts +38 -0
- package/dist/program/ProgramAccount.js +170 -0
- package/dist/program/TypeGenerator.d.ts +90 -0
- package/dist/program/TypeGenerator.js +195 -0
- package/dist/program/index.d.ts +24 -0
- package/dist/program/index.js +21 -0
- package/dist/project/config.d.ts +5 -0
- package/dist/project/config.js +33 -0
- package/dist/project/toml.d.ts +6 -0
- package/dist/project/toml.js +43 -0
- package/dist/project/workspace.d.ts +160 -0
- package/dist/project/workspace.js +73 -0
- package/dist/testing/AccountMetaGenerator.d.ts +121 -0
- package/dist/testing/AccountMetaGenerator.js +261 -0
- package/dist/testing/AccountTestFixture.d.ts +211 -0
- package/dist/testing/AccountTestFixture.js +530 -0
- package/dist/testing/OnChainAccountManager.d.ts +81 -0
- package/dist/testing/OnChainAccountManager.js +260 -0
- package/dist/testing/StateSerializer.d.ts +65 -0
- package/dist/testing/StateSerializer.js +330 -0
- package/dist/testing/TestDiscovery.d.ts +79 -0
- package/dist/testing/TestDiscovery.js +274 -0
- package/dist/testing/TestRunner.d.ts +117 -0
- package/dist/testing/TestRunner.js +346 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +13 -0
- package/dist/types.d.ts +356 -0
- package/dist/types.js +32 -0
- package/dist/utils/abi.d.ts +31 -0
- package/dist/utils/abi.js +92 -0
- package/dist/utils/transaction.d.ts +5 -0
- package/dist/utils/transaction.js +48 -0
- package/dist/validation/InputValidator.d.ts +142 -0
- package/dist/validation/InputValidator.js +332 -0
- package/dist/validation/index.d.ts +4 -0
- package/dist/validation/index.js +4 -0
- package/dist/wasm/compiler/AbiLogic.d.ts +4 -0
- package/dist/wasm/compiler/AbiLogic.js +37 -0
- package/dist/wasm/compiler/AnalysisLogic.d.ts +6 -0
- package/dist/wasm/compiler/AnalysisLogic.js +61 -0
- package/dist/wasm/compiler/CompilationLogic.d.ts +10 -0
- package/dist/wasm/compiler/CompilationLogic.js +431 -0
- package/dist/wasm/compiler/FiveCompiler.d.ts +48 -0
- package/dist/wasm/compiler/FiveCompiler.js +183 -0
- package/dist/wasm/compiler/InfoLogic.d.ts +6 -0
- package/dist/wasm/compiler/InfoLogic.js +24 -0
- package/dist/wasm/compiler/OptimizationLogic.d.ts +2 -0
- package/dist/wasm/compiler/OptimizationLogic.js +13 -0
- package/dist/wasm/compiler/ValidationLogic.d.ts +7 -0
- package/dist/wasm/compiler/ValidationLogic.js +26 -0
- package/dist/wasm/compiler/index.d.ts +2 -0
- package/dist/wasm/compiler/index.js +2 -0
- package/dist/wasm/compiler/types.d.ts +8 -0
- package/dist/wasm/compiler/types.js +1 -0
- package/dist/wasm/compiler/utils.d.ts +8 -0
- package/dist/wasm/compiler/utils.js +75 -0
- package/dist/wasm/index.d.ts +9 -0
- package/dist/wasm/index.js +12 -0
- package/dist/wasm/instance.d.ts +1 -0
- package/dist/wasm/instance.js +26 -0
- package/dist/wasm/loader.d.ts +7 -0
- package/dist/wasm/loader.js +112 -0
- package/dist/wasm/vm.d.ts +33 -0
- package/dist/wasm/vm.js +250 -0
- package/package.json +59 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Five SDK Account System
|
|
3
|
+
*
|
|
4
|
+
* Client-agnostic account management system with validation, PDA derivation,
|
|
5
|
+
* and account size calculations. Uses serialization instead of direct blockchain calls.
|
|
6
|
+
*/
|
|
7
|
+
import { PDAUtils, SolanaPublicKeyUtils, RentCalculator, AccountValidator } from '../crypto/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* AccountType enum for test compatibility
|
|
10
|
+
*/
|
|
11
|
+
export const AccountType = {
|
|
12
|
+
SCRIPT: 'script',
|
|
13
|
+
METADATA: 'metadata',
|
|
14
|
+
USER_STATE: 'user_state',
|
|
15
|
+
SYSTEM: 'system',
|
|
16
|
+
RENT_SYSVAR: 'rent_sysvar',
|
|
17
|
+
CLOCK_SYSVAR: 'clock_sysvar',
|
|
18
|
+
SPL_TOKEN: 'spl_token',
|
|
19
|
+
CUSTOM: 'custom'
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Account manager for Five VM scripts (serialization-based)
|
|
23
|
+
*/
|
|
24
|
+
export class FiveAccountManager {
|
|
25
|
+
constructor(programId = 'FiveProgramID11111111111111111111111111111') {
|
|
26
|
+
this.programId = programId;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Encode System Program CreateAccount instruction
|
|
30
|
+
*/
|
|
31
|
+
encodeCreateAccountInstruction(params) {
|
|
32
|
+
// Encoding for CreateAccount instruction
|
|
33
|
+
// In a real implementation, this would use proper Solana instruction encoding
|
|
34
|
+
const buffer = new ArrayBuffer(32);
|
|
35
|
+
const view = new DataView(buffer);
|
|
36
|
+
// Instruction discriminator for CreateAccount (0)
|
|
37
|
+
view.setUint32(0, 0, true);
|
|
38
|
+
// Account size
|
|
39
|
+
view.setUint32(4, params.size, true);
|
|
40
|
+
// Rent lamports (calculated)
|
|
41
|
+
const rentLamports = params.rentExempt ? RentCalculator.calculateRentExemption(params.size) : 0;
|
|
42
|
+
view.setBigUint64(8, BigInt(rentLamports), true);
|
|
43
|
+
// Owner program ID would be encoded here in real implementation
|
|
44
|
+
// Return the basic instruction data
|
|
45
|
+
return new Uint8Array(buffer);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create script account PDA and return serialized instruction
|
|
49
|
+
*/
|
|
50
|
+
async createScriptAccount(bytecode, payerAddress) {
|
|
51
|
+
const pda = await PDAUtils.deriveScriptAccount(bytecode, this.programId);
|
|
52
|
+
const rentLamports = RentCalculator.getScriptAccountRent(bytecode.length);
|
|
53
|
+
// Create serialized instruction for System Program CreateAccount
|
|
54
|
+
const createInstruction = {
|
|
55
|
+
programId: '11111111111111111111111111111112', // System Program
|
|
56
|
+
accounts: [
|
|
57
|
+
{ pubkey: payerAddress, isSigner: true, isWritable: true },
|
|
58
|
+
{ pubkey: pda.address, isSigner: false, isWritable: true }
|
|
59
|
+
],
|
|
60
|
+
data: this.encodeCreateAccountInstruction({
|
|
61
|
+
size: bytecode.length + 256, // Bytecode + metadata
|
|
62
|
+
owner: this.programId,
|
|
63
|
+
rentExempt: true
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
address: pda.address,
|
|
68
|
+
bump: pda.bump,
|
|
69
|
+
createInstruction,
|
|
70
|
+
rentLamports
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create metadata account for script
|
|
75
|
+
*/
|
|
76
|
+
async createMetadataAccount(scriptAccount, payerAddress) {
|
|
77
|
+
const pda = await PDAUtils.deriveMetadataAccount(scriptAccount, this.programId);
|
|
78
|
+
const rentLamports = RentCalculator.getMetadataAccountRent();
|
|
79
|
+
const createInstruction = {
|
|
80
|
+
programId: '11111111111111111111111111111112', // System Program
|
|
81
|
+
accounts: [
|
|
82
|
+
{ pubkey: payerAddress, isSigner: true, isWritable: true },
|
|
83
|
+
{ pubkey: pda.address, isSigner: false, isWritable: true }
|
|
84
|
+
],
|
|
85
|
+
data: this.encodeCreateAccountInstruction({
|
|
86
|
+
size: 1024, // 1KB for metadata
|
|
87
|
+
owner: this.programId,
|
|
88
|
+
rentExempt: true
|
|
89
|
+
})
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
address: pda.address,
|
|
93
|
+
bump: pda.bump,
|
|
94
|
+
createInstruction,
|
|
95
|
+
rentLamports
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create user state account for script interaction
|
|
100
|
+
*/
|
|
101
|
+
async createUserStateAccount(userPublicKey, scriptAccount) {
|
|
102
|
+
const pda = await PDAUtils.deriveUserStateAccount(userPublicKey, scriptAccount, this.programId);
|
|
103
|
+
const rentLamports = RentCalculator.getUserStateAccountRent();
|
|
104
|
+
return {
|
|
105
|
+
address: pda.address,
|
|
106
|
+
bump: pda.bump,
|
|
107
|
+
createInstruction: {
|
|
108
|
+
programId: '11111111111111111111111111111112', // System Program
|
|
109
|
+
accounts: [
|
|
110
|
+
{ pubkey: pda.address, isSigner: false, isWritable: true },
|
|
111
|
+
{ pubkey: userPublicKey, isSigner: true, isWritable: true },
|
|
112
|
+
{ pubkey: this.programId, isSigner: false, isWritable: false }
|
|
113
|
+
],
|
|
114
|
+
data: this.encodeCreateAccountInstruction({
|
|
115
|
+
size: 512, // 512 bytes for user state
|
|
116
|
+
owner: this.programId,
|
|
117
|
+
rentExempt: true
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
rentLamports
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Validate account constraints for script execution
|
|
125
|
+
*/
|
|
126
|
+
async validateAccountConstraints(accounts, constraints) {
|
|
127
|
+
const errors = [];
|
|
128
|
+
const warnings = [];
|
|
129
|
+
let totalRentCost = 0;
|
|
130
|
+
// Validate signers (if specified)
|
|
131
|
+
if (constraints.signers) {
|
|
132
|
+
const providedSigners = accounts.filter(acc => acc.isSigner).map(acc => acc.address);
|
|
133
|
+
for (const requiredSigner of constraints.signers) {
|
|
134
|
+
if (!providedSigners.includes(requiredSigner)) {
|
|
135
|
+
errors.push(`Missing required signer: ${requiredSigner}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Validate writable accounts (if specified)
|
|
140
|
+
if (constraints.writableAccounts) {
|
|
141
|
+
const providedWritable = accounts.filter(acc => acc.isWritable).map(acc => acc.address);
|
|
142
|
+
for (const requiredWritable of constraints.writableAccounts) {
|
|
143
|
+
if (!providedWritable.includes(requiredWritable)) {
|
|
144
|
+
errors.push(`Missing required writable account: ${requiredWritable}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Validate readonly accounts (if specified)
|
|
149
|
+
if (constraints.readonlyAccounts) {
|
|
150
|
+
const providedReadonly = accounts.filter(acc => !acc.isWritable).map(acc => acc.address);
|
|
151
|
+
for (const requiredReadonly of constraints.readonlyAccounts) {
|
|
152
|
+
if (!providedReadonly.includes(requiredReadonly)) {
|
|
153
|
+
errors.push(`Missing required readonly account: ${requiredReadonly}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Validate account types (if specified)
|
|
158
|
+
if (constraints.typeConstraints) {
|
|
159
|
+
for (const [address, expectedType] of constraints.typeConstraints) {
|
|
160
|
+
const account = accounts.find(acc => acc.address === address);
|
|
161
|
+
if (!account) {
|
|
162
|
+
errors.push(`Missing account for type constraint: ${address}`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (account.type !== expectedType) {
|
|
166
|
+
errors.push(`Account ${address} has type ${account.type}, expected ${expectedType}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Validate rent requirements (if specified)
|
|
171
|
+
if (constraints.rentRequirements) {
|
|
172
|
+
for (const [address, requiredRent] of constraints.rentRequirements) {
|
|
173
|
+
const account = accounts.find(acc => acc.address === address);
|
|
174
|
+
if (!account) {
|
|
175
|
+
continue; // Already handled above
|
|
176
|
+
}
|
|
177
|
+
if (account.lamports !== undefined && account.lamports < requiredRent) {
|
|
178
|
+
errors.push(`Account ${address} has ${account.lamports} lamports, needs ${requiredRent} for rent exemption`);
|
|
179
|
+
}
|
|
180
|
+
totalRentCost += requiredRent;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Validate maximum accounts constraint
|
|
184
|
+
if (constraints.maxAccounts !== undefined && accounts.length > constraints.maxAccounts) {
|
|
185
|
+
errors.push(`Too many accounts: ${accounts.length}, maximum allowed: ${constraints.maxAccounts}`);
|
|
186
|
+
}
|
|
187
|
+
// Validate maximum total size constraint
|
|
188
|
+
if (constraints.maxTotalSize !== undefined) {
|
|
189
|
+
const totalSize = accounts.reduce((sum, acc) => sum + (acc.size || 0), 0);
|
|
190
|
+
if (totalSize > constraints.maxTotalSize) {
|
|
191
|
+
errors.push(`Total account size ${totalSize} exceeds maximum: ${constraints.maxTotalSize}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Validate required types constraint
|
|
195
|
+
if (constraints.requiredTypes) {
|
|
196
|
+
const providedTypes = new Set(accounts.map(acc => acc.type));
|
|
197
|
+
for (const requiredType of constraints.requiredTypes) {
|
|
198
|
+
if (!providedTypes.has(requiredType)) {
|
|
199
|
+
errors.push(`Missing required account type: ${requiredType}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Calculate costs for all accounts
|
|
204
|
+
const accountSizes = accounts.map(account => ({
|
|
205
|
+
type: account.type,
|
|
206
|
+
size: account.size || 0
|
|
207
|
+
}));
|
|
208
|
+
const costs = await this.calculateAccountCreationCosts(accountSizes);
|
|
209
|
+
totalRentCost = costs.rentExemption;
|
|
210
|
+
// Validate maximum rent cost constraint
|
|
211
|
+
if (constraints.maxRentCost !== undefined && totalRentCost > constraints.maxRentCost) {
|
|
212
|
+
errors.push(`Total rent cost ${totalRentCost} exceeds maximum: ${constraints.maxRentCost}`);
|
|
213
|
+
}
|
|
214
|
+
// Validate account addresses
|
|
215
|
+
for (const account of accounts) {
|
|
216
|
+
const addressValidation = AccountValidator.validateAddress(account.address);
|
|
217
|
+
if (!addressValidation.valid) {
|
|
218
|
+
errors.push(`Invalid account address ${account.address}: ${addressValidation.errors.join(', ')}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const valid = errors.length === 0;
|
|
222
|
+
const result = {
|
|
223
|
+
valid,
|
|
224
|
+
errors,
|
|
225
|
+
warnings
|
|
226
|
+
};
|
|
227
|
+
if (valid) {
|
|
228
|
+
result.costs = {
|
|
229
|
+
rentExemption: costs.rentExemption,
|
|
230
|
+
transactionFee: costs.transactionFees,
|
|
231
|
+
totalCost: costs.total
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get account info using client-agnostic account fetcher interface
|
|
238
|
+
*/
|
|
239
|
+
async getAccountInfo(address, accountFetcher) {
|
|
240
|
+
if (!accountFetcher) {
|
|
241
|
+
throw new Error('Account fetcher required for blockchain operations. Use client-agnostic account fetcher interface.');
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const accountData = await accountFetcher.getAccountData(address);
|
|
245
|
+
if (!accountData) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
address,
|
|
250
|
+
type: this.determineAccountTypeFromData(accountData, address),
|
|
251
|
+
isSigner: false, // Cannot determine from account info alone
|
|
252
|
+
isWritable: false, // Cannot determine from account info alone
|
|
253
|
+
owner: accountData.owner,
|
|
254
|
+
size: accountData.data.length,
|
|
255
|
+
lamports: accountData.lamports,
|
|
256
|
+
data: accountData.data
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
console.warn(`Failed to get account info for ${address}:`, error);
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get multiple account infos in batch using client-agnostic interface
|
|
266
|
+
*/
|
|
267
|
+
async getMultipleAccountInfos(addresses, accountFetcher) {
|
|
268
|
+
if (!accountFetcher) {
|
|
269
|
+
throw new Error('Account fetcher required for blockchain operations. Use client-agnostic account fetcher interface.');
|
|
270
|
+
}
|
|
271
|
+
const results = new Map();
|
|
272
|
+
// Validate addresses first
|
|
273
|
+
const validAddresses = [];
|
|
274
|
+
for (const address of addresses) {
|
|
275
|
+
if (SolanaPublicKeyUtils.isValid(address)) {
|
|
276
|
+
validAddresses.push(address);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// Invalid address - set to null
|
|
280
|
+
results.set(address, null);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (validAddresses.length === 0) {
|
|
284
|
+
return results;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const accountsData = await accountFetcher.getMultipleAccountsData(validAddresses);
|
|
288
|
+
for (const address of validAddresses) {
|
|
289
|
+
const accountData = accountsData.get(address);
|
|
290
|
+
if (!accountData) {
|
|
291
|
+
results.set(address, null);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
results.set(address, {
|
|
295
|
+
address,
|
|
296
|
+
type: this.determineAccountTypeFromData(accountData, address),
|
|
297
|
+
isSigner: false,
|
|
298
|
+
isWritable: false,
|
|
299
|
+
owner: accountData.owner,
|
|
300
|
+
size: accountData.data.length,
|
|
301
|
+
lamports: accountData.lamports,
|
|
302
|
+
data: accountData.data
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
// Fallback to individual requests
|
|
308
|
+
for (const address of addresses) {
|
|
309
|
+
const accountInfo = await this.getAccountInfo(address, accountFetcher);
|
|
310
|
+
results.set(address, accountInfo);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return results;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Check if accounts exist and are properly initialized
|
|
317
|
+
*/
|
|
318
|
+
async validateAccountsExist(addresses) {
|
|
319
|
+
const existing = [];
|
|
320
|
+
const missing = [];
|
|
321
|
+
const invalid = [];
|
|
322
|
+
for (const address of addresses) {
|
|
323
|
+
if (!SolanaPublicKeyUtils.isValid(address)) {
|
|
324
|
+
invalid.push(address);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const accountInfo = await this.getAccountInfo(address);
|
|
328
|
+
if (accountInfo) {
|
|
329
|
+
existing.push(address);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
missing.push(address);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return { existing, missing, invalid };
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Calculate total costs for account creation
|
|
339
|
+
*/
|
|
340
|
+
async calculateAccountCreationCosts(accounts) {
|
|
341
|
+
let totalRent = 0;
|
|
342
|
+
const breakdown = [];
|
|
343
|
+
for (const account of accounts) {
|
|
344
|
+
const rent = RentCalculator.calculateRentExemption(account.size);
|
|
345
|
+
totalRent += rent;
|
|
346
|
+
breakdown.push({
|
|
347
|
+
type: account.type,
|
|
348
|
+
size: account.size,
|
|
349
|
+
rent
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
const transactionFees = 5000 * accounts.length; // Base fee per account creation
|
|
353
|
+
const total = totalRent + transactionFees;
|
|
354
|
+
return {
|
|
355
|
+
rentExemption: totalRent,
|
|
356
|
+
transactionFees,
|
|
357
|
+
total,
|
|
358
|
+
breakdown
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Build standard account list for script execution
|
|
363
|
+
*/
|
|
364
|
+
buildExecutionAccounts(scriptAccount, userAccount, additionalAccounts = []) {
|
|
365
|
+
const accounts = [
|
|
366
|
+
{
|
|
367
|
+
address: scriptAccount,
|
|
368
|
+
type: 'script',
|
|
369
|
+
isSigner: false,
|
|
370
|
+
isWritable: false
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
address: userAccount,
|
|
374
|
+
type: 'custom',
|
|
375
|
+
isSigner: true,
|
|
376
|
+
isWritable: true
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
address: this.programId,
|
|
380
|
+
type: 'custom',
|
|
381
|
+
isSigner: false,
|
|
382
|
+
isWritable: false
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
address: '11111111111111111111111111111112', // System Program
|
|
386
|
+
type: 'system',
|
|
387
|
+
isSigner: false,
|
|
388
|
+
isWritable: false
|
|
389
|
+
}
|
|
390
|
+
];
|
|
391
|
+
// Add additional accounts
|
|
392
|
+
for (const account of additionalAccounts) {
|
|
393
|
+
accounts.push({
|
|
394
|
+
address: account.address,
|
|
395
|
+
type: 'custom',
|
|
396
|
+
isSigner: account.isSigner,
|
|
397
|
+
isWritable: account.isWritable
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return accounts;
|
|
401
|
+
}
|
|
402
|
+
// Private helper methods
|
|
403
|
+
determineAccountTypeFromData(accountData, address) {
|
|
404
|
+
const owner = accountData.owner;
|
|
405
|
+
// System accounts
|
|
406
|
+
if (owner === '11111111111111111111111111111112') {
|
|
407
|
+
return 'system';
|
|
408
|
+
}
|
|
409
|
+
// Five VM accounts
|
|
410
|
+
if (owner === this.programId) {
|
|
411
|
+
if (accountData.data.length > 1000) {
|
|
412
|
+
return 'script'; // Likely contains bytecode
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
return 'metadata'; // Likely metadata or state
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// SPL Token accounts
|
|
419
|
+
if (owner === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') {
|
|
420
|
+
return 'spl_token';
|
|
421
|
+
}
|
|
422
|
+
// Sysvar accounts
|
|
423
|
+
if (address === 'SysvarRent111111111111111111111111111111111') {
|
|
424
|
+
return 'rent_sysvar';
|
|
425
|
+
}
|
|
426
|
+
if (address === 'SysvarC1ock11111111111111111111111111111111') {
|
|
427
|
+
return 'clock_sysvar';
|
|
428
|
+
}
|
|
429
|
+
return 'custom';
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Account utilities for client-agnostic operations
|
|
434
|
+
*/
|
|
435
|
+
export class AccountUtils {
|
|
436
|
+
/**
|
|
437
|
+
* Build serializable account list (client-agnostic)
|
|
438
|
+
*/
|
|
439
|
+
static buildSerializableAccounts(accounts) {
|
|
440
|
+
return accounts.map(account => ({
|
|
441
|
+
pubkey: account.address,
|
|
442
|
+
isSigner: account.isSigner,
|
|
443
|
+
isWritable: account.isWritable
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Deduplicate account list while preserving most permissive permissions
|
|
448
|
+
*/
|
|
449
|
+
static deduplicateAccounts(accounts) {
|
|
450
|
+
const accountMap = new Map();
|
|
451
|
+
for (const account of accounts) {
|
|
452
|
+
const existing = accountMap.get(account.address);
|
|
453
|
+
if (!existing) {
|
|
454
|
+
accountMap.set(account.address, { ...account });
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
// Keep most permissive permissions
|
|
458
|
+
existing.isSigner = existing.isSigner || account.isSigner;
|
|
459
|
+
existing.isWritable = existing.isWritable || account.isWritable;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return Array.from(accountMap.values());
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Sort accounts by standard Solana conventions
|
|
466
|
+
*/
|
|
467
|
+
static sortAccounts(accounts) {
|
|
468
|
+
return accounts.sort((a, b) => {
|
|
469
|
+
// Signers first
|
|
470
|
+
if (a.isSigner !== b.isSigner) {
|
|
471
|
+
return b.isSigner ? 1 : -1;
|
|
472
|
+
}
|
|
473
|
+
// Writable accounts next
|
|
474
|
+
if (a.isWritable !== b.isWritable) {
|
|
475
|
+
return b.isWritable ? 1 : -1;
|
|
476
|
+
}
|
|
477
|
+
// Alphabetical by address
|
|
478
|
+
return a.address.localeCompare(b.address);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Validate account list structure and compute statistics
|
|
483
|
+
*/
|
|
484
|
+
static validateAccountList(accounts) {
|
|
485
|
+
const errors = [];
|
|
486
|
+
const requiredAccounts = [];
|
|
487
|
+
const optionalAccounts = [];
|
|
488
|
+
let totalSize = 0;
|
|
489
|
+
if (!Array.isArray(accounts)) {
|
|
490
|
+
errors.push('Accounts must be an array');
|
|
491
|
+
return {
|
|
492
|
+
valid: false,
|
|
493
|
+
errors,
|
|
494
|
+
totalSize: 0,
|
|
495
|
+
requiredAccounts: [],
|
|
496
|
+
optionalAccounts: []
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
for (let i = 0; i < accounts.length; i++) {
|
|
500
|
+
const account = accounts[i];
|
|
501
|
+
const prefix = `Account ${i}`;
|
|
502
|
+
// Validate address
|
|
503
|
+
if (!account.address || !SolanaPublicKeyUtils.isValid(account.address)) {
|
|
504
|
+
errors.push(`${prefix}: Invalid address`);
|
|
505
|
+
}
|
|
506
|
+
// Validate size
|
|
507
|
+
if (account.size !== undefined) {
|
|
508
|
+
if (account.size < 0) {
|
|
509
|
+
errors.push(`${prefix}: size must be positive`);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
totalSize += account.size;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// Categorize by required/optional
|
|
516
|
+
if (account.required) {
|
|
517
|
+
requiredAccounts.push(account);
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
optionalAccounts.push(account);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
valid: errors.length === 0,
|
|
525
|
+
errors,
|
|
526
|
+
totalSize,
|
|
527
|
+
requiredAccounts,
|
|
528
|
+
optionalAccounts
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Filter accounts by type
|
|
533
|
+
*/
|
|
534
|
+
static filterAccountsByType(accounts, type) {
|
|
535
|
+
return accounts.filter(account => account.type === type);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Calculate total size of accounts
|
|
539
|
+
*/
|
|
540
|
+
static calculateTotalSize(accounts) {
|
|
541
|
+
return accounts.reduce((total, account) => total + (account.size || 0), 0);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
File without changes
|