Skip to content
Closed
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
- beta-boe

jobs:
test:
Expand Down
9 changes: 8 additions & 1 deletion .releaserc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"branches": ["main"],
"branches": [
"main",
{
"name": "beta-boe",
"prerelease": "beta",
"channel": "beta"
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
Expand Down
718 changes: 537 additions & 181 deletions README.md

Large diffs are not rendered by default.

535 changes: 490 additions & 45 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
},
"dependencies": {
"@inquirer/prompts": "^5.3.8",
"@trustvc/trustvc": "^2.14.1",
"@trustvc/trustvc": "2.15.0-beta.3",
"@types/yargs": "^17.0.32",
"chalk": "^4.1.2",
"dotenv": "^16.0.0",
Expand Down
55 changes: 55 additions & 0 deletions samples/obligation-credential-subject.sample.json

Large diffs are not rendered by default.

82 changes: 79 additions & 3 deletions src/commands/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@ import {
v4SupportInterfaceIds,
v5SupportInterfaceIds,
DocumentStore__factory,
encrypt,
} from '@trustvc/trustvc';
import { encrypt } from '@trustvc/trustvc';

// Internal utilities
import { ConnectedSigner } from '../utils';

// Contract factories from TrustVC v5
const { TitleEscrow__factory, TradeTrustToken__factory, TDocDeployer__factory } = v5Contracts;
const {
TrustVCToken__factory,
ObligationEscrow__factory,
TitleEscrow__factory,
TradeTrustToken__factory,
TDocDeployer__factory,
} = v5Contracts;

// Interface for connectToTokenRegistry function arguments
interface ConnectToTokenRegistryArgs {
Expand All @@ -27,6 +32,11 @@ interface ConnectToTDocDeployerContractArgs {
wallet: Wallet | HDNodeWallet | ConnectedSigner | Signer;
}

interface ConnectToObligationRegistryArgs {
address: string;
wallet: Wallet | HDNodeWallet | ConnectedSigner | Signer;
}

/**
* Connects to a token registry contract instance.
*
Expand Down Expand Up @@ -419,3 +429,69 @@ export async function waitForTransaction(tx: any | string, provider: Provider, c
throw new Error('Provider required for transaction hash');
}
}

/**
* Connects to a TrustVCToken (obligation registry) contract instance.
*/
export const connectToObligationRegistry = async ({
address,
wallet,
}: ConnectToObligationRegistryArgs) => {
try {
signale.info(`Connecting to obligation registry at: ${address}`);
const registry = new ethers.Contract(address, TrustVCToken__factory.abi, wallet as any);
if (!registry) {
throw new Error(`Failed to connect to obligation registry at address: ${address}`);
}
signale.success(`Successfully connected to obligation registry`);
return registry;
} catch (error) {
signale.error(
`Error in connectToObligationRegistry: ${error instanceof Error ? error.message : String(error)}`,
);
throw error;
}
};

interface ConnectToObligationEscrowArgs {
tokenId: string;
address: string;
wallet: Wallet | HDNodeWallet | ConnectedSigner | Signer;
}

/**
* Resolves ObligationEscrow via ownerOf(tokenId) on the obligation registry and connects.
*/
export const connectToObligationEscrow = async ({
tokenId,
address,
wallet,
}: ConnectToObligationEscrowArgs) => {
try {
signale.info(`Connecting to obligation registry at: ${address}`);
const registry = new ethers.Contract(address, TrustVCToken__factory.abi, wallet as any);

signale.info(`Fetching obligation escrow address for tokenId: ${tokenId}`);
const escrowAddress = await registry.ownerOf(tokenId);
signale.info(`Obligation escrow address: ${escrowAddress}`);

if (!escrowAddress || escrowAddress === ZeroAddress) {
throw new Error(
`Invalid obligation escrow address for tokenId: ${tokenId}. Address: ${escrowAddress}`,
);
}

signale.info(`Connecting to obligation escrow at: ${escrowAddress}`);
const escrow = new ethers.Contract(escrowAddress, ObligationEscrow__factory.abi, wallet as any);
if (!escrow) {
throw new Error(`Failed to connect to obligation escrow at address: ${escrowAddress}`);
}
signale.success(`Successfully connected to obligation escrow`);
return escrow;
} catch (error) {
signale.error(
`Error in connectToObligationEscrow: ${error instanceof Error ? error.message : String(error)}`,
);
throw error;
}
};
51 changes: 51 additions & 0 deletions src/commands/obligation-escrow/accept-return-to-issuer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { error, info, success } from 'signale';
import { acceptReturnedObligationRegistry } from '@trustvc/trustvc';
import { BaseObligationEscrowCommand } from '../../types';
import {
displayTransactionPrice,
getErrorMessage,
getEtherscanAddress,
NetworkCmdName,
TransactionReceiptFees,
} from '../../utils';
import { promptBaseObligationEscrowInputs } from './shared';
import { runObligationEscrowTx } from './runTx';

export const command = 'accept-return-to-issuer';
export const describe = 'Issuer accepts a returned BoE obligation (burn / shred)';

export const handler = async (): Promise<void> => {
try {
const answers = await promptForInputs();
if (!answers) return;
await acceptReturnedHandler(answers);
} catch (err: unknown) {
error(err instanceof Error ? err.message : String(err));
}
};

export const promptForInputs = promptBaseObligationEscrowInputs;

export const acceptReturnedHandler = async (args: BaseObligationEscrowCommand) => {
try {
info(`Accepting returned obligation ${args.tokenId}`);
const transaction = await runObligationEscrowTx({
args,
// acceptReturned burns the title, which lives on the TrustVCToken registry, not the escrow.
populate: ({ registry }, encryptedRemark) =>
registry.burn.populateTransaction(args.tokenId, encryptedRemark),
sdk: acceptReturnedObligationRegistry as any,
sdkParams: { tokenId: args.tokenId, remarks: args.remark },
});
displayTransactionPrice(
transaction as unknown as TransactionReceiptFees,
args.network as NetworkCmdName,
);
success(`Return accepted (burned)`);
info(
`Find more details at ${getEtherscanAddress({ network: args.network })}/tx/${transaction.hash}`,
);
} catch (e) {
error(getErrorMessage(e));
}
};
49 changes: 49 additions & 0 deletions src/commands/obligation-escrow/accept.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { error, info, success } from 'signale';
import { acceptObligationRegistry } from '@trustvc/trustvc';
import { BaseObligationEscrowCommand } from '../../types';
import {
displayTransactionPrice,
getErrorMessage,
getEtherscanAddress,
NetworkCmdName,
TransactionReceiptFees,
} from '../../utils';
import { promptBaseObligationEscrowInputs } from './shared';
import { runObligationEscrowTx } from './runTx';

export const command = 'accept';
export const describe = 'Holder accepts an issued BoE obligation (Issued → Accepted)';

export const handler = async (): Promise<void> => {
try {
const answers = await promptForInputs();
if (!answers) return;
await acceptHandler(answers);
} catch (err: unknown) {
error(err instanceof Error ? err.message : String(err));
}
};

export const promptForInputs = promptBaseObligationEscrowInputs;

export const acceptHandler = async (args: BaseObligationEscrowCommand) => {
try {
info(`Accepting obligation token ${args.tokenId} on ${args.obligationRegistryAddress}`);
const transaction = await runObligationEscrowTx({
args,
populate: ({ escrow }, encryptedRemark) => escrow.accept.populateTransaction(encryptedRemark),
sdk: acceptObligationRegistry as any,
sdkParams: { remarks: args.remark },
});
displayTransactionPrice(
transaction as unknown as TransactionReceiptFees,
args.network as NetworkCmdName,
);
success(`Obligation ${args.tokenId} accepted`);
info(
`Find more details at ${getEtherscanAddress({ network: args.network })}/tx/${transaction.hash}`,
);
} catch (e) {
error(getErrorMessage(e));
}
};
50 changes: 50 additions & 0 deletions src/commands/obligation-escrow/discharge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { error, info, success } from 'signale';
import { dischargeObligationRegistry } from '@trustvc/trustvc';
import { BaseObligationEscrowCommand } from '../../types';
import {
displayTransactionPrice,
getErrorMessage,
getEtherscanAddress,
NetworkCmdName,
TransactionReceiptFees,
} from '../../utils';
import { promptBaseObligationEscrowInputs } from './shared';
import { runObligationEscrowTx } from './runTx';

export const command = 'discharge';
export const describe = 'Beneficiary discharges an accepted BoE obligation (terminal Discharged)';

export const handler = async (): Promise<void> => {
try {
const answers = await promptForInputs();
if (!answers) return;
await dischargeHandler(answers);
} catch (err: unknown) {
error(err instanceof Error ? err.message : String(err));
}
};

export const promptForInputs = promptBaseObligationEscrowInputs;

export const dischargeHandler = async (args: BaseObligationEscrowCommand) => {
try {
info(`Discharging obligation token ${args.tokenId} on ${args.obligationRegistryAddress}`);
const transaction = await runObligationEscrowTx({
args,
populate: ({ escrow }, encryptedRemark) =>
escrow.discharge.populateTransaction(encryptedRemark),
sdk: dischargeObligationRegistry as any,
sdkParams: { remarks: args.remark },
});
displayTransactionPrice(
transaction as unknown as TransactionReceiptFees,
args.network as NetworkCmdName,
);
success(`Obligation ${args.tokenId} discharged`);
info(
`Find more details at ${getEtherscanAddress({ network: args.network })}/tx/${transaction.hash}`,
);
} catch (e) {
error(getErrorMessage(e));
}
};
55 changes: 55 additions & 0 deletions src/commands/obligation-escrow/endorse-transfer-owner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { error, info, success } from 'signale';
import { transferBeneficiaryObligationRegistry } from '@trustvc/trustvc';
import { ObligationEscrowNominateBeneficiaryCommand } from '../../types';
import {
displayTransactionPrice,
getErrorMessage,
getEtherscanAddress,
NetworkCmdName,
promptAddress,
TransactionReceiptFees,
} from '../../utils';
import { promptBaseObligationEscrowInputs } from './shared';
import { runObligationEscrowTx } from './runTx';

export const command = 'endorse-transfer-owner';
export const describe = 'Endorse / transfer beneficiary (owner) of a BoE obligation';

export const handler = async (): Promise<void> => {
try {
const answers = await promptForInputs();
if (!answers) return;
await endorseHandler(answers);
} catch (err: unknown) {
error(err instanceof Error ? err.message : String(err));
}
};

export const promptForInputs = async (): Promise<ObligationEscrowNominateBeneficiaryCommand> => {
const base = await promptBaseObligationEscrowInputs();
const newBeneficiary = await promptAddress('new beneficiary', 'endorsed owner');
return { ...base, newBeneficiary } as ObligationEscrowNominateBeneficiaryCommand;
};

export const endorseHandler = async (args: ObligationEscrowNominateBeneficiaryCommand) => {
try {
info(`Transferring beneficiary to ${args.newBeneficiary} for obligation ${args.tokenId}`);
const transaction = await runObligationEscrowTx({
args,
populate: ({ escrow }, encryptedRemark) =>
escrow.transferBeneficiary.populateTransaction(args.newBeneficiary, encryptedRemark),
sdk: transferBeneficiaryObligationRegistry as any,
sdkParams: { newBeneficiaryAddress: args.newBeneficiary, remarks: args.remark },
});
displayTransactionPrice(
transaction as unknown as TransactionReceiptFees,
args.network as NetworkCmdName,
);
success(`Beneficiary transferred to ${args.newBeneficiary}`);
info(
`Find more details at ${getEtherscanAddress({ network: args.network })}/tx/${transaction.hash}`,
);
} catch (e) {
error(getErrorMessage(e));
}
};
11 changes: 11 additions & 0 deletions src/commands/obligation-escrow/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Argv } from 'yargs';

export const command = 'obligation-escrow <method>';

export const describe =
'Invoke ObligationEscrow lifecycle / transfer functions for BoE obligation records';

export const builder = (yargs: Argv): Argv =>
yargs.commandDir(__dirname, { extensions: ['ts', 'js'] });

export const handler = (): void => {};
Loading
Loading