Skip to content

Commit 960002b

Browse files
committed
chore: centralize identifier shortening across Surface
1 parent 86ac831 commit 960002b

11 files changed

Lines changed: 139 additions & 61 deletions

File tree

e2e/vue.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ test('connects Keplr without discovering identities', async ({ page }) => {
4848
await connectTrigger.click()
4949
await expect(page.locator('.wallet-register-head')).toHaveText('WALLET')
5050
await expect(page.locator('.wallet-provider')).toHaveText('Keplr')
51-
await expect(page.locator('.wallet-address')).toHaveText('axone1wall...ddress')
51+
await expect(page.locator('.wallet-address')).toHaveText('axone1wallddress')
5252
await expect(page.locator('.wallet-address-copy')).toHaveAttribute(
5353
'title',
5454
'axone1walletprivateaddress',

src/App.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { AnimatePresence, motion, MotionConfig } from 'motion-v'
55
66
import SurfaceActStream from './components/SurfaceActStream.vue'
77
import SurfaceDropdown from './components/SurfaceDropdown.vue'
8+
import { shortenWalletAddress } from './lib/shorten'
89
import { useSurfaceActs } from './composables/useSurfaceActs'
910
import { useWalletConnection } from './composables/useWalletConnection'
1011
import type { WalletProviderId } from './domain/wallet-connection'
@@ -108,10 +109,6 @@ async function connectWallet(provider: WalletProviderId) {
108109
}
109110
}
110111
111-
function compactWalletAddress(address: string): string {
112-
return address.length <= 16 ? address : `${address.slice(0, 10)}...${address.slice(-6)}`
113-
}
114-
115112
function clearWalletAddressCopiedTimer() {
116113
window.clearTimeout(walletAddressCopiedTimer)
117114
walletAddressCopiedTimer = undefined
@@ -297,7 +294,7 @@ onBeforeUnmount(() => {
297294
target="_blank"
298295
rel="noopener noreferrer"
299296
>
300-
{{ compactWalletAddress(walletConnection.address) }}
297+
{{ shortenWalletAddress(walletConnection.address) }}
301298
</a>
302299
<span class="wallet-address-action">
303300
<button

src/__tests__/App.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ describe('App', () => {
294294
await wrapper.get('.top-connect').trigger('click')
295295
expect(wrapper.get('.wallet-menu .surface-dropdown-heading').text()).toBe('WALLET')
296296
expect(wrapper.get('.wallet-provider').text()).toBe('Keplr')
297-
expect(wrapper.get('.wallet-address').text()).toBe('axone1wall...ddress')
297+
expect(wrapper.get('.wallet-address').text()).toBe('axone1wallddress')
298298
const walletAddressLink = wrapper.get<HTMLAnchorElement>('.wallet-address')
299299
expect(walletAddressLink.attributes('href')).toBe(
300300
`https://explorer.aknodes.com/AXONE-TESTNET/account/${walletAddress}`,

src/components/SurfaceActLine.vue

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import { computed, onBeforeUnmount, ref, watch } from 'vue'
33
4+
import { shortenHash, shortenIdentifier } from '../lib/shorten'
45
import { surfaceActKindCategories, type SurfaceAct } from '../domain/surface-act'
56
67
const props = defineProps<{
@@ -21,8 +22,10 @@ const transactionCopyState = ref<'idle' | 'copying' | 'copied'>('idle')
2122
let copiedTimer: number | undefined
2223
let isUnmounted = false
2324
24-
const assertionIdentifierPattern = /(did:pkh:…cosmos1[a-z0-9]+[a-z0-9]{6}|urn:[a-z0-9:-]+)/i
25-
const technicalIdentifierPattern = /^(did:pkh:…cosmos1[a-z0-9]+[a-z0-9]{6}|urn:[a-z0-9:-]+)$/i
25+
const assertionIdentifierPattern =
26+
/(did:pkh:…cosmos1[a-z0-9]+[a-z0-9]{6}|[a-z][a-z\d+.-]*:[^\s.]+(?:\.[^\s.]+)*)/i
27+
const technicalIdentifierPattern =
28+
/^(did:pkh:…cosmos1[a-z0-9]+[a-z0-9]{6}|[a-z][a-z\d+.-]*:[^\s.]+(?:\.[^\s.]+)*)$/i
2629
2730
const typedAssertionParts = computed(() => {
2831
let remainingLength = typedLength.value
@@ -41,22 +44,6 @@ const entryParts = computed(() => {
4144
})
4245
const transactionExplorerUrl = computed(() => `${props.explorer}/tx/${props.act.txhash}`)
4346
44-
function shortValue(value: string) {
45-
if (value.length <= 24) {
46-
return value
47-
}
48-
49-
return `${value.slice(0, 12)}...${value.slice(-6)}`
50-
}
51-
52-
function shortHash(value: string) {
53-
if (value.length <= 17) {
54-
return value
55-
}
56-
57-
return `${value.slice(0, 8)}…${value.slice(-8)}`
58-
}
59-
6047
function compactDate(value: string) {
6148
const normalized = value.match(
6249
/^(\d{4}-\d{2}-\d{2})[T ](\d{2}):(\d{2})(?::\d{2}(?:\.\d{3})?)?(?:Z| UTC)?$/,
@@ -173,7 +160,7 @@ onBeforeUnmount(() => {
173160
<div class="surface-act-proof-row surface-act-tx-row">
174161
<dt>tx</dt>
175162
<dd class="surface-act-tx">
176-
<span class="surface-act-tx-value">{{ shortValue(act.txhash) }}</span>
163+
<span class="surface-act-tx-value">{{ shortenHash(act.txhash) }}</span>
177164
<span class="surface-act-tx-action">
178165
<button
179166
v-if="transactionCopyState !== 'copied'"
@@ -213,7 +200,7 @@ onBeforeUnmount(() => {
213200
<dt>constitution</dt>
214201
<dd>
215202
r. {{ act.payload.constitution_revision }} ·
216-
{{ shortHash(act.payload.constitution_hash) }}
203+
{{ shortenHash(act.payload.constitution_hash) }}
217204
</dd>
218205
</div>
219206
<div
@@ -223,7 +210,7 @@ onBeforeUnmount(() => {
223210
"
224211
>
225212
<dt>credential</dt>
226-
<dd>{{ shortValue(act.payload.identifier) }}</dd>
213+
<dd>{{ shortenIdentifier(act.payload.identifier) }}</dd>
227214
</div>
228215
<div v-if="act.kind === 'governance.decision.recorded' && act.payload.verdict">
229216
<dt>verdict</dt>

src/components/__tests__/SurfaceActStream.spec.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ describe('SurfaceActStream', () => {
6868

6969
it('renders embedded DID and URN identifiers in monospace spans', async () => {
7070
const assertion =
71-
'Credential issued by did:pkh:…cosmos1s7u…texh8c for subject urn:axone:testnet:subject:gh29632273325a1-1.'
71+
'Credential issued by did:pkh:…cosmos1s7u…texh8c for subject urn:axone:te…23a1-1.'
7272
const wrapper = mount(SurfaceActLine, {
7373
props: {
7474
act: { ...makeAct('TX-DID', 1, 'axone1issuer'), assertion },
@@ -84,7 +84,7 @@ describe('SurfaceActStream', () => {
8484
const identifiers = wrapper.findAll('.surface-act-identifier')
8585
expect(identifiers.map((identifier) => identifier.text())).toEqual([
8686
'did:pkh:…cosmos1s7u…texh8c',
87-
'urn:axone:testnet:subject:gh29632273325a1-1',
87+
'urn:axone:te…23a1-1',
8888
])
8989
expect(wrapper.get('.surface-act-inscription').text()).toBe(assertion)
9090
})
@@ -120,27 +120,23 @@ describe('SurfaceActStream', () => {
120120
verdict: 'gov:permitted',
121121
},
122122
},
123-
expected: [
124-
'decisionn° 1',
125-
'constitution r. 3 · 8C11A47D…B2903E12',
126-
'verdictgov:permitted',
127-
],
123+
expected: ['decisionn° 1', 'constitution r. 3 · 8C11A47D…B2903E12', 'verdictgov:permitted'],
128124
},
129125
{
130126
act: {
131127
...makeAct('TX-CRED-ISS', 1, 'axone1authority'),
132128
kind: 'credential.issued',
133129
payload: { identifier: credentialId },
134130
},
135-
expected: ['credentialCRED-1234567...ABCDEF'],
131+
expected: ['credentialCRED-1234567ABCDEF'],
136132
},
137133
{
138134
act: {
139135
...makeAct('TX-CRED-REVK', 1, 'axone1authority'),
140136
kind: 'credential.revoked',
141137
payload: { identifier: credentialId },
142138
},
143-
expected: ['credentialCRED-1234567...ABCDEF'],
139+
expected: ['credentialCRED-1234567ABCDEF'],
144140
},
145141
]
146142

@@ -178,7 +174,7 @@ describe('SurfaceActStream', () => {
178174
})
179175

180176
const transactionValue = wrapper.get('.surface-act-tx-value')
181-
expect(transactionValue.text()).toBe('34BB1E16A405...8A931F')
177+
expect(transactionValue.text()).toBe('34BB1E16…EF8A931F')
182178
expect(transactionValue.element.tagName).toBe('SPAN')
183179
expect(transactionValue.attributes('href')).toBeUndefined()
184180
const explorerButton = wrapper.get('.surface-act-explorer')

src/domain/__tests__/abstract-account.spec.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from 'vitest'
22

3-
import { compactCanonicalDid, toCanonicalDid } from '../abstract-account'
3+
import { toCanonicalDid } from '../abstract-account'
44

55
const axoneAddress = 'axone1lfcc2yt3gmd3xspw5yxsl3r9qyuumuya6hur2gnejgmafyrapmkqhg7gd5'
66

@@ -11,14 +11,6 @@ describe('toCanonicalDid', () => {
1111
)
1212
})
1313

14-
it('formats a canonical DID with a stable compact Cosmos address', () => {
15-
expect(
16-
compactCanonicalDid(
17-
'did:pkh:cosmos:axone-dendrite-2:cosmos1lfcc2yt3gmd3xspw5yxsl3r9qyuumuya6hur2gnejgmafyrapmkqpk2un3',
18-
),
19-
).toBe('did:pkh:…cosmos1lfc…pk2un3')
20-
})
21-
2214
it('rejects malformed bech32 addresses', () => {
2315
expect(() => toCanonicalDid('not-an-address', 'axone-dendrite-2')).toThrow(
2416
'No separator character for not-an-address',

src/domain/__tests__/surface-act-mapper.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,15 @@ describe('surface-act-mapper', () => {
185185
module: {
186186
issue_credential: {
187187
credential: btoa(
188-
`<urn:credential> <https://www.w3.org/2018/credentials#issuer> <${credentialIssuerDid}> .\n<urn:credential> <https://www.w3.org/2018/credentials#credentialSubject> <urn:axone:testnet:subject:gh29632273325a1-1> .`,
188+
`<urn:credential> <https://www.w3.org/2018/credentials#issuer> <${credentialIssuerDid}> .\n<urn:credential> <https://www.w3.org/2018/credentials#credentialSubject> <urn:axone:testnet:subject:gh31175346323a1-1> .`,
189189
),
190190
},
191191
},
192192
},
193193
},
194194
),
195195
kind: 'credential.issued',
196-
assertion: `Credential issued by ${compactCredentialIssuerDid} for subject urn:axone:testnet:subject:gh29632273325a1-1.`,
196+
assertion: `Credential issued by ${compactCredentialIssuerDid} for subject urn:axone:te…23a1-1.`,
197197
},
198198
{
199199
tx: makeExecuteTx('TX-ISS-DID', [

src/domain/abstract-account.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,3 @@ export function toCanonicalDid(address: string, chainId: string): string {
66

77
return `did:pkh:cosmos:${chainId}:${cosmosAddress}`
88
}
9-
10-
export function compactCanonicalDid(did: string): string {
11-
const cosmosAddress = did.slice(did.lastIndexOf(':') + 1)
12-
13-
return `did:pkh:…${cosmosAddress.slice(0, 10)}${cosmosAddress.slice(-6)}`
14-
}

src/domain/surface-act-mapper.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ import {
77
type CosmosMessage,
88
type CosmosTxResponse,
99
} from '../infra/axone-event-extractor'
10+
import { shortenDid, shortenUri } from '../lib/shorten'
1011
import {
1112
surfaceActKindDescriptions,
1213
surfaceActKindLabels,
1314
type SurfaceAct,
1415
type SurfaceActKind,
1516
} from './surface-act'
16-
import { compactCanonicalDid, toCanonicalDid } from './abstract-account'
17+
import { toCanonicalDid } from './abstract-account'
1718

1819
const instantiateAction = '/cosmwasm.wasm.v1.MsgInstantiateContract2'
20+
const uriSchemePattern = /^[a-z][a-z\d+.-]*:/i
1921
const executeAction = '/cosmwasm.wasm.v1.MsgExecuteContract'
2022

2123
function toNumber(value: string | undefined, fallback = 0) {
@@ -63,14 +65,18 @@ function stringPayload(values: Record<string, string | undefined>): Record<strin
6365

6466
function abstractAccountSubject(address: string, chainId: string) {
6567
try {
66-
return compactCanonicalDid(toCanonicalDid(address, chainId))
68+
return shortenDid(toCanonicalDid(address, chainId))
6769
} catch {
6870
return undefined
6971
}
7072
}
7173

72-
function compactDidIdentifier(identifier: string) {
73-
return identifier.startsWith('did:pkh:') ? compactCanonicalDid(identifier) : identifier
74+
function shortenAssertionIdentifier(identifier: string) {
75+
if (identifier.startsWith('did:')) {
76+
return shortenDid(identifier)
77+
}
78+
79+
return uriSchemePattern.test(identifier) ? shortenUri(identifier) : identifier
7480
}
7581

7682
function installedModuleIds(value: string | undefined) {
@@ -366,7 +372,7 @@ function mapWasmAbstractEvent(
366372
action,
367373
title: surfaceActKindLabels['credential.issued'],
368374
description: surfaceActKindDescriptions['credential.issued'],
369-
assertion: `Credential issued by ${compactDidIdentifier(issuer)} for subject ${compactDidIdentifier(subject)}.`,
375+
assertion: `Credential issued by ${shortenAssertionIdentifier(issuer)} for subject ${shortenAssertionIdentifier(subject)}.`,
370376
payload: stringPayload({
371377
identifier: credentialId,
372378
issuer,
@@ -401,7 +407,7 @@ function mapWasmAbstractEvent(
401407
action,
402408
title: surfaceActKindLabels['credential.revoked'],
403409
description: surfaceActKindDescriptions['credential.revoked'],
404-
assertion: `Credential ${compactDidIdentifier(credentialId)} revoked by ${authority}.`,
410+
assertion: `Credential ${shortenAssertionIdentifier(credentialId)} revoked by ${authority}.`,
405411
payload: stringPayload({
406412
identifier: credentialId,
407413
issuer: attributes.issuer ?? '',

src/lib/__tests__/shorten.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import {
4+
shortenDid,
5+
shortenHash,
6+
shortenIdentifier,
7+
shortenUri,
8+
shortenWalletAddress,
9+
} from '../shorten'
10+
11+
describe('identifier shortening', () => {
12+
it('shortens identifiers according to their semantic display policies', () => {
13+
expect(
14+
shortenDid(
15+
'did:pkh:cosmos:axone-dendrite-2:cosmos1lfcc2yt3gmd3xspw5yxsl3r9qyuumuya6hur2gnejgmafyrapmkqpk2un3',
16+
),
17+
).toBe('did:pkh:…cosmos1lfc…pk2un3')
18+
expect(shortenUri('https://example.org/path/to/resource')).toBe('https://exam…source')
19+
expect(shortenUri('urn:axone:testnet:subject:gh29632273325a1-1')).toBe('urn:axone:te…25a1-1')
20+
expect(shortenWalletAddress('axone1walletprivateaddress')).toBe('axone1wall…ddress')
21+
expect(shortenHash('0123456789ABCDEF'.repeat(4))).toBe('01234567…89ABCDEF')
22+
expect(shortenIdentifier('CRED-12345678901234567890-ABCDEF')).toBe('CRED-1234567…ABCDEF')
23+
})
24+
25+
it.each([
26+
['DID', shortenDid],
27+
['URI', shortenUri],
28+
['wallet address', shortenWalletAddress],
29+
['hash', shortenHash],
30+
['identifier', shortenIdentifier],
31+
])('preserves empty %s input', (_kind, shorten) => {
32+
expect(shorten('')).toBe('')
33+
})
34+
35+
it.each([
36+
['URI', shortenUri, 12, 6],
37+
['wallet address', shortenWalletAddress, 10, 6],
38+
['hash', shortenHash, 8, 8],
39+
['identifier', shortenIdentifier, 12, 6],
40+
])('preserves and then compacts %s threshold values', (_kind, shorten, leading, trailing) => {
41+
const thresholdValue = 'a'.repeat(leading + 1 + trailing)
42+
const overThresholdValue = `${thresholdValue}x`
43+
44+
expect(shorten(thresholdValue)).toBe(thresholdValue)
45+
expect(shorten(overThresholdValue)).toBe(`${'a'.repeat(leading)}${'a'.repeat(trailing - 1)}x`)
46+
expect(shorten(overThresholdValue).length).toBeLessThanOrEqual(overThresholdValue.length)
47+
})
48+
49+
it('preserves and then compacts DID final identifier segment thresholds', () => {
50+
const prefix = 'did:pkh:cosmos:axone-dendrite-2:'
51+
const thresholdDid = `${prefix}${'a'.repeat(17)}`
52+
const overThresholdDid = `${thresholdDid}x`
53+
54+
expect(shortenDid(thresholdDid)).toBe(thresholdDid)
55+
expect(shortenDid(overThresholdDid)).toBe('did:pkh:…aaaaaaaaaa…aaaaax')
56+
expect(shortenDid(overThresholdDid).length).toBeLessThanOrEqual(overThresholdDid.length)
57+
})
58+
59+
it('delegates malformed DIDs to identifier shortening without throwing', () => {
60+
const malformedDid = 'did:missing-separator-with-a-long-identifier'
61+
62+
expect(() => shortenDid(malformedDid)).not.toThrow()
63+
expect(shortenDid(malformedDid)).toBe(shortenIdentifier(malformedDid))
64+
})
65+
})

0 commit comments

Comments
 (0)