Skip to content

Commit

Permalink
Adding block and burn features to v2 swaps.
Browse files Browse the repository at this point in the history
  • Loading branch information
blockiosaurus committed Nov 20, 2024
1 parent 49b435a commit fb53909
Show file tree
Hide file tree
Showing 16 changed files with 693 additions and 63 deletions.
26 changes: 26 additions & 0 deletions clients/js/src/generated/errors/mplHybrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,32 @@ export class InvalidAuthorityError extends ProgramError {
codeToErrorMap.set(0x1780, InvalidAuthorityError);
nameToErrorMap.set('InvalidAuthority', InvalidAuthorityError);

/** CaptureBlocked: Capture is blocked for this recipe */
export class CaptureBlockedError extends ProgramError {
override readonly name: string = 'CaptureBlocked';

readonly code: number = 0x1781; // 6017

constructor(program: Program, cause?: Error) {
super('Capture is blocked for this recipe', program, cause);
}
}
codeToErrorMap.set(0x1781, CaptureBlockedError);
nameToErrorMap.set('CaptureBlocked', CaptureBlockedError);

/** ReleaseBlocked: Release is blocked for this recipe */
export class ReleaseBlockedError extends ProgramError {
override readonly name: string = 'ReleaseBlocked';

readonly code: number = 0x1782; // 6018

constructor(program: Program, cause?: Error) {
super('Release is blocked for this recipe', program, cause);
}
}
codeToErrorMap.set(0x1782, ReleaseBlockedError);
nameToErrorMap.set('ReleaseBlocked', ReleaseBlockedError);

/**
* Attempts to resolve a custom program error from the provided error code.
* @category Errors
Expand Down
2 changes: 1 addition & 1 deletion clients/js/src/generated/instructions/captureV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function captureV2(
},
token: {
index: 8,
isWritable: false as boolean,
isWritable: true as boolean,
value: input.token ?? null,
},
feeTokenAccount: {
Expand Down
4 changes: 4 additions & 0 deletions clients/js/src/generated/types/internalPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers';

export enum InternalPath {
NoRerollMetadata,
BlockCapture,
BlockRelease,
BurnOnCapture,
BurnOnRelease,
}

export type InternalPathArgs = InternalPath;
Expand Down
247 changes: 247 additions & 0 deletions clients/js/test/v2/captureV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -587,3 +587,250 @@ test('it can swap tokens for an asset as UpdateDelegate without reroll', async (
// Make sure the URI has not changed.
t.is(assetAfter.uri, 'https://example.com/asset');
});

test('it cannot swap tokens for an asset with BlockCapture', async (t) => {
// Given a Umi instance using the project's plugin.
const umi = await createUmi();
const feeLocation = generateSigner(umi);
const { assets, collection } = await createCoreCollection(umi);
const tokenMint = generateSigner(umi);
await createFungible(umi, {
name: 'Test Token',
uri: 'www.fungible.com',
sellerFeeBasisPoints: {
basisPoints: 0n,
identifier: '%',
decimals: 2,
},
mint: tokenMint,
}).sendAndConfirm(umi);

await mintV1(umi, {
mint: tokenMint.publicKey,
tokenStandard: TokenStandard.Fungible,
tokenOwner: umi.identity.publicKey,
amount: 1000,
}).sendAndConfirm(umi);

await initEscrowV2(umi, {}).sendAndConfirm(umi);

const escrow = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [
string({ size: 'variable' }).serialize('escrow'),
publicKeySerializer().serialize(umi.identity.publicKey),
]);

t.like(await fetchEscrowV2(umi, escrow), <EscrowV2>{
authority: umi.identity.publicKey,
bump: escrow[1],
});

// Transfer the assets to the escrow.
// eslint-disable-next-line no-restricted-syntax
for (const asset of assets) {
// eslint-disable-next-line no-await-in-loop
await transfer(umi, {
asset,
collection,
newOwner: escrow,
}).sendAndConfirm(umi);
}

const recipe = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [
string({ size: 'variable' }).serialize('recipe'),
publicKeySerializer().serialize(collection.publicKey),
]);

await initRecipeV1(umi, {
collection: collection.publicKey,
token: tokenMint.publicKey,
feeLocation: feeLocation.publicKey,
name: 'Test Escrow',
uri: 'www.test.com/',
max: 9,
min: 0,
amount: 5,
feeAmountCapture: 1,
feeAmountRelease: 1,
solFeeAmountCapture: 890_880n,
solFeeAmountRelease: 100_000n,
path: buildPath([Path.NoRerollMetadata, Path.BlockCapture]),
}).sendAndConfirm(umi);

await addCollectionPlugin(umi, {
collection: collection.publicKey,
plugin: {
type: 'UpdateDelegate',
additionalDelegates: [],
authority: { type: 'Address', address: publicKey(recipe) },
},
}).sendAndConfirm(umi);

const recipeData = await fetchRecipeV1(umi, recipe);
t.like(recipeData, {
publicKey: publicKey(recipe),
collection: collection.publicKey,
authority: umi.identity.publicKey,
token: tokenMint.publicKey,
feeLocation: feeLocation.publicKey,
name: 'Test Escrow',
uri: 'www.test.com/',
max: 9n,
min: 0n,
amount: 5n,
feeAmountCapture: 1n,
feeAmountRelease: 1n,
solFeeAmountCapture: 890_880n,
solFeeAmountRelease: 100_000n,
count: 1n,
path: buildPath([Path.NoRerollMetadata, Path.BlockCapture]),
bump: recipe[1],
});

const result = captureV2(umi, {
owner: umi.identity,
authority: recipe,
recipe,
escrow,
asset: assets[0].publicKey,
collection: collection.publicKey,
feeProjectAccount: feeLocation.publicKey,
token: tokenMint.publicKey,
}).sendAndConfirm(umi);

await t.throwsAsync(result, { name: "CaptureBlocked" })
});

test('it can burn tokens for an asset with BurnOnCapture', async (t) => {
// Given a Umi instance using the project's plugin.
const umi = await createUmi();
const feeLocation = generateSigner(umi);
const { assets, collection } = await createCoreCollection(umi);
const tokenMint = generateSigner(umi);
await createFungible(umi, {
name: 'Test Token',
uri: 'www.fungible.com',
sellerFeeBasisPoints: {
basisPoints: 0n,
identifier: '%',
decimals: 2,
},
mint: tokenMint,
}).sendAndConfirm(umi);

await mintV1(umi, {
mint: tokenMint.publicKey,
tokenStandard: TokenStandard.Fungible,
tokenOwner: umi.identity.publicKey,
amount: 1000,
}).sendAndConfirm(umi);

await initEscrowV2(umi, {}).sendAndConfirm(umi);

const escrow = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [
string({ size: 'variable' }).serialize('escrow'),
publicKeySerializer().serialize(umi.identity.publicKey),
]);

t.like(await fetchEscrowV2(umi, escrow), <EscrowV2>{
authority: umi.identity.publicKey,
bump: escrow[1],
});

// Transfer the assets to the escrow.
// eslint-disable-next-line no-restricted-syntax
for (const asset of assets) {
// eslint-disable-next-line no-await-in-loop
await transfer(umi, {
asset,
collection,
newOwner: escrow,
}).sendAndConfirm(umi);
}

const recipe = umi.eddsa.findPda(MPL_HYBRID_PROGRAM_ID, [
string({ size: 'variable' }).serialize('recipe'),
publicKeySerializer().serialize(collection.publicKey),
]);

await initRecipeV1(umi, {
collection: collection.publicKey,
token: tokenMint.publicKey,
feeLocation: feeLocation.publicKey,
name: 'Test Escrow',
uri: 'www.test.com/',
max: 9,
min: 0,
amount: 5,
feeAmountCapture: 1,
feeAmountRelease: 1,
solFeeAmountCapture: 890_880n,
solFeeAmountRelease: 100_000n,
path: buildPath([Path.NoRerollMetadata, Path.BurnOnCapture]),
}).sendAndConfirm(umi);

await addCollectionPlugin(umi, {
collection: collection.publicKey,
plugin: {
type: 'UpdateDelegate',
additionalDelegates: [],
authority: { type: 'Address', address: publicKey(recipe) },
},
}).sendAndConfirm(umi);

const recipeData = await fetchRecipeV1(umi, recipe);
t.like(recipeData, {
publicKey: publicKey(recipe),
collection: collection.publicKey,
authority: umi.identity.publicKey,
token: tokenMint.publicKey,
feeLocation: feeLocation.publicKey,
name: 'Test Escrow',
uri: 'www.test.com/',
max: 9n,
min: 0n,
amount: 5n,
feeAmountCapture: 1n,
feeAmountRelease: 1n,
solFeeAmountCapture: 890_880n,
solFeeAmountRelease: 100_000n,
count: 1n,
path: buildPath([Path.NoRerollMetadata, Path.BurnOnCapture]),
bump: recipe[1],
});

await captureV2(umi, {
owner: umi.identity,
authority: recipe,
recipe,
escrow,
asset: assets[0].publicKey,
collection: collection.publicKey,
feeProjectAccount: feeLocation.publicKey,
token: tokenMint.publicKey,
}).sendAndConfirm(umi, { send: { skipPreflight: true } });

const escrowTokenAfter = await fetchDigitalAssetWithAssociatedToken(
umi,
tokenMint.publicKey,
publicKey(escrow)
);
// The tokens are burned so the amount should still be 0.
t.deepEqual(escrowTokenAfter.token.amount, 0n);
const userTokenAfter = await fetchDigitalAssetWithAssociatedToken(
umi,
tokenMint.publicKey,
umi.identity.publicKey
);
t.deepEqual(userTokenAfter.token.amount, 994n);
const feeTokenAfter = await fetchDigitalAssetWithAssociatedToken(
umi,
tokenMint.publicKey,
feeLocation.publicKey
);
t.deepEqual(feeTokenAfter.token.amount, 1n);
const assetAfter = await fetchAsset(umi, assets[0].publicKey);
t.is(assetAfter.owner, umi.identity.publicKey);

// Make sure the URI has not changed.
t.is(assetAfter.uri, 'https://example.com/asset');
});
Loading

0 comments on commit fb53909

Please sign in to comment.