Skip to content

Commit 875df5b

Browse files
feat: support for solana contracts and releases
1 parent e979b40 commit 875df5b

File tree

33 files changed

+899
-271
lines changed

33 files changed

+899
-271
lines changed

src/evm/contracts/queries.ts

Lines changed: 14 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,19 @@
11
import { Protocol } from "@src/evm/enums";
22
import { releasesQueries } from "@src/evm/releases/queries";
3+
import { createContractsQueries } from "@src/internal/factories/queries";
34
import type { Sablier } from "@src/types";
4-
import _ from "lodash";
55
import { catalog } from "./catalog";
66

7-
export const contractsQueries = {
8-
/**
9-
* Get a single contract using the following options:
10-
*
11-
* - { chainId, contractName, release }
12-
* - { chainId, contractAddress, protocol }
13-
* - { chainId, contractAddress, release }
14-
* - { chainId, contractAddress, protocol, release }
15-
*
16-
* Note: If a contract address exists in multiple releases for the same protocol, you must specify the release.
17-
*/
18-
get: (opts: {
19-
chainId: number;
20-
contractAddress?: string;
21-
contractName?: string;
22-
protocol?: Sablier.EVM.Protocol;
23-
release?: Sablier.EVM.Release;
24-
}): Sablier.EVM.Contract | undefined => {
25-
const { chainId, contractAddress, contractName, protocol, release } = opts;
26-
27-
// Validation
28-
if (contractAddress && contractName) {
29-
throw new Error("Sablier SDK: Cannot specify both contractAddress and contractName");
30-
}
31-
32-
// Query by name requires release
33-
if (contractName) {
34-
if (!release) {
35-
throw new Error("Sablier SDK: contractName requires release to be specified");
36-
}
37-
const dep = _.find(release.deployments, { chainId });
38-
return dep ? _.find(dep.contracts, { name: contractName }) : undefined;
39-
}
40-
41-
// Query by address
42-
if (contractAddress) {
43-
const address = contractAddress.toLowerCase();
44-
45-
// Scoped to specific release
46-
if (release) {
47-
const dep = _.find(release.deployments, { chainId });
48-
return dep ? _.find(dep.contracts, (c) => c.address.toLowerCase() === address) : undefined;
49-
}
50-
51-
// Scoped to protocol - check for duplicates across releases
52-
if (protocol) {
53-
const releases = releasesQueries.getAll({ protocol });
54-
const matches = releases.filter((rel) => {
55-
const dep = _.find(rel.deployments, { chainId });
56-
return dep && _.some(dep.contracts, (c) => c.address.toLowerCase() === address);
57-
});
58-
59-
if (matches.length > 1) {
60-
const versions = matches.map((r) => r.version).join(", ");
61-
throw new Error(
62-
`Sablier SDK: Contract ${contractAddress} exists in multiple releases (${versions}) for "${protocol}". ` +
63-
`Specify release: { chainId, contractAddress, release }`,
64-
);
65-
}
66-
67-
return _.get(catalog, [protocol, chainId, address]);
68-
}
69-
70-
// Fallback: search all protocols
71-
return (
72-
_.get(catalog, [Protocol.Airdrops, chainId, address]) ||
73-
_.get(catalog, [Protocol.Flow, chainId, address]) ||
74-
_.get(catalog, [Protocol.Legacy, chainId, address]) ||
75-
_.get(catalog, [Protocol.Lockup, chainId, address])
76-
);
77-
}
78-
79-
return undefined;
80-
},
81-
/**
82-
* Get many contracts.
83-
* - no options ⇒ all
84-
* - { chainId } ⇒ all for that chain
85-
* - { protocol } ⇒ all for that protocol
86-
* - { protocol, chainId } ⇒ all for that protocol and chain
87-
* - { release } ⇒ all deployments of that release
88-
* - { release, chainId } ⇒ all for that release and chain
89-
*/
90-
getAll: (opts?: {
91-
chainId?: number;
92-
protocol?: Sablier.EVM.Protocol;
93-
release?: Sablier.EVM.Release;
94-
}): Sablier.EVM.Contract[] | undefined => {
95-
const { protocol, chainId, release } = opts || {};
96-
97-
if (protocol && release) {
98-
throw new Error("Sablier SDK: Cannot specify both protocol and release as query options");
99-
}
100-
101-
// by protocol
102-
if (protocol) {
103-
const releases = releasesQueries.getAll({ protocol });
104-
let deps = _.flatMap(releases, (r) => r.deployments);
105-
if (chainId) {
106-
deps = _.filter(deps, (d) => d.chainId === chainId);
107-
if (deps.length === 0) return undefined;
108-
}
109-
return _.flatMap(deps, (d) => d.contracts);
110-
}
111-
112-
// by explicit release
113-
if (release) {
114-
let deps = release.deployments;
115-
if (chainId) {
116-
deps = _.filter(deps, (d) => d.chainId === chainId);
117-
if (deps.length === 0) return undefined;
118-
}
119-
return _.flatMap(deps, (d) => d.contracts);
120-
}
121-
122-
// by chain id
123-
if (chainId) {
124-
const deps = _.flatMap(releasesQueries.getAll(), (r) => r.deployments);
125-
const filtered = _.filter(deps, (d) => d.chainId === chainId);
126-
return _.flatMap(filtered, (d) => d.contracts);
127-
}
128-
129-
// no filters → all
130-
return _.flatMap(releasesQueries.getAll(), (r) => r.deployments.flatMap((d) => d.contracts));
131-
},
132-
};
7+
export const contractsQueries = createContractsQueries<
8+
Sablier.EVM.Protocol,
9+
Sablier.EVM.Contract,
10+
Sablier.EVM.Deployment,
11+
Sablier.EVM.Release,
12+
Sablier.EVM.ContractCatalog
13+
>({
14+
catalog,
15+
contractsField: "contracts",
16+
normalizeAddress: (address) => address.toLowerCase(),
17+
protocols: [Protocol.Airdrops, Protocol.Flow, Protocol.Legacy, Protocol.Lockup],
18+
releasesQueries,
19+
});

