Skip to content

Commit

Permalink
pg: fix sparkle key & close from playground/products page (#344)
Browse files Browse the repository at this point in the history
  • Loading branch information
yurushao authored Dec 28, 2024
1 parent 350f6ba commit b59ed81
Show file tree
Hide file tree
Showing 16 changed files with 341 additions and 302 deletions.
17 changes: 3 additions & 14 deletions anchor/src/client/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
FundMetadataAccount,
FundModel,
FundOpenfundsModel,
ShareClassModel,
} from "../models";
import { AssetMeta, ASSETS_MAINNET, ASSETS_TESTS } from "./assets";
import { GlamError } from "../error";
Expand Down Expand Up @@ -439,23 +440,11 @@ export class BaseClient {
}

getOpenfundsPDA(fundPDA: PublicKey): PublicKey {
const [pda, _bump] = PublicKey.findProgramAddressSync(
[anchor.utils.bytes.utf8.encode("openfunds"), fundPDA.toBuffer()],
this.programId,
);
return pda;
return FundModel.openfundsPda(fundPDA);
}

getShareClassPDA(fundPDA: PublicKey, shareId: number = 0): PublicKey {
const [pda, _bump] = PublicKey.findProgramAddressSync(
[
anchor.utils.bytes.utf8.encode("share"),
Uint8Array.from([shareId % 256]),
fundPDA.toBuffer(),
],
this.programId,
);
return pda;
return ShareClassModel.mintAddress(fundPDA, shareId);
}

getShareClassAta(user: PublicKey, shareClassPDA: PublicKey): PublicKey {
Expand Down
6 changes: 5 additions & 1 deletion anchor/src/client/fund.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ export class FundClient {
.rpc();
}

public async closeFund(fundPDA: PublicKey): Promise<TransactionSignature> {
public async closeFund(
fundPDA: PublicKey,
txOptions: TxOptions = {},
): Promise<TransactionSignature> {
const openfunds = this.base.getOpenfundsPDA(fundPDA);

return await this.base.program.methods
Expand All @@ -136,6 +139,7 @@ export class FundClient {
fund: fundPDA,
openfunds,
})
.preInstructions(txOptions.preInstructions || [])
.rpc();
}

Expand Down
15 changes: 15 additions & 0 deletions anchor/src/client/shareclass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ export class ShareClassClient {
});
}

public async closeShareClassIx(fundPDA: PublicKey, shareClassId: number = 0) {
const openfunds = this.base.getOpenfundsPDA(fundPDA);
const shareClassMint = this.base.getShareClassPDA(fundPDA, shareClassId);

// @ts-ignore Type instantiation is excessively deep and possibly infinite.
return await this.base.program.methods
.closeShareClass(shareClassId)
.accounts({
fund: fundPDA,
openfunds,
shareClassMint,
})
.instruction();
}

public async closeShareClass(
fundPDA: PublicKey,
shareClassId: number = 0,
Expand Down
102 changes: 82 additions & 20 deletions anchor/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const GlamIntegrations =

export const VaultIntegrations = GlamIntegrations.filter((i) => i !== "Mint");

const GlamProgramId = new PublicKey(GlamIDLJson.address);

// FIXME: Anchor is not able to handle enums with too many options
// The culprit of so many broken types suppressed by @ts-ignore is ShareClassFieldName, which
// has 100+ options.
Expand Down Expand Up @@ -63,13 +65,51 @@ export class FundIdlModel implements FundModelType {
this.rawOpenfunds = data.rawOpenfunds ?? null;
}
}

