Skip to content
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
6 changes: 3 additions & 3 deletions core/token-standard-service/src/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { vi, describe, it, expect, beforeEach, type Mocked } from 'vitest'

import { v3_4 } from '@canton-network/core-ledger-client-types'
import { v3_5 } from '@canton-network/core-ledger-client-types'
import { CoreService } from './token-standard-service.js'
import { AccessTokenProvider } from '@canton-network/core-wallet-auth'
import { LedgerProvider } from '@canton-network/core-provider-ledger'
Expand All @@ -13,9 +13,9 @@ import ledgerEffectsMock from './test-data/mock/utility-payload-ledger-effects.j
import ledgerEffectsExpected from './test-data/expected/utility-payload-ledger-effects-sender.json'

type JsGetEventsByContractIdResponse =
v3_4.components['schemas']['JsGetEventsByContractIdResponse']
v3_5.components['schemas']['JsGetEventsByContractIdResponse']

type CreatedEvent = v3_4.components['schemas']['CreatedEvent']
type CreatedEvent = v3_5.components['schemas']['CreatedEvent']

const makeLedgerProviderMock = (
responses: JsGetEventsByContractIdResponse[]
Expand Down
2 changes: 2 additions & 0 deletions core/token-standard-service/src/test-data/expected/txs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2667,6 +2667,7 @@
"creates": [
{
"amount": "30.0000000000",
"contractId": "4",
"instrumentId": {
"admin": "alice::normalized",
"id": "DummyHolding"
Expand Down Expand Up @@ -2759,6 +2760,7 @@
"creates": [
{
"amount": "40.0000000000",
"contractId": "5",
"instrumentId": {
"admin": "alice::normalized",
"id": "DummyHolding"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@
"id": "LOAD"
},
"amount": "1016.0000000000",
"contractId": "0027bd11771073016844744a664ae407f590c36090602e6ed6fa5c044c6185b911ca111220d8c8c605a8a79276475e4f3e2133cf1ef154669624084fde9fcf7ded8c08dc9a",
"lock": {
"holders": [
"test-registrar::122002e9e6d1d24cf3fc613d9c6e44919e13f6577b805000f629a773f3817081efc6"
Expand Down
106 changes: 51 additions & 55 deletions core/token-standard-service/src/token-standard-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -964,61 +964,57 @@ describe('Token standard service', () => {
])
})

it('holding locked returns correctly', async () => {
const future = new Date(Date.now() + 100_000).toISOString()
const past = new Date(Date.now() - 100_000).toISOString()

expect(
TokenStandardService.isHoldingLocked({
lock: null,
owner: '',
instrumentId: {
admin: '',
id: '',
},
amount: '',
meta: undefined,
} as any)
).toBe(false)

expect(
TokenStandardService.isHoldingLocked({
lock: {},
owner: '',
instrumentId: {
admin: '',
id: '',
},
amount: '',
meta: undefined,
} as any)
).toBe(true)

expect(
TokenStandardService.isHoldingLocked({
lock: { expiresAt: future },
owner: '',
instrumentId: {
admin: '',
id: '',
},
amount: '',
meta: undefined,
} as any)
).toBe(true)

expect(
TokenStandardService.isHoldingLocked({
lock: { expiresAt: past },
owner: '',
instrumentId: {
admin: '',
id: '',
},
amount: '',
meta: undefined,
} as any)
).toBe(false)
describe('isHoldingLocked', () => {
// Used for relative time
const createdAt = '2026-09-01T10:00:00.000Z'

const past = '2026-09-01T10:59:59.999Z'
const pastRel = { microseconds: '3599999000' }

const now = '2026-09-01T11:00:00.000Z'
const nowRel = { microseconds: '3600000000' }

const future = '2026-09-01T11:00:00.001Z'
const futureRel = { microseconds: '3600001000' }

it.each([
[null, false],
[{}, true],
[{ expiresAt: null }, true],
[{ expiresAt: past }, false],
[{ expiresAt: now }, false],
[{ expiresAt: future }, true],
[{ expiresAfter: pastRel }, false],
[{ expiresAfter: nowRel }, false],
[{ expiresAfter: futureRel }, true],
[{ expiresAt: future, expiresAfter: futureRel }, true],
[{ expiresAt: now, expiresAfter: futureRel }, false],
[{ expiresAt: future, expiresAfter: nowRel }, false],
])('holding locked with lock %o returns %s', (lock, expected) => {
expect(
TokenStandardService.isHoldingLocked(
{
interfaceViewValue: {
lock,
owner: '',
instrumentId: {
admin: '',
id: '',
},
amount: '',
meta: undefined,
},
contractId: '',
activeContract: {
createdEvent: { createdAt },
synchronizerId: '',
reassignmentCounter: 0,
},
} as any,
new Date(now)
)
).toBe(expected)
})
})

it('create delegate proxy transfer', async () => {
Expand Down
57 changes: 39 additions & 18 deletions core/token-standard-service/src/token-standard-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import {
PrettyContract,
renderTransaction,
ViewValue,
Holding as TxParseHolding,
PrettyTransactions,
Transaction,
TransferObject,
Expand Down Expand Up @@ -126,17 +125,9 @@ export class CoreService {
)
}

const unlockedSenderHoldings = senderHoldings.filter((utxo) => {
//filter out locked holdings
const lock = utxo.interfaceViewValue.lock
if (!lock) return true

const expiresAt = lock.expiresAt
if (!expiresAt) return false

const expiresAtDate = new Date(expiresAt)
return expiresAtDate <= now
})
const unlockedSenderHoldings = senderHoldings.filter(
(utxo) => !TokenStandardService.isHoldingLocked(utxo, now)
)

if (unlockedSenderHoldings.length > 100) {
this.logger.warn(`Sender has more than 100 unlocked utxos.`)
Expand Down Expand Up @@ -1723,16 +1714,46 @@ export class TokenStandardService {
}

static isHoldingLocked(
holding: Holding | TxParseHolding,
holding: PrettyContract<HoldingView>,
currentTime: Date = new Date()
): boolean {
const lock = holding.lock
const lock = holding.interfaceViewValue.lock
if (!lock) return false

const expiresAt = lock.expiresAt
if (!expiresAt) return true
let expiresAtAbsolute: Date | null = null
let expiresAtRelative: Date | null = null

if (lock.expiresAfter) {
const createdAt = new Date(
holding.activeContract.createdEvent.createdAt
)

// 1 microsecond = 0.001 milliseconds
const msToAdd = parseInt(lock.expiresAfter.microseconds) / 1000

expiresAtRelative = new Date(createdAt.getTime() + msToAdd)
}
Comment thread
pascalhonegger-da marked this conversation as resolved.
if (lock.expiresAt) {
expiresAtAbsolute = new Date(lock.expiresAt)
}

let expiresAt: Date

// If both `expiresAt` and `expiresAfter` are set, the lock expires at the earlier of the two times.
if (expiresAtRelative && expiresAtAbsolute) {
expiresAt =
expiresAtRelative < expiresAtAbsolute
? expiresAtRelative
: expiresAtAbsolute
} else if (expiresAtRelative) {
expiresAt = expiresAtRelative
} else if (expiresAtAbsolute) {
expiresAt = expiresAtAbsolute
} else {
// No expiration => locked
return true
}

const expiresAtDate = new Date(expiresAt)
return currentTime < expiresAtDate
return currentTime < expiresAt
}
}
8 changes: 4 additions & 4 deletions core/tx-parser/src/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { LedgerProvider } from '@canton-network/core-provider-ledger'
import { v3_4 } from '@canton-network/core-ledger-client-types'
import { v3_5 } from '@canton-network/core-ledger-client-types'
import { vi, describe, it, expect, beforeEach, Mocked } from 'vitest'
import { TransactionParser } from './parser.js'
import eventsByContractIdResponses from './test-data/mock/eventsByContractIdResponses.json'
Expand All @@ -11,11 +11,11 @@ import bobTransferObjectsExpected from './test-data/expected/bob-transfer-object
import txsMock from './test-data/mock/txs.json'
import txsExpected from './test-data/expected/txs.json'

type JsTransaction = v3_4.components['schemas']['JsTransaction']
type CreatedEvent = v3_4.components['schemas']['CreatedEvent']
type JsTransaction = v3_5.components['schemas']['JsTransaction']
type CreatedEvent = v3_5.components['schemas']['CreatedEvent']
const EVENTS_BY_CID_PATH = '/v2/events/events-by-contract-id' as const
type JsGetEventsByContractIdResponse =
v3_4.components['schemas']['JsGetEventsByContractIdResponse']
v3_5.components['schemas']['JsGetEventsByContractIdResponse']

const makeLedgerProviderMock = (
responses: JsGetEventsByContractIdResponse[]
Expand Down
Loading
Loading