src/evm/releases/helpers.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
11
import { chainsQueries } from "@src/evm/chains/queries";
22
import _ from "lodash";
33

4-
export function getNestedValues<T extends Record<string, unknown>>(obj: T): string[] {
5-
return _.flatMap(obj, (value) => {
6-
if (_.isObject(value) && !_.isArray(value)) {
7-
return getNestedValues(value as Record<string, unknown>);
8-
}
9-
return _.isString(value) ? value : [];
10-
});
11-
}
12-
134
export function sortDeployments<T extends { chainId: number }>(deployments: T[]): T[] {
145
return deployments.sort((a, b) => {
156
const aChain = chainsQueries.getOrThrow(a.chainId);

src/evm/releases/queries.ts

Lines changed: 5 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,9 @@
11
import { Protocol } from "@src/evm/enums";
2+
import { createReleasesQueries } from "@src/internal/factories/queries";
23
import type { Sablier } from "@src/types";
3-
import _ from "lodash";
44
import { releases } from "./data";
55

6-
export const releasesQueries = {
7-
get: (opts: { protocol: Sablier.EVM.Protocol; version: Sablier.EVM.Version }): Sablier.EVM.Release | undefined => {
8-
const { protocol, version } = opts;
9-
return _.get(releases, [protocol, version]);
10-
},
11-
/**
12-
* Get all releases for a protocol.
13-
* - {} ⇒ all releases for all protocols
14-
* - {protocol} ⇒ all releases for that protocol
15-
*/
16-
getAll: (opts?: { protocol?: Sablier.EVM.Protocol }): Sablier.EVM.Release[] => {
17-
const { protocol } = opts || {};
18-
if (protocol) {
19-
return _.flatMap(_.values(releases[protocol]));
20-
}
21-
// Recursively get all releases from all protocols in the enum
22-
return _.flatMap(Object.values(Protocol), (protocolName) => _.flatMap(_.values(releases[protocolName])));
23-
},
24-
/**
25-
* Get the first release:
26-
* - {protocol} ⇒ first overall
27-
* - {protocol,chainId} ⇒ first on that chain
28-
*/
29-
getFirst: (opts: { protocol: Sablier.EVM.Protocol; chainId?: number }): Sablier.EVM.Release | undefined => {
30-
const { protocol, chainId } = opts;
31-
const list = releases[protocol];
32-
33-
if (chainId) {
34-
return _.find(list, (r) => _.some(r.deployments, { chainId }));
35-
}
36-
37-
return _.values(list)[0];
38-
},
39-
/**
40-
* Get the latest release for a protocol.
41-
* - {protocol}
42-
*/
43-
getLatest: (opts: { protocol: Sablier.EVM.Protocol }): Sablier.EVM.Release => {
44-
const list = _.values(releases[opts.protocol]);
45-
const latest = list[list.length - 1];
46-
if (!latest.isLatest) {
47-
throw new Error(`Sablier SDK: No latest release found for Sablier ${opts.protocol}. Please report on GitHub.`);
48-
}
49-
return latest;
50-
},
51-
};
6+
export const releasesQueries = createReleasesQueries<Sablier.EVM.Protocol, Sablier.EVM.Version, Sablier.EVM.Release>({
7+
ProtocolEnum: Protocol,
8+
releases: releases as Record<Sablier.EVM.Protocol, Record<string, Sablier.EVM.Release>>,
9+
});

src/evm/releases/resolvers.ts

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { chainsQueries } from "@src/evm/chains/queries";
2-
import { getNestedValues } from "@src/evm/releases/helpers";
3-
import { getContractExplorerURL } from "@src/helpers";
2+
import { getNestedValues } from "@src/helpers";
3+
import { createContractMapper, createStandardDeploymentResolver } from "@src/internal/factories/resolver";
4+
import type { AliasMap } from "@src/shared/types";
45
import type { Sablier } from "@src/types";
5-
import _ from "lodash";
66

77
/* -------------------------------------------------------------------------- */
88
/* TYPES */
@@ -12,7 +12,7 @@ type DeploymentBaseParams = {
1212
protocol: Sablier.EVM.Protocol;
1313
version: Sablier.EVM.Version;
1414
chainId: number;
15-
aliasMap: Sablier.EVM.AliasMap;
15+
aliasMap: AliasMap;
1616
};
1717

1818
type DeploymentLockupV1Params = DeploymentBaseParams & {
@@ -28,6 +28,49 @@ type DeploymentStandardParams = DeploymentBaseParams & {
2828

2929
type ReleaseParams<T> = Omit<T, "kind" | "contractNames">;
3030

31+
/* -------------------------------------------------------------------------- */
32+
/* PLATFORM SETUP */
33+
/* -------------------------------------------------------------------------- */
34+
35+
const contractMapper = createContractMapper<
36+
Sablier.EVM.Contract,
37+
Sablier.EVM.Protocol,
38+
Sablier.EVM.Version,
39+
Sablier.EVM.Address
40+
>(chainsQueries);
41+
42+
const standardDeploymentResolver = createStandardDeploymentResolver<
43+
Sablier.EVM.Deployment,
44+
Sablier.EVM.Contract,
45+
Sablier.EVM.Protocol,
46+
Sablier.EVM.Version,
47+
Sablier.EVM.Address
48+
>(contractMapper, "contracts");
49+
50+
/* -------------------------------------------------------------------------- */
51+
/* EVM-SPECIFIC RESOLVERS */
52+
/* -------------------------------------------------------------------------- */
53+
54+
/**
55+
* Creates a LockupV1 deployment with separate core and periphery contracts
56+
*/
57+
function createLockupV1Deployment(params: DeploymentLockupV1Params): Sablier.EVM.Deployment.LockupV1 {
58+
const { contractMap, ...baseParams } = params;
59+
60+
// Create standard deployment with merged contracts
61+
const mergedContracts = { ...contractMap.core, ...contractMap.periphery };
62+
const deployment = standardDeploymentResolver({
63+
...baseParams,
64+
contractMap: mergedContracts,
65+
}) as Sablier.EVM.Deployment.LockupV1;
66+
67+
// Add separated core and periphery contracts
68+
deployment.core = contractMapper(contractMap.core, baseParams);
69+
deployment.periphery = contractMapper(contractMap.periphery, baseParams);
70+
71+
return deployment;
72+
}
73+
3174
/* -------------------------------------------------------------------------- */
3275
/* RESOLVERS */
3376
/* -------------------------------------------------------------------------- */
@@ -38,33 +81,14 @@ export const resolvers = {
3881
* Creates a LockupV1 deployment with separate core and periphery contracts
3982
*/
4083
lockupV1: (params: DeploymentLockupV1Params): Sablier.EVM.Deployment.LockupV1 => {
41-
const { contractMap, ...baseParams } = params;
42-
43-
// Create standard deployment with merged contracts
44-
const mergedContracts = { ...contractMap.core, ...contractMap.periphery };
45-
const deployment = resolvers.deployment.standard({
46-
...baseParams,
47-
contractMap: mergedContracts,
48-
}) as Sablier.EVM.Deployment.LockupV1;
49-
50-
// Add separated core and periphery contracts
51-
deployment.core = mapContractsToDeployment(contractMap.core, baseParams);
52-
deployment.periphery = mapContractsToDeployment(contractMap.periphery, baseParams);
53-
54-
return deployment;
84+
return createLockupV1Deployment(params);
5585
},
5686

5787
/**
5888
* Creates a standard deployment with all contracts in a single array
5989
*/
6090
standard: (params: DeploymentStandardParams): Sablier.EVM.Deployment => {
61-
const { contractMap, ...baseParams } = params;
62-
const contracts = mapContractsToDeployment(contractMap, baseParams);
63-
64-
return {
65-
chainId: baseParams.chainId,
66-
contracts,
67-
};
91+
return standardDeploymentResolver(params);
6892
},
6993
},
7094

@@ -92,33 +116,3 @@ export const resolvers = {
92116
},
93117
},
94118
};
95-
96-
/* -------------------------------------------------------------------------- */
97-
/* HELPERS */
98-
/* -------------------------------------------------------------------------- */
99-
100-
/**
101-
* Converts a contract map to an array of deployment contracts
102-
*/
103-
function mapContractsToDeployment(
104-
contractMap: Sablier.EVM.ContractMap,
105-
params: Pick<DeploymentBaseParams, "chainId" | "protocol" | "version" | "aliasMap">,
106-
): Sablier.EVM.Contract[] {
107-
const { chainId, protocol, version, aliasMap } = params;
108-
const chain = chainsQueries.getOrThrow(chainId);
109-
110-
return _.entries(contractMap).map(([name, addressOrTuple]) => {
111-
const [address, blockNumber] = Array.isArray(addressOrTuple) ? addressOrTuple : [addressOrTuple];
112-
113-
return {
114-
address,
115-
alias: aliasMap[name],
116-
block: blockNumber,
117-
chainId,
118-
explorerURL: getContractExplorerURL(chain.blockExplorers.default.url, address),
119-
name,
120-
protocol,
121-
version,
122-
};
123-
});
124-
}

0 commit comments

Comments
 (0)