Skip to content
This repository was archived by the owner on Nov 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { QueryInterface } from 'sequelize';
import getSchema from '../../utils/getSchema';

export async function up({ context: sequelize }: any): Promise<void> {
const schema = getSchema();
const qi: QueryInterface = sequelize.getQueryInterface();

await qi.sequelize.query(`
ALTER TABLE ${schema}.linked_identities
ALTER COLUMN owner_address DROP NOT NULL,
ALTER COLUMN owner_account_id DROP NOT NULL,
ALTER COLUMN is_linked SET DEFAULT false;
`);
}

export async function down({ context: sequelize }: any): Promise<void> {
const schema = getSchema();
const qi: QueryInterface = sequelize.getQueryInterface();

await qi.sequelize.query(`
ALTER TABLE ${schema}.linked_identities
ALTER COLUMN is_linked DROP DEFAULT,
ALTER COLUMN owner_address SET NOT NULL,
ALTER COLUMN owner_account_id SET NOT NULL;
`);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
nftDriverContract,
} from '../../../core/contractClients';
import { ProjectModel } from '../../../models';
import { ensureLinkedIdentityExists } from '../../../utils/linkedIdentityUtils';

type Params = {
ipfsHash: IpfsHash;
Expand Down Expand Up @@ -256,24 +257,31 @@ async function createNewSplitReceivers({
// 3. Persist receivers.
const receiverPromises = splitReceivers.map(async (receiver) => {
switch (receiver.type) {
case 'repoDriver':
case 'orcid':
assertIsRepoDriverId(receiver.accountId);
await ensureLinkedIdentityExists(
receiver.accountId,
{ blockNumber, logIndex },
transaction,
scopedLogger,
);

if (receiver.source.forge === 'orcid') {
return createSplitReceiver({
scopedLogger,
transaction,
splitReceiverShape: {
senderAccountId: emitterAccountId,
senderAccountType: 'drip_list',
receiverAccountId: receiver.accountId,
receiverAccountType: 'linked_identity',
relationshipType: 'drip_list_receiver',
weight: receiver.weight,
blockTimestamp,
},
});
}
return createSplitReceiver({
scopedLogger,
transaction,
splitReceiverShape: {
senderAccountId: emitterAccountId,
senderAccountType: 'drip_list',
receiverAccountId: receiver.accountId,
receiverAccountType: 'linked_identity',
relationshipType: 'drip_list_receiver',
weight: receiver.weight,
blockTimestamp,
},
});

case 'repoDriver':
assertIsRepoDriverId(receiver.accountId);

await ProjectModel.findOrCreate({
transaction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
} from '../../../utils/lastProcessedVersion';
import type { repoSubAccountDriverSplitReceiverSchema } from '../../../metadata/schemas/common/repoSubAccountDriverSplitReceiverSchema';
import type { gitHubSourceSchema } from '../../../metadata/schemas/common/sources';
import { ensureLinkedIdentityExists } from '../../../utils/linkedIdentityUtils';

type Params = {
ipfsHash: IpfsHash;
Expand Down Expand Up @@ -229,6 +230,13 @@ async function createNewSplitReceivers({
const repoDriverId = await calcParentRepoDriverId(receiver.accountId);

if (receiver.source.forge === 'orcid') {
await ensureLinkedIdentityExists(
repoDriverId,
{ blockNumber, logIndex },
transaction,
scopedLogger,
);

return createSplitReceiver({
scopedLogger,
transaction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import { makeVersion } from '../../../utils/lastProcessedVersion';
import RecoverableError from '../../../utils/recoverableError';
import type { gitHubSourceSchema } from '../../../metadata/schemas/common/sources';
import { ensureLinkedIdentityExists } from '../../../utils/linkedIdentityUtils';

type Params = {
logIndex: number;
Expand Down Expand Up @@ -251,6 +252,13 @@ async function createNewSplitReceivers({
}

if (dependency.source.forge === 'orcid') {
await ensureLinkedIdentityExists(
dependency.accountId,
{ blockNumber, logIndex },
transaction,
scopedLogger,
);

return createSplitReceiver({
scopedLogger,
transaction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
import { makeVersion } from '../../../utils/lastProcessedVersion';
import type { repoSubAccountDriverSplitReceiverSchema } from '../../../metadata/schemas/common/repoSubAccountDriverSplitReceiverSchema';
import type { gitHubSourceSchema } from '../../../metadata/schemas/common/sources';
import { ensureLinkedIdentityExists } from '../../../utils/linkedIdentityUtils';

type Params = {
logIndex: number;
Expand Down Expand Up @@ -205,6 +206,13 @@ async function createNewSplitReceivers({
const repoDriverId = await calcParentRepoDriverId(receiver.accountId);

if (receiver.source.forge === 'orcid') {
await ensureLinkedIdentityExists(
repoDriverId,
{ blockNumber, logIndex },
transaction,
scopedLogger,
);

return createSplitReceiver({
scopedLogger,
transaction,
Expand Down
18 changes: 18 additions & 0 deletions src/eventHandlers/SplitsSetEvent/processLinkedIdentitySplits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ export async function processLinkedIdentitySplits(
);
}

if (!linkedIdentity.ownerAccountId) {
scopedLogger.bufferMessage(
`ORCID account ${accountId} has no owner set yet. Skipping validation and splits creation.`,
);

linkedIdentity.isLinked = false;

scopedLogger.bufferUpdate({
type: LinkedIdentityModel,
id: linkedIdentity.accountId,
input: linkedIdentity,
});

await linkedIdentity.save({ transaction });

return;
}

const isLinked = await validateLinkedIdentity(
accountId,
linkedIdentity.ownerAccountId,
Expand Down
2 changes: 2 additions & 0 deletions src/metadata/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { repoDriverAccountMetadataSchemaV5 } from './repo-driver/v5';
import { nftDriverAccountMetadataSchemaV5 } from './nft-driver/v5';
import { subListMetadataSchemaV1 } from './immutable-splits-driver/v1';
import { nftDriverAccountMetadataSchemaV6 } from './nft-driver/v6';
import { nftDriverAccountMetadataSchemaV7 } from './nft-driver/v7';

export const nftDriverAccountMetadataParser = createVersionedParser([
nftDriverAccountMetadataSchemaV7.parse,
nftDriverAccountMetadataSchemaV6.parse,
nftDriverAccountMetadataSchemaV5.parse,
nftDriverAccountMetadataSchemaV4.parse,
Expand Down
2 changes: 1 addition & 1 deletion src/metadata/schemas/nft-driver/v6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const ecosystemVariant = base.extend({
avatar: emojiAvatarSchema,
});

const dripListVariant = base.extend({
export const dripListVariant = base.extend({
type: z.literal('dripList'),
recipients: z.array(
z.union([
Expand Down
20 changes: 20 additions & 0 deletions src/metadata/schemas/nft-driver/v7.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { z } from 'zod';
import {
dripListVariant as dripListVariantV6,
nftDriverAccountMetadataSchemaV6,
} from './v6';
import { orcidSplitReceiverSchema } from '../repo-driver/v6';

export const dripListVariantV7 = dripListVariantV6.extend({
recipients: z.array(
z.union([
...dripListVariantV6.shape.recipients._def.type.options,
orcidSplitReceiverSchema,
]),
),
});

export const nftDriverAccountMetadataSchemaV7 = z.discriminatedUnion('type', [
nftDriverAccountMetadataSchemaV6._def.options[0],
dripListVariantV7,
]);
12 changes: 12 additions & 0 deletions src/metadata/schemas/repo-driver/v6.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import z from 'zod';

export const orcidSplitReceiverSchema = z.object({
type: z.literal('orcid'),
weight: z.number(),
accountId: z.string(),
orcidId: z.string(),
});

// TODO: actually export new version
// should allow orcidSplitReceiverSchema as a dependency
// for repoDriverAccountSplitsSchema
21 changes: 11 additions & 10 deletions src/models/LinkedIdentityModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ export default class LinkedIdentityModel extends Model<
InferAttributes<LinkedIdentityModel>,
InferCreationAttributes<LinkedIdentityModel>
> {
declare public accountId: RepoDriverId;
declare public identityType: LinkedIdentityType;
declare public ownerAddress: Address;
declare public ownerAccountId: AddressDriverId;
declare public isLinked: boolean;
declare public lastProcessedVersion: string;
declare public createdAt: CreationOptional<Date>;
declare public updatedAt: CreationOptional<Date>;
public declare accountId: RepoDriverId;
public declare identityType: LinkedIdentityType;
public declare ownerAddress: Address | null;
public declare ownerAccountId: AddressDriverId | null;
public declare isLinked: boolean;
public declare lastProcessedVersion: string;
public declare createdAt: CreationOptional<Date>;
public declare updatedAt: CreationOptional<Date>;

public static initialize(sequelize: Sequelize): void {
this.init(
Expand All @@ -41,16 +41,17 @@ export default class LinkedIdentityModel extends Model<
type: DataTypes.ENUM(...LINKED_IDENTITY_TYPES),
},
ownerAddress: {
allowNull: false,
allowNull: true,
type: DataTypes.STRING,
},
ownerAccountId: {
allowNull: false,
allowNull: true,
type: DataTypes.STRING,
},
isLinked: {
allowNull: false,
type: DataTypes.BOOLEAN,
defaultValue: false,
},
lastProcessedVersion: {
allowNull: false,
Expand Down
45 changes: 45 additions & 0 deletions src/utils/linkedIdentityUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { Transaction } from 'sequelize';
import type ScopedLogger from '../core/ScopedLogger';
import LinkedIdentityModel from '../models/LinkedIdentityModel';
import type { RepoDriverId } from '../core/types';
import { isOrcidAccount } from './accountIdUtils';
import { makeVersion } from './lastProcessedVersion';

export async function ensureLinkedIdentityExists(
accountId: RepoDriverId,
ctx: { blockNumber: number; logIndex: number },
transaction: Transaction,
scopedLogger: ScopedLogger,
): Promise<void> {
if (!isOrcidAccount(accountId)) {
throw new Error(
`${ensureLinkedIdentityExists.name} called with non-ORCID accountId: ${accountId}`,
);
}

const [identity, isCreation] = await LinkedIdentityModel.findOrCreate({
transaction,
lock: transaction.LOCK.UPDATE,
where: { accountId },
// Creates an "unclaimed" linked identity.
defaults: {
accountId,
identityType: 'orcid',
ownerAddress: null,
ownerAccountId: null,
isLinked: false,
lastProcessedVersion: makeVersion(
ctx.blockNumber,
ctx.logIndex,
).toString(),
},
});

if (isCreation) {
scopedLogger.bufferCreation({
type: LinkedIdentityModel,
input: identity,
id: identity.accountId,
});
}
}