From 5510d3a50bbc754e35f9be24e9e1e922767ed105 Mon Sep 17 00:00:00 2001 From: Maycon Mello Date: Thu, 23 Jul 2026 10:05:46 -0300 Subject: [PATCH 1/4] should not add delegation chain document to the credential store --- integration-tests/delegation-offer.test.ts | 38 ++++++++++++------- .../core/src/delegation/delegation-offer.ts | 11 +++--- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/integration-tests/delegation-offer.test.ts b/integration-tests/delegation-offer.test.ts index 3f4c65d2..4c4adb4a 100644 --- a/integration-tests/delegation-offer.test.ts +++ b/integration-tests/delegation-offer.test.ts @@ -169,18 +169,19 @@ describe('Credential Distribution', () => { expect(updatedOffer.holderDID).toBe(holderWallet.did); // Step 4: Holder receive the delegatable credential from the issuer - const delegatableCredential = await holderWallet.messageProvider.waitForMessage(); + const delegationIssuanceMessage = + await holderWallet.messageProvider.waitForMessage(); - console.log('[holder] received message after credential request:', delegatableCredential); + console.log('[holder] received message after credential request:', delegationIssuanceMessage); - expect(delegatableCredential).toBeDefined(); - expect(delegatableCredential.type).toBe('https://didcomm.org/issue-credential/3.0/issue-credential'); - expect(delegatableCredential.from).toBe(issuerWallet.did); - expect(delegatableCredential.body.delegationOfferId).toBe(acceptedOffer.id); - expect(Array.isArray(delegatableCredential.body.credentials)).toBe(true); - expect(delegatableCredential.body.credentials.length).toBeGreaterThan(0); + expect(delegationIssuanceMessage).toBeDefined(); + expect(delegationIssuanceMessage.type).toBe('https://didcomm.org/issue-credential/3.0/issue-credential'); + expect(delegationIssuanceMessage.from).toBe(issuerWallet.did); + expect(delegationIssuanceMessage.body.delegationOfferId).toBe(acceptedOffer.id); + expect(Array.isArray(delegationIssuanceMessage.body.credentials)).toBe(true); + expect(delegationIssuanceMessage.body.credentials.length).toBeGreaterThan(0); - const [issuedCredential] = delegatableCredential.body.credentials; + const [issuedCredential] = delegationIssuanceMessage.body.credentials; expect(issuedCredential.type).toContain('DelegationCredential'); expect(issuedCredential.rootCredentialId).toBe(rootCredential.id); expect(issuedCredential.delegationRoleId).toBe('e79c0d16-8739-4e54-94d7-53d9f1c97c71'); @@ -188,7 +189,7 @@ describe('Credential Distribution', () => { // Re-delegated credential must carry the same credentialSchema as the root credential. expect(issuedCredential.credentialSchema).toEqual(rootCredential.credentialSchema); - const delegationChain = delegatableCredential.body.delegationChain; + const delegationChain = delegationIssuanceMessage.body.delegationChain; expect(Array.isArray(delegationChain)).toBe(true); expect(delegationChain.length).toBeGreaterThan(0); expect(delegationChain[0].id).toBe(rootCredential.id); @@ -205,7 +206,7 @@ describe('Credential Distribution', () => { ); }); - await handleMessage(delegatableCredential, { + await handleMessage(delegationIssuanceMessage, { wallet: holderWallet.wallet, messageProvider: holderWallet.messageProvider, }); @@ -214,13 +215,22 @@ describe('Credential Distribution', () => { expect(receivedPayload.delegationOfferId).toBe(acceptedOffer.id); expect(receivedPayload.credentials[0].id).toBe(issuedCredential.id); + const allCredentials = + await holderWallet.credentialProvider.getCredentials(); + + // Delegated credential should be added to the credential store + expect(allCredentials.some(c => c.id === issuedCredential.id)).toBe(true); + // Delegation chain credentials should NOT be added to the credential store + expect(allCredentials.some(c => c.id === rootCredential.id)).toBe(false); + // Delegation chain documents should be stored in the wallet as separate document + const delegationChainDoc = await holderWallet.wallet.getDocumentById(`delegation-chain-${issuedCredential.id}`); + expect(delegationChainDoc).toBeDefined(); + expect(delegationChainDoc.credentials).toEqual(delegationChain); + // Verify the delegated credential was persisted on the holder side. const storedCredential = await holderWallet.wallet.getDocumentById( issuedCredential.id, ); - expect(storedCredential).toBeDefined(); - expect(storedCredential.rootCredentialId).toBe(rootCredential.id); - // Resolve the full delegation details for the stored credential and verify // it carries the capabilities the holder was delegated. diff --git a/packages/core/src/delegation/delegation-offer.ts b/packages/core/src/delegation/delegation-offer.ts index f03ea9cd..36f6b1de 100644 --- a/packages/core/src/delegation/delegation-offer.ts +++ b/packages/core/src/delegation/delegation-offer.ts @@ -426,12 +426,11 @@ export const ISSUE_CREDENTIAL_HANDLER = { await wallet.addDocument(credential); } - for (const ancestor of delegationChain) { - const existing = await wallet.getDocumentById(ancestor.id); - if (!existing) { - await wallet.addDocument(ancestor); - } - } + await wallet.addDocument({ + type: 'DelegationChain', + id: `delegation-chain-${credentials[0].id}`, + credentials: delegationChain, + }); await wallet.updateDocument({ ...storedOffer, From ec8dcaccf090d82c597c6398558b5f662ef7e4f5 Mon Sep 17 00:00:00 2001 From: Maycon Mello Date: Thu, 23 Jul 2026 10:25:16 -0300 Subject: [PATCH 2/4] handle PR feedback --- integration-tests/delegation-offer.test.ts | 4 ++-- .../core/src/delegation/delegation-offer.ts | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/integration-tests/delegation-offer.test.ts b/integration-tests/delegation-offer.test.ts index 4c4adb4a..cfa85870 100644 --- a/integration-tests/delegation-offer.test.ts +++ b/integration-tests/delegation-offer.test.ts @@ -223,9 +223,9 @@ describe('Credential Distribution', () => { // Delegation chain credentials should NOT be added to the credential store expect(allCredentials.some(c => c.id === rootCredential.id)).toBe(false); // Delegation chain documents should be stored in the wallet as separate document - const delegationChainDoc = await holderWallet.wallet.getDocumentById(`delegation-chain-${issuedCredential.id}`); + const delegationChainDoc = await holderWallet.wallet.getDocumentById(`${issuedCredential.id}#delegationChain`); expect(delegationChainDoc).toBeDefined(); - expect(delegationChainDoc.credentials).toEqual(delegationChain); + expect(delegationChainDoc.delegationChain).toEqual(delegationChain); // Verify the delegated credential was persisted on the holder side. const storedCredential = await holderWallet.wallet.getDocumentById( diff --git a/packages/core/src/delegation/delegation-offer.ts b/packages/core/src/delegation/delegation-offer.ts index 36f6b1de..ece67c4e 100644 --- a/packages/core/src/delegation/delegation-offer.ts +++ b/packages/core/src/delegation/delegation-offer.ts @@ -3,7 +3,7 @@ import {v4 as uuid} from 'uuid'; import {logger} from '@docknetwork/wallet-sdk-data-store/src/logger'; import {getAllDIDs, getDefaultDID} from '../did-provider'; import {delegateCredential} from './delegation-issuance'; -import {getDelegationChain} from './delegation-chain'; +import {addDelegationChain, getDelegationChain} from './delegation-chain'; import {getDelegationDetails, getRole} from './delegation-policy'; import {isDelegatableCredential} from './delegation-utils'; import { @@ -382,8 +382,7 @@ export const DELEGATION_REQUEST_HANDLER = { export const ISSUE_CREDENTIAL_HANDLER = { check: function (message) { return ( - message.type === ISSUE_CREDENTIAL && - message.body?.goal_code === GOAL_CODE + message.type === ISSUE_CREDENTIAL && message.body?.goal_code === GOAL_CODE ); }, handle: async function (message, {wallet, messageProvider}) { @@ -426,11 +425,13 @@ export const ISSUE_CREDENTIAL_HANDLER = { await wallet.addDocument(credential); } - await wallet.addDocument({ - type: 'DelegationChain', - id: `delegation-chain-${credentials[0].id}`, - credentials: delegationChain, - }); + const [delegatedCredential] = credentials; + const existingChain = await wallet.getDocumentById( + `${delegatedCredential.id}#delegationChain`, + ); + if (!existingChain) { + await addDelegationChain(delegatedCredential, delegationChain, wallet); + } await wallet.updateDocument({ ...storedOffer, From bb1d93d8e26853fd187e7f834e9824b8cb40dd7b Mon Sep 17 00:00:00 2001 From: Maycon Mello Date: Thu, 23 Jul 2026 14:55:52 -0300 Subject: [PATCH 3/4] update delegation chain order --- packages/core/src/delegation/delegation-chain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/delegation/delegation-chain.ts b/packages/core/src/delegation/delegation-chain.ts index 42aa38e5..baa678a4 100644 --- a/packages/core/src/delegation/delegation-chain.ts +++ b/packages/core/src/delegation/delegation-chain.ts @@ -10,7 +10,7 @@ export async function getDelegationChain(credential, wallet) { ? document.delegationChain : []; - return [credential, ...delegationChain]; + return [...delegationChain, credential]; } export async function addDelegationChain(credential, delegationChain, wallet) { From cab159578475d2d11c28db6de3dd952e9e5c91d2 Mon Sep 17 00:00:00 2001 From: Maycon Mello Date: Thu, 23 Jul 2026 15:25:50 -0300 Subject: [PATCH 4/4] handle message ack --- integration-tests/delegation-offer.test.ts | 16 ++++++++--- .../core/src/delegation/delegation-offer.ts | 27 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/integration-tests/delegation-offer.test.ts b/integration-tests/delegation-offer.test.ts index cfa85870..a499fc99 100644 --- a/integration-tests/delegation-offer.test.ts +++ b/integration-tests/delegation-offer.test.ts @@ -262,9 +262,8 @@ describe('Credential Distribution', () => { ].sort(), ); - // Chain starts with the holder's own delegated credential. - expect(delegationDetails.delegationChain[0].id).toBe(storedCredential.id); - expect(delegationDetails.delegationChain[0].rootCredentialId).toBe(rootCredential.id); + expect(delegationDetails.delegationChain[1].id).toBe(storedCredential.id); + expect(delegationDetails.delegationChain[1].rootCredentialId).toBe(rootCredential.id); // Verify the holder's stored offer was advanced to 'accepted'. const holderStoredOffer = await holderWallet.wallet.getDocumentById( @@ -279,5 +278,16 @@ describe('Credential Distribution', () => { expect(ackMessage.from).toBe(holderWallet.did); expect(ackMessage.body.delegationOfferId).toBe(acceptedOffer.id); expect(ackMessage.body.status).toBe('OK'); + + await handleMessage(ackMessage, { + wallet: issuerWallet.wallet, + messageProvider: issuerWallet.messageProvider, + }); + + const delegationOfferDoc = await issuerWallet.wallet.getDocumentById( + acceptedOffer.id, + ); + + expect(delegationOfferDoc.status).toBe('accepted'); }, 60_000); }); diff --git a/packages/core/src/delegation/delegation-offer.ts b/packages/core/src/delegation/delegation-offer.ts index ece67c4e..056b8180 100644 --- a/packages/core/src/delegation/delegation-offer.ts +++ b/packages/core/src/delegation/delegation-offer.ts @@ -462,10 +462,37 @@ export const ISSUE_CREDENTIAL_HANDLER = { }, }; +export const DELEGATION_ACK_HANDLER = { + check: function (message) { + return message.type === ACK && message.body?.goal_code === GOAL_CODE; + }, + handle: async function (message, {wallet}) { + const offerId = message.body.delegationOfferId; + + const storedOffer = await wallet.getDocumentById(offerId); + if (!storedOffer) { + logger.debug( + `DELEGATION_ACK_HANDLER: no stored offer found for ${offerId}`, + ); + return; + } + + storedOffer.status = 'accepted'; + storedOffer.updatedAt = new Date().toISOString(); + + await wallet.updateDocument(storedOffer); + + logger.debug( + `DELEGATION_ACK_HANDLER: delegation offer ${offerId} marked as accepted`, + ); + }, +}; + export const messageHandlers = [ INVITATION_HANDLER, DELEGATION_REQUEST_HANDLER, ISSUE_CREDENTIAL_HANDLER, + DELEGATION_ACK_HANDLER, ]; export async function handleMessage(