export class FundModel extends FundIdlModel {
idStr: string = "";

constructor(data: Partial<FundModelType>) {
constructor(data: Partial<FundIdlModel>) {
super(data);
this.idStr = this?.id ? this?.id.toBase58() : "";
}

get idStr() {
return this.id?.toBase58() || "";
}

get productType() {
if (this.shareClasses.length === 0) {
return "Vault";
}
if (
// @ts-ignore
this.integrationAcls.find((acl) => Object.keys(acl.name)[0] === "mint")
) {
return "Mint";
}
return "Fund";
}

get shareClassMints() {
if (this.shareClasses.length > 0 && !this.id) {
// If share classes are set, fund ID should be set as well
throw new Error("Fund ID not set");
}
return this.shareClasses.map((_, i) =>
ShareClassModel.mintAddress(this.id!, i),
);
}

get sparkleKey() {
if (this.shareClasses.length === 0) {
return this.idStr;
}
return this.shareClassMints[0].toBase58() || this.idStr;
}

static openfundsPda(fundPda: PublicKey) {
const [pda, _] = PublicKey.findProgramAddressSync(
[Buffer.from("openfunds"), fundPda.toBuffer()],
GlamProgramId,
);
return pda;
}

/**
Expand All @@ -85,20 +125,26 @@ export class FundModel extends FundIdlModel {
openfundsAccount?: FundMetadataAccount,
shareClassMint?: Mint,
) {
let fundModel: Partial<FundModelType> = {
let fundModel: Partial<FundIdlModel> = {
id: fundPDA,
name: fundAccount.name,
uri: fundAccount.uri,
manager: new ManagerModel({ pubkey: fundAccount.manager }),
openfundsUri: fundAccount.openfundsUri,
shareClasses: [],
};

// All fields in fund params[0] should be available on the FundModel
fundAccount.params[0].forEach((param) => {
const name = Object.keys(param.name)[0];
// @ts-ignore
const value = Object.values(param.value)[0].val;
fundModel[name] = value;
if (new FundIdlModel({}).hasOwnProperty(name)) {
// @ts-ignore
fundModel[name] = value;
} else {
console.warn(`Fund param ${name} not found in FundIdlModel`);
}
});

// Build fundModel.rawOpenfunds from openfunds account
Expand All @@ -112,21 +158,22 @@ export class FundModel extends FundIdlModel {
fundModel.rawOpenfunds = new FundOpenfundsModel(fundOpenfundsFields);

// Build the array of ShareClassModel
fundModel.shareClasses = [] as ShareClassModel[];
fundAccount.shareClasses.forEach((_, i) => {
const shareClassModel = {} as any;
const shareClassIdlModel = {} as any;
shareClassIdlModel["fundId"] = fundPDA;

fundAccount.params[i + 1].forEach((param) => {
const name = Object.keys(param.name)[0];
// @ts-ignore
const value = Object.values(param.value)[0].val;
if (name === "shareClassAllowlist") {
shareClassModel["allowlist"] = value as PublicKey[];
shareClassIdlModel["allowlist"] = value as PublicKey[];
} else if (name === "shareClassBlocklist") {
shareClassModel["blocklist"] = value as PublicKey[];
shareClassIdlModel["blocklist"] = value as PublicKey[];
} else if (name == "lockUp") {
shareClassModel["lockUpPeriodInSeconds"] = Number(value);
shareClassIdlModel["lockUpPeriodInSeconds"] = Number(value);
} else {
shareClassModel[name] = value;
shareClassIdlModel[name] = value;
}
});

Expand All @@ -138,7 +185,7 @@ export class FundModel extends FundIdlModel {
// @ts-ignore
shareClassOpenfundsFields[name] = value;
});
shareClassModel["rawOpenfunds"] = new ShareClassOpenfundsModel(
shareClassIdlModel["rawOpenfunds"] = new ShareClassOpenfundsModel(
shareClassOpenfundsFields,
);
}
Expand All @@ -151,21 +198,23 @@ export class FundModel extends FundIdlModel {
const tokenMetadata = extMetadata
? unpack(extMetadata)
: ({} as TokenMetadata);
shareClassModel["symbol"] = tokenMetadata?.symbol;
shareClassModel["name"] = tokenMetadata?.name;
shareClassModel["uri"] = tokenMetadata?.uri;
shareClassIdlModel["symbol"] = tokenMetadata?.symbol;
shareClassIdlModel["name"] = tokenMetadata?.name;
shareClassIdlModel["uri"] = tokenMetadata?.uri;

const extPermDelegate = getExtensionData(
ExtensionType.PermanentDelegate,
shareClassMint.tlvData,
);
if (extPermDelegate) {
const permanentDelegate = new PublicKey(extPermDelegate);
shareClassModel["permanentDelegate"] = permanentDelegate;
shareClassIdlModel["permanentDelegate"] = permanentDelegate;
}
}

fundModel.shareClasses.push(new ShareClassModel(shareClassModel));
// fundModel.shareClasses should never be null
// non-null assertion is safe and is needed to suppress type error
fundModel.shareClasses!.push(new ShareClassModel(shareClassIdlModel));
});

fundModel.name =
Expand Down Expand Up @@ -220,7 +269,7 @@ export class FundOpenfundsModel implements FundOpenfundsModelType {
}

export type ShareClassModelType = IdlTypes<Glam>["shareClassModel"];
export class ShareClassModel implements ShareClassModelType {
export class ShareClassIdlModel implements ShareClassModelType {
symbol: string | null;
name: string | null;
uri: string | null;
Expand Down Expand Up @@ -251,6 +300,19 @@ export class ShareClassModel implements ShareClassModelType {
this.defaultAccountStateFrozen = data.defaultAccountStateFrozen ?? false;
}
}
export class ShareClassModel extends ShareClassIdlModel {
constructor(data: Partial<ShareClassIdlModel>) {
super(data);
}

static mintAddress(fundPDA: PublicKey, idx: number = 0): PublicKey {
const [pda, _] = PublicKey.findProgramAddressSync(
[Buffer.from("share"), Uint8Array.from([idx % 256]), fundPDA.toBuffer()],
GlamProgramId,
);
return pda;
}
}

export type ShareClassOpenfundsModelType =
IdlTypes<Glam>["shareClassOpenfundsModel"];
Expand Down
7 changes: 4 additions & 3 deletions anchor/src/react/glam.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ interface Treasury {
interface FundCache {
address: string;
pubkey: PublicKey;
imageKey: string;
sparkleKey: string;
name: string;
}

Expand Down Expand Up @@ -100,8 +100,8 @@ const deserializeFundCache = (f: any) => {
const toFundCache = (f: FundModel) => {
return {
pubkey: f.id,
imageKey: f.id?.toBase58(),
address: f.id?.toBase58(),
sparkleKey: f.sparkleKey,
address: f.idStr,
name: f.name,
} as FundCache;
};
Expand Down Expand Up @@ -301,6 +301,7 @@ export function GlamProvider({
pubkey: wallet.publicKey,
...walletBalances,
} as UserWallet);
console.log("user wallet balances", walletBalances);
}
}, [walletBalances]);

Expand Down
2 changes: 1 addition & 1 deletion playground/src/app/(mint)/mint/create/createMintForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export default function MultiStepForm() {
setActiveFund({
address: fundPDA.toBase58(),
pubkey: fundPDA,
imageKey: fundPDA.toBase58(),
sparkleKey: fundPDA.toBase58(),
name: basicInfoFormData.name,
});
toast({
Expand Down
Loading

0 comments on commit b59ed81

Please sign in to comment.