Skip to content

Commit

Permalink
feat(#629): Add configurable destination for lazy distributor (#630)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChewingGlass authored Apr 12, 2024
1 parent c1704f4 commit c64972f
Show file tree
Hide file tree
Showing 23 changed files with 688 additions and 123 deletions.
185 changes: 123 additions & 62 deletions packages/distributor-oracle/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ import {
populateMissingDraftInfo,
toVersionedTx,
truthy,
withPriorityFees
withPriorityFees,
} from "@helium/spl-utils";
import { getAssociatedTokenAddress } from "@solana/spl-token";
import {
getAssociatedTokenAddress,
getAssociatedTokenAddressSync,
} from "@solana/spl-token";
import {
AddressLookupTableAccount,
PublicKey,
Expand Down Expand Up @@ -317,44 +320,67 @@ export async function formBulkTransactions({
false
);
// construct the set and distribute ixs
const setAndDistributeIxs = (
await Promise.all(
compressionAssetAccs.map(async (assetAcc, idx) => {
const keyToAssetK = keyToAssets[idx]?.pubkey;
const keyToAsset = keyToAssets[idx]?.info;
if (!keyToAsset || !keyToAssetK) {
return [];
}
const inits = ixsPerAsset[idx];
const entityKey = decodeEntityKey(
keyToAsset.entityKey,
keyToAsset.keySerialization
)!;
const setRewardIxs = (
await Promise.all(
rewards.map(async (bulkRewards, oracleIdx) => {
if (!(entityKey in bulkRewards.currentRewards)) {
return null;
}
return await rewardsOracleProgram!.methods
.setCurrentRewardsWrapperV1({
currentRewards: new BN(bulkRewards.currentRewards[entityKey]),
oracleIndex: oracleIdx,
})
.accounts({
lazyDistributor,
recipient: recipientKeys[idx],
keyToAsset: keyToAssetK,
oracle: bulkRewards.oracleKey,
})
.instruction();
})
)
).filter(truthy);
if (setRewardIxs.length == 0) {
return [];
}
const distributeIx = await (
const setAndDistributeIxs = await Promise.all(
compressionAssetAccs.map(async (assetAcc, idx) => {
const keyToAssetK = keyToAssets[idx]?.pubkey;
const keyToAsset = keyToAssets[idx]?.info;
if (!keyToAsset || !keyToAssetK) {
return [];
}
const inits = ixsPerAsset[idx];
const entityKey = decodeEntityKey(
keyToAsset.entityKey,
keyToAsset.keySerialization
)!;
const setRewardIxs = (
await Promise.all(
rewards.map(async (bulkRewards, oracleIdx) => {
if (!(entityKey in bulkRewards.currentRewards)) {
return null;
}
return await rewardsOracleProgram!.methods
.setCurrentRewardsWrapperV1({
currentRewards: new BN(bulkRewards.currentRewards[entityKey]),
oracleIndex: oracleIdx,
})
.accounts({
lazyDistributor,
recipient: recipientKeys[idx],
keyToAsset: keyToAssetK,
oracle: bulkRewards.oracleKey,
})
.instruction();
})
)
).filter(truthy);
if (setRewardIxs.length == 0) {
return [];
}
let distributeIx;
if (
recipientAccs[idx] &&
!recipientAccs[idx]?.destination.equals(PublicKey.default)
) {
const destination = recipientAccs[idx]!.destination;
distributeIx = await lazyDistributorProgram.methods
.distributeCustomDestinationV0()
.accounts({
common: {
payer,
recipient: recipientKeys[idx],
lazyDistributor,
rewardsMint: lazyDistributorAcc.rewardsMint!,
owner: assetAcc.ownership.owner,
destinationAccount: getAssociatedTokenAddressSync(
lazyDistributorAcc.rewardsMint!,
destination,
true
),
},
})
.instruction();
} else {
distributeIx = await (
await distributeCompressionRewards({
program: lazyDistributorProgram,
assetId: assets![idx],
Expand All @@ -370,25 +396,28 @@ export async function formBulkTransactions({
assetEndpoint,
})
).instruction();
const ret = [...inits, ...setRewardIxs, distributeIx];
// filter arrays where init recipient is the only ix
if (ret.length > 1) {
return ret;
}
}
const ret = [...inits, ...setRewardIxs, distributeIx];
// filter arrays where init recipient is the only ix
if (ret.length > 1) {
return ret;
}

return [];
})
)
return [];
})
);

// unsigned txs
const initialTxDrafts =
await batchInstructionsToTxsWithPriorityFee(provider, setAndDistributeIxs, {
const initialTxDrafts = await batchInstructionsToTxsWithPriorityFee(
provider,
setAndDistributeIxs,
{
basePriorityFee,
addressLookupTableAddresses: [
isDevnet ? HELIUM_COMMON_LUT_DEVNET : HELIUM_COMMON_LUT,
],
});
}
);
const initialTxs = initialTxDrafts.map(toVersionedTx);

// @ts-ignore
Expand All @@ -409,7 +438,11 @@ export async function formBulkTransactions({
const finalTxs = serTxs.map((tx) => VersionedTransaction.deserialize(tx));
// Check instructions are the same
finalTxs.forEach((finalTx, idx) => {
assertSameTx(finalTx, initialTxs[idx], initialTxDrafts[0]?.addressLookupTables);
assertSameTx(
finalTx,
initialTxs[idx],
initialTxDrafts[0]?.addressLookupTables
);
});

return finalTxs;
Expand Down Expand Up @@ -484,9 +517,10 @@ export async function formTransaction({
true
);

const instructions: TransactionInstruction[] = []
const recipientExists = await provider.connection.getAccountInfo(recipient);
if (!recipientExists) {
const instructions: TransactionInstruction[] = [];
const recipientAcc =
await lazyDistributorProgram.account.recipientV0.fetchNullable(recipient);
if (!recipientAcc) {
let initRecipientIx;
if (assetAcc.compression.compressed) {
initRecipientIx = await (
Expand Down Expand Up @@ -532,12 +566,35 @@ export async function formTransaction({
instructions.push(
...(await withPriorityFees({
connection: provider.connection,
computeUnits: recipientExists ? RECIPIENT_EXISTS_CU : MISSING_RECIPIENT_CU,
computeUnits: recipientAcc ? RECIPIENT_EXISTS_CU : MISSING_RECIPIENT_CU,
instructions: ixs,
basePriorityFee,
}))
);

if (recipientAcc && !recipientAcc.destination.equals(PublicKey.default)) {
const destination = recipientAcc.destination;
instructions.push(
await lazyDistributorProgram.methods
.distributeCustomDestinationV0()
.accounts({
// @ts-ignore
common: {
payer,
recipient,
lazyDistributor,
rewardsMint,
owner: destination,
destinationAccount: getAssociatedTokenAddressSync(
rewardsMint,
destination,
true
),
},
})
.instruction()
);
}
if (assetAcc.compression.compressed) {
const distributeIx = await (
await distributeCompressionRewards({
Expand Down Expand Up @@ -599,22 +656,26 @@ function assertSameTx(
tx1: VersionedTransaction,
luts: AddressLookupTableAccount[] = []
) {
if (tx.message.compiledInstructions.length !== tx1.message.compiledInstructions.length) {
if (
tx.message.compiledInstructions.length !==
tx1.message.compiledInstructions.length
) {
throw new Error("Extra instructions added by oracle");
}

tx.message.compiledInstructions.forEach((instruction, idx) => {
const instruction1 = tx1.message.compiledInstructions[idx];
if (
instruction.programIdIndex !== instruction1.programIdIndex
) {
if (instruction.programIdIndex !== instruction1.programIdIndex) {
throw new Error("Program id mismatch");
}
if (!Buffer.from(instruction.data).equals(Buffer.from(instruction1.data))) {
throw new Error("Instruction data mismatch");
}

if (instruction.accountKeyIndexes.length !== instruction1.accountKeyIndexes.length) {
if (
instruction.accountKeyIndexes.length !==
instruction1.accountKeyIndexes.length
) {
throw new Error("Key length mismatch");
}

Expand All @@ -635,7 +696,7 @@ function assertSameTx(
addressLookupTableAccounts: luts,
})
.keySegments()
.reduce((acc, cur) => acc.concat(cur), []);;
.reduce((acc, cur) => acc.concat(cur), []);
if (keys1.length !== keys.length) {
throw new Error("Account keys do not match");
}
Expand Down
1 change: 1 addition & 0 deletions packages/distributor-oracle/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ export class OracleServer {
(decoded.name !== "setCurrentRewardsV0" &&
decoded.name !== "distributeRewardsV0" &&
decoded.name !== "distributeCompressionRewardsV0" &&
decoded.name !== "distributeCustomDestinationV0" &&
decoded.name !== "initializeRecipientV0" &&
decoded.name !== "initializeCompressionRecipientV0" &&
decoded.name !== "setCurrentRewardsWrapperV0" &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Idl, Program } from "@coral-xyz/anchor";
import { LazyDistributor } from "@helium/idls/lib/types/lazy_distributor";
import { Asset, AssetProof, proofArgsAndAccounts } from "@helium/spl-utils";
import { PublicKey } from "@solana/web3.js";
import { recipientKey } from "../pdas";

export async function updateCompressionDestination<IDL extends Idl>({
program,
assetId,
lazyDistributor,
rewardsMint,
payer,
destination,
...rest
}: {
program: Program<LazyDistributor>;
assetId: PublicKey;
rewardsMint?: PublicKey;
assetEndpoint?: string;
lazyDistributor: PublicKey;
owner?: PublicKey;
payer?: PublicKey;
destination: PublicKey | null;
getAssetFn?: (url: string, assetId: PublicKey) => Promise<Asset | undefined>;
getAssetProofFn?: (
url: string,
assetId: PublicKey
) => Promise<AssetProof | undefined>;
}) {
const {
asset: {
ownership: { owner },
},
args,
accounts,
remainingAccounts,
} = await proofArgsAndAccounts({
connection: program.provider.connection,
assetId,
...rest,
});

return program.methods
.updateCompressionDestinationV0({
...args,
})
.accounts({
...accounts,
owner,
recipient: recipientKey(lazyDistributor, assetId)[0],
destination: destination == null ? PublicKey.default : destination,
})
.remainingAccounts(remainingAccounts);
}
1 change: 1 addition & 0 deletions packages/lazy-distributor-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { PublicKey } from "@solana/web3.js";
import { PROGRAM_ID } from "./constants";
import { lazyDistributorResolvers } from "./resolvers";

export { updateCompressionDestination } from "./functions/updateCompressionDestination";
export { distributeCompressionRewards } from "./functions/distributeCompressionRewards";
export { initializeCompressionRecipient } from "./functions/initializeCompressionRecipient";

Expand Down
40 changes: 40 additions & 0 deletions packages/lazy-distributor-sdk/src/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export const lazyDistributorResolvers = combineResolvers(
mint: 'common.rewardsMint',
owner: 'common.owner',
}),
ataResolver({
instruction: 'distributeCustomDestinationV0',
account: 'common.destinationAccount',
mint: 'common.rewardsMint',
owner: 'common.owner',
}),
circuitBreakerResolvers,
resolveIndividual(async ({ path, accounts, idlIx }) => {
if (path[path.length - 1] === 'targetMetadata') {
Expand Down Expand Up @@ -101,6 +107,40 @@ export const lazyDistributorResolvers = combineResolvers(
resolved,
accounts,
};
},
async ({ accounts, provider, idlIx }) => {
let resolved = 0;
if (
idlIx.name === 'updateDestinationV0' &&
// @ts-ignore
(!accounts.recipientMintAccount ||
// @ts-ignore
!accounts.owner)
) {
// @ts-ignore
const recipient = accounts.recipient as PublicKey;
const recipientAcc = await provider.connection.getAccountInfo(recipient);
const recipientMint = new PublicKey(
recipientAcc!.data.subarray(8 + 32, 8 + 32 + 32)
);
const recipientMintAccount = (
await provider.connection.getTokenLargestAccounts(recipientMint)
).value[0].address;
const recipientMintTokenAccount = await getAccount(
provider.connection,
recipientMintAccount
);
// @ts-ignore
accounts.owner = recipientMintTokenAccount.owner;
// @ts-ignore
accounts.recipientMintAccount = recipientMintAccount;
resolved += 1;
}

return {
accounts,
resolved,
};
},
async ({ accounts, provider, idlIx }) => {
let resolved = 0;
Expand Down
Loading

0 comments on commit c64972f

Please sign in to comment.