Skip to content

Commit

Permalink
Add NftMetadataUpdateWebhook (#276)
Browse files Browse the repository at this point in the history
  • Loading branch information
thebrianchen authored Mar 6, 2023
1 parent 519478c commit 5a78ca1
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Major Changes

- Added the `NftMetadataUpdateWebhook` to be used with the `NotifyNamespace`. This webhook tracks all ERC721 and ERC1155 token metadata updates.

### Minor Changes

## 2.5.0
Expand Down
55 changes: 49 additions & 6 deletions src/api/notify-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
NftActivityWebhook,
NftFilter,
NftFiltersResponse,
NftMetadataUpdateWebhook,
NftMetadataWebhookUpdate,
NftWebhookParams,
NftWebhookUpdate,
TransactionWebhookParams,
Expand Down Expand Up @@ -178,6 +180,17 @@ export class NotifyNamespace {
*/
updateWebhook(nftWebhookId: string, update: NftWebhookUpdate): Promise<void>;

/**
* Update a {@link NftMetadataUpdateWebhook}'s active status or NFT filters.
*
* @param nftMetadataWebhookId The id of the NFT activity webhook.
* @param update Object containing the update.
*/
updateWebhook(
nftMetadataWebhookId: string,
update: NftMetadataWebhookUpdate
): Promise<void>;

/**
* Update a {@link AddressActivityWebhook}'s active status or addresses.
*
Expand All @@ -201,7 +214,7 @@ export class NotifyNamespace {
): Promise<void>;
async updateWebhook(
webhookOrId: NftActivityWebhook | AddressActivityWebhook | string,
update: NftWebhookUpdate | AddressWebhookUpdate
update: NftWebhookUpdate | AddressWebhookUpdate | NftMetadataWebhookUpdate
): Promise<void> {
const webhookId =
typeof webhookOrId === 'string' ? webhookOrId : webhookOrId.id;
Expand Down Expand Up @@ -230,6 +243,22 @@ export class NotifyNamespace {
? update.removeFilters.map(nftFilterToParam)
: []
};
} else if (
'addMetadataFilters' in update ||
'removeMetadataFilters' in update
) {
restApiName = 'update-webhook-nft-metadata-filters';
methodName = 'updateWebhookNftMetadataFilters';
method = 'PATCH';
data = {
webhook_id: webhookId,
nft_metadata_filters_to_add: update.addMetadataFilters
? update.addMetadataFilters.map(nftFilterToParam)
: [],
nft_metadata_filters_to_remove: update.removeMetadataFilters
? update.removeMetadataFilters.map(nftFilterToParam)
: []
};
} else if ('addAddresses' in update || 'removeAddresses' in update) {
restApiName = 'update-webhook-addresses';
methodName = 'webhook:updateWebhookAddresses';
Expand Down Expand Up @@ -310,6 +339,12 @@ export class NotifyNamespace {
params: NftWebhookParams
): Promise<NftActivityWebhook>;

createWebhook(
url: string,
type: WebhookType.NFT_METADATA_UPDATE,
params: NftWebhookParams
): Promise<NftMetadataUpdateWebhook>;

/**
* Create a new {@link AddressActivityWebhook} to track address activity.
*
Expand All @@ -332,6 +367,7 @@ export class NotifyNamespace {
| DroppedTransactionWebhook
| NftActivityWebhook
| AddressActivityWebhook
| NftMetadataUpdateWebhook
> {
let appId;
if (
Expand All @@ -345,9 +381,12 @@ export class NotifyNamespace {
}

let network = NETWORK_TO_WEBHOOK_NETWORK.get(this.config.network);
let filters;
let nftFilterObj;
let addresses;
if (type === WebhookType.NFT_ACTIVITY) {
if (
type === WebhookType.NFT_ACTIVITY ||
type === WebhookType.NFT_METADATA_UPDATE
) {
if (!('filters' in params) || params.filters.length === 0) {
throw new Error(
'Nft Activity Webhooks require a non-empty array input.'
Expand All @@ -356,7 +395,7 @@ export class NotifyNamespace {
network = params.network
? NETWORK_TO_WEBHOOK_NETWORK.get(params.network)
: network;
filters = (params.filters as NftFilter[]).map(filter =>
const filters = (params.filters as NftFilter[]).map(filter =>
filter.tokenId
? {
contract_address: filter.contractAddress,
Expand All @@ -366,6 +405,10 @@ export class NotifyNamespace {
contract_address: filter.contractAddress
}
);
nftFilterObj =
type === WebhookType.NFT_ACTIVITY
? { nft_filters: filters }
: { nft_metadata_filters: filters };
} else if (type === WebhookType.ADDRESS_ACTIVITY) {
if (
params === undefined ||
Expand All @@ -388,8 +431,8 @@ export class NotifyNamespace {
webhook_url: url,
...(appId && { app_id: appId }),

// Only include the filters/addresses in the final response if it's defined
...(filters && { nft_filters: filters }),
// Only include the filters/addresses in the final response if they're defined
...nftFilterObj,
...(addresses && { addresses })
};

Expand Down
36 changes: 32 additions & 4 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2123,7 +2123,8 @@ export enum WebhookType {
MINED_TRANSACTION = 'MINED_TRANSACTION',
DROPPED_TRANSACTION = 'DROPPED_TRANSACTION',
ADDRESS_ACTIVITY = 'ADDRESS_ACTIVITY',
NFT_ACTIVITY = 'NFT_ACTIVITY'
NFT_ACTIVITY = 'NFT_ACTIVITY',
NFT_METADATA_UPDATE = 'NFT_METADATA_UPDATE'
}

/**
Expand Down Expand Up @@ -2162,6 +2163,15 @@ export interface NftActivityWebhook extends Webhook {
type: WebhookType.NFT_ACTIVITY;
}

/**
* The NFT Metadata Update Webhook tracks all ERC721 and ERC1155 metadata updates.
* This can be used to notify your app with real time state changes when an NFT's
* metadata changes.
*/
export interface NftMetadataUpdateWebhook extends Webhook {
type: WebhookType.NFT_METADATA_UPDATE;
}

/** The response for a {@link NotifyNamespace.getAllWebhooks} method. */
export interface GetAllWebhooksResponse {
/** All webhooks attached to the provided auth token. */
Expand Down Expand Up @@ -2207,7 +2217,7 @@ export interface TransactionWebhookParams {

/**
* Params to pass in when calling {@link NotifyNamespace.createWebhook} in order
* to create a {@link NftActivityWebhook}.
* to create a {@link NftActivityWebhook} or {@link NftMetadataUpdateWebhook}.
*/
export interface NftWebhookParams {
/** Array of NFT filters the webhook should track. */
Expand All @@ -2233,7 +2243,7 @@ export interface AddressWebhookParams {
network?: Network;
}

/** NFT to track on a {@link NftActivityWebhook}. */
/** NFT to track on a {@link NftActivityWebhook} or {@link NftMetadataUpdateWebhook}. */
export interface NftFilter {
/** The contract address of the NFT. */
contractAddress: string;
Expand Down Expand Up @@ -2274,6 +2284,17 @@ export interface WebhookNftFilterUpdate {
removeFilters: NftFilter[];
}

/**
* Params object when calling {@link NotifyNamespace.updateWebhook} to add and
* remove NFT filters for a {@link NftMetadataUpdateWebhook}.
*/
export interface WebhookNftMetadataFilterUpdate {
/** The filters to additionally track. */
addMetadataFilters: NftFilter[];
/** Existing filters to remove. */
removeMetadataFilters: NftFilter[];
}

/**
* Params object when calling {@link NotifyNamespace.updateWebhook} to add and
* remove addresses for a {@link AddressActivityWebhook}.
Expand All @@ -2298,11 +2319,18 @@ export interface WebhookAddressOverride {
* Params object when calling {@link NotifyNamespace.updateWebhook} to update a
* {@link NftActivityWebhook}.
*/

export type NftWebhookUpdate =
| WebhookStatusUpdate
| RequireAtLeastOne<WebhookNftFilterUpdate>;

/**
* Params object when calling {@link NotifyNamespace.updateWebhook} to update a
* {@link NftMetadataUpdateWebhook}.
*/
export type NftMetadataWebhookUpdate =
| WebhookStatusUpdate
| RequireAtLeastOne<WebhookNftMetadataFilterUpdate>;

/**
* Params object when calling {@link NotifyNamespace.updateWebhook} to update a
* {@link AddressActivityWebhook}.
Expand Down
78 changes: 78 additions & 0 deletions test/integration/notify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Network,
NftActivityWebhook,
NftFilter,
NftMetadataUpdateWebhook,
WebhookType
} from '../../src';
import { loadAlchemyEnv } from '../test-util';
Expand Down Expand Up @@ -32,6 +33,7 @@ describe('E2E integration tests', () => {

let addressWh: AddressActivityWebhook;
let nftWh: NftActivityWebhook;
let nftMetadataWh: NftMetadataUpdateWebhook;

async function createInitialWebhooks(): Promise<void> {
addressWh = await alchemy.notify.createWebhook(
Expand All @@ -44,6 +46,11 @@ describe('E2E integration tests', () => {
WebhookType.NFT_ACTIVITY,
{ filters: nftFilters, network: Network.ETH_MAINNET }
);
nftMetadataWh = await alchemy.notify.createWebhook(
webhookUrl,
WebhookType.NFT_METADATA_UPDATE,
{ filters: nftFilters, network: Network.ETH_MAINNET }
);
}

beforeAll(async () => {
Expand Down Expand Up @@ -226,12 +233,35 @@ describe('E2E integration tests', () => {
).toEqual(0);
});

it('create and delete NftActivityWebhook', async () => {
const nftActivityWebhook = await alchemy.notify.createWebhook(
webhookUrl,
WebhookType.NFT_METADATA_UPDATE,
{ filters: nftFilters, network: Network.ETH_GOERLI }
);
expect(nftActivityWebhook.url).toEqual(webhookUrl);
expect(nftActivityWebhook.type).toEqual(WebhookType.NFT_METADATA_UPDATE);
expect(nftActivityWebhook.network).toEqual(Network.ETH_GOERLI);
let response = await alchemy.notify.getAllWebhooks();
expect(
response.webhooks.filter(wh => wh.id === nftActivityWebhook.id).length
).toEqual(1);

await alchemy.notify.deleteWebhook(nftActivityWebhook.id);
response = await alchemy.notify.getAllWebhooks();
expect(
response.webhooks.filter(wh => wh.id === nftActivityWebhook.id).length
).toEqual(0);
});

it('update NftActivityWebhook filter with same filter', async () => {
const addFilters = [
// Duplicate filter
{
contractAddress: '0x88b48f654c30e99bc2e4a1559b4dcf1ad93fa656',
tokenId: '234'
},
// New Filter
{
contractAddress: '0x88b48f654c30e99bc2e4a1559b4dcf1ad93fa656',
tokenId: '123'
Expand Down Expand Up @@ -272,6 +302,54 @@ describe('E2E integration tests', () => {
expect(updated[0].isActive).toEqual(false);
});

it('update NftMetadataUpdateWebhook status', async () => {
await alchemy.notify.updateWebhook(nftMetadataWh.id, {
isActive: false
});
const response = await alchemy.notify.getAllWebhooks();
const updated = response.webhooks.filter(wh => wh.id === nftMetadataWh.id);
expect(updated.length).toEqual(1);
expect(updated[0].isActive).toEqual(false);
});

it('update NftMetadataUpdateWebhook filter with same filter', async () => {
const addMetadataFilters = [
// Duplicate filter
{
contractAddress: '0x88b48f654c30e99bc2e4a1559b4dcf1ad93fa656',
tokenId: '234'
},
// New Filter
{
contractAddress: '0x88b48f654c30e99bc2e4a1559b4dcf1ad93fa656',
tokenId: '123'
}
];

const removeMetadataFilters = [
{
contractAddress: '0x17dc95f9052f86ed576af55b018360f853e19ac2',
tokenId: 345
}
];

await alchemy.notify.updateWebhook(nftWh, {
addFilters: addMetadataFilters,
removeFilters: removeMetadataFilters
});

const response = await alchemy.notify.getNftFilters(nftWh);
expect(response.filters.length).toEqual(2);

await alchemy.notify.updateWebhook(nftWh, {
removeFilters: removeMetadataFilters
});

await alchemy.notify.updateWebhook(nftWh, {
addFilters: addMetadataFilters
});
});

it('update AddressActivityWebhook address', async () => {
const addAddress = '0x7f268357A8c2552623316e2562D90e642bB538E5';
const removeAddress = '0xfdb16996831753d5331ff813c29a93c76834a0ad';
Expand Down

0 comments on commit 5a78ca1

Please sign in to comment.