@ledgerhq/device-contacts-kit 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +22 -13
  2. package/lib/cjs/api/index.js +1 -1
  3. package/lib/cjs/api/index.js.map +3 -3
  4. package/lib/cjs/api/model/ContactsVersionRequirements.js +1 -1
  5. package/lib/cjs/api/model/ContactsVersionRequirements.js.map +3 -3
  6. package/lib/cjs/api/model/ContactsVersionRequirements.test.js +1 -1
  7. package/lib/cjs/api/model/ContactsVersionRequirements.test.js.map +3 -3
  8. package/lib/cjs/api/model/ProvideContact.js +2 -0
  9. package/lib/cjs/api/model/ProvideContact.js.map +7 -0
  10. package/lib/cjs/internal/app-binder/command/ProvideContactCommand.js +2 -0
  11. package/lib/cjs/internal/app-binder/command/ProvideContactCommand.js.map +7 -0
  12. package/lib/cjs/internal/app-binder/command/ProvideContactCommand.test.js +2 -0
  13. package/lib/cjs/internal/app-binder/command/ProvideContactCommand.test.js.map +7 -0
  14. package/lib/cjs/internal/app-binder/contactsVersionGuards.js +1 -1
  15. package/lib/cjs/internal/app-binder/contactsVersionGuards.js.map +2 -2
  16. package/lib/cjs/internal/app-binder/contactsVersionGuards.test.js +1 -1
  17. package/lib/cjs/internal/app-binder/contactsVersionGuards.test.js.map +2 -2
  18. package/lib/cjs/internal/app-binder/model/contactsConstants.js +1 -1
  19. package/lib/cjs/internal/app-binder/model/contactsConstants.js.map +3 -3
  20. package/lib/cjs/internal/app-binder/services/provideContact.js +2 -0
  21. package/lib/cjs/internal/app-binder/services/provideContact.js.map +7 -0
  22. package/lib/cjs/internal/app-binder/services/provideContact.test.js +2 -0
  23. package/lib/cjs/internal/app-binder/services/provideContact.test.js.map +7 -0
  24. package/lib/cjs/internal/app-binder/task/SendRegisterIdentityTask.js.map +2 -2
  25. package/lib/cjs/internal/app-binder/task/SendRegisterIdentityTask.test.js.map +1 -1
  26. package/lib/cjs/internal/app-binder/task/SendRenameContactTask.js +1 -1
  27. package/lib/cjs/internal/app-binder/task/SendRenameContactTask.js.map +2 -2
  28. package/lib/cjs/internal/app-binder/task/SendRenameContactTask.test.js +1 -1
  29. package/lib/cjs/internal/app-binder/task/SendRenameContactTask.test.js.map +3 -3
  30. package/lib/cjs/package.json +2 -2
  31. package/lib/esm/api/index.js +1 -1
  32. package/lib/esm/api/index.js.map +3 -3
  33. package/lib/esm/api/model/ContactsVersionRequirements.js +1 -1
  34. package/lib/esm/api/model/ContactsVersionRequirements.js.map +3 -3
  35. package/lib/esm/api/model/ContactsVersionRequirements.test.js +1 -1
  36. package/lib/esm/api/model/ContactsVersionRequirements.test.js.map +3 -3
  37. package/lib/esm/api/model/ProvideContact.js +1 -0
  38. package/lib/esm/api/model/ProvideContact.js.map +7 -0
  39. package/lib/esm/internal/app-binder/command/ProvideContactCommand.js +2 -0
  40. package/lib/esm/internal/app-binder/command/ProvideContactCommand.js.map +7 -0
  41. package/lib/esm/internal/app-binder/command/ProvideContactCommand.test.js +2 -0
  42. package/lib/esm/internal/app-binder/command/ProvideContactCommand.test.js.map +7 -0
  43. package/lib/esm/internal/app-binder/contactsVersionGuards.js +1 -1
  44. package/lib/esm/internal/app-binder/contactsVersionGuards.js.map +2 -2
  45. package/lib/esm/internal/app-binder/contactsVersionGuards.test.js +1 -1
  46. package/lib/esm/internal/app-binder/contactsVersionGuards.test.js.map +2 -2
  47. package/lib/esm/internal/app-binder/model/contactsConstants.js +1 -1
  48. package/lib/esm/internal/app-binder/model/contactsConstants.js.map +3 -3
  49. package/lib/esm/internal/app-binder/services/provideContact.js +2 -0
  50. package/lib/esm/internal/app-binder/services/provideContact.js.map +7 -0
  51. package/lib/esm/internal/app-binder/services/provideContact.test.js +2 -0
  52. package/lib/esm/internal/app-binder/services/provideContact.test.js.map +7 -0
  53. package/lib/esm/internal/app-binder/task/SendRegisterIdentityTask.js.map +2 -2
  54. package/lib/esm/internal/app-binder/task/SendRegisterIdentityTask.test.js.map +1 -1
  55. package/lib/esm/internal/app-binder/task/SendRenameContactTask.js +1 -1
  56. package/lib/esm/internal/app-binder/task/SendRenameContactTask.js.map +3 -3
  57. package/lib/esm/internal/app-binder/task/SendRenameContactTask.test.js +1 -1
  58. package/lib/esm/internal/app-binder/task/SendRenameContactTask.test.js.map +3 -3
  59. package/lib/esm/package.json +2 -2
  60. package/lib/types/api/index.d.ts +4 -1
  61. package/lib/types/api/index.d.ts.map +1 -1
  62. package/lib/types/api/model/ContactsVersionRequirements.d.ts +7 -25
  63. package/lib/types/api/model/ContactsVersionRequirements.d.ts.map +1 -1
  64. package/lib/types/api/model/ProvideContact.d.ts +20 -0
  65. package/lib/types/api/model/ProvideContact.d.ts.map +1 -0
  66. package/lib/types/internal/app-binder/command/ProvideContactCommand.d.ts +18 -0
  67. package/lib/types/internal/app-binder/command/ProvideContactCommand.d.ts.map +1 -0
  68. package/lib/types/internal/app-binder/command/ProvideContactCommand.test.d.ts +2 -0
  69. package/lib/types/internal/app-binder/command/ProvideContactCommand.test.d.ts.map +1 -0
  70. package/lib/types/internal/app-binder/model/contactsConstants.d.ts +2 -0
  71. package/lib/types/internal/app-binder/model/contactsConstants.d.ts.map +1 -1
  72. package/lib/types/internal/app-binder/services/provideContact.d.ts +15 -0
  73. package/lib/types/internal/app-binder/services/provideContact.d.ts.map +1 -0
  74. package/lib/types/internal/app-binder/services/provideContact.test.d.ts +2 -0
  75. package/lib/types/internal/app-binder/services/provideContact.test.d.ts.map +1 -0
  76. package/lib/types/internal/app-binder/task/SendRegisterIdentityTask.d.ts.map +1 -1
  77. package/lib/types/internal/app-binder/task/SendRenameContactTask.d.ts.map +1 -1
  78. package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
  79. package/package.json +5 -5
@@ -0,0 +1,2 @@
1
+ import{bufferToHexaString as c,CommandResultFactory as i,CommandResultStatus as s}from"@ledgerhq/device-management-kit";import{ProvideContactCommand as p}from"../../app-binder/command/ProvideContactCommand";import{makeDeviceActionInternalApiMock as r}from"../../app-binder/device-action/__test-utils__/makeInternalApi";import{ContactsCommandError as f}from"../../app-binder/model/contactsErrors";import{buildProvideContactPayload as t,sendProvideContactPayload as d}from"./provideContact";const h=new Uint8Array(20).fill(17),C=new Uint8Array(64).fill(34),u=new Uint8Array(32).fill(51),y=new Uint8Array(32).fill(68),o={contactName:"Alice",scope:"Ethereum",identifier:h,groupHandle:C,hmacProof:u,hmacRest:y,blockchainFamily:"ethereum",chainId:1n},T="010133",x="020101",E="81f005416c696365",P="81f108457468657265756d",_="81f214"+"11".repeat(20),l="81f640"+"22".repeat(64),I="230101",g="510101",A="2920"+"33".repeat(32),b="81f720"+"44".repeat(32);describe("buildProvideContactPayload",()=>{it("encodes every tag in the order the spec lists",()=>{const e=t(o);expect(c(e,!1)).toBe(T+x+E+P+_+l+I+g+A+b)}),it("fits an Ethereum contact in a single 255-byte chunk",()=>{expect(t(o).length).toBeLessThanOrEqual(253)}),it("omits CHAIN_ID for a family that does not carry one",()=>{const e=t({...o,blockchainFamily:"bitcoin",chainId:void 0});expect(c(e,!1)).toContain(l+"510100")}),it("encodes a multi-byte chain id big-endian with no leading zeroes",()=>{const e=t({...o,chainId:8453n});expect(c(e,!1)).toContain("23022105")}),it("accepts the family name case-insensitively",()=>{expect(t({...o,blockchainFamily:"Ethereum"})).toStrictEqual(t(o))}),it("throws on an unknown blockchain family",()=>{expect(()=>t({...o,blockchainFamily:"dogecoin"})).toThrow("Unsupported blockchain family: dogecoin")})});describe("sendProvideContactPayload",()=>{it("sends one chunk under P2=0x00, prefixed with the 2-byte BE total length",async()=>{const e=r();e.sendCommand.mockResolvedValue(i({data:void 0}));const n=t(o),m=await d(e,{payload:n});expect(e.sendCommand).toHaveBeenCalledTimes(1);const a=e.sendCommand.mock.calls[0][0];expect(a).toBeInstanceOf(p),expect(a.args.p2).toBe(0),expect(a.args.data.slice(0,2)).toStrictEqual(Uint8Array.from([n.length>>8&255,n.length&255])),expect(a.args.data.slice(2)).toStrictEqual(n),expect(m.status).toBe(s.Success)}),it("propagates a device rejection instead of throwing",async()=>{const e=r();e.sendCommand.mockResolvedValue(i({error:new f({errorCode:"6982",message:"wrong seed"})}));const n=await d(e,{payload:t(o)});expect(n.status).toBe(s.Error)})});
2
+ //# sourceMappingURL=provideContact.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/internal/app-binder/services/provideContact.test.ts"],
4
+ "sourcesContent": ["import {\n bufferToHexaString,\n CommandResultFactory,\n CommandResultStatus,\n} from \"@ledgerhq/device-management-kit\";\n\nimport { type ProvideContactInput } from \"@api/model/ProvideContact\";\nimport { ProvideContactCommand } from \"@internal/app-binder/command/ProvideContactCommand\";\nimport { makeDeviceActionInternalApiMock } from \"@internal/app-binder/device-action/__test-utils__/makeInternalApi\";\nimport { ContactsCommandError } from \"@internal/app-binder/model/contactsErrors\";\n\nimport {\n buildProvideContactPayload,\n sendProvideContactPayload,\n} from \"./provideContact\";\n\nconst IDENTIFIER = new Uint8Array(20).fill(0x11);\nconst GROUP_HANDLE = new Uint8Array(64).fill(0x22);\nconst HMAC_PROOF = new Uint8Array(32).fill(0x33);\nconst HMAC_REST = new Uint8Array(32).fill(0x44);\n\nconst INPUT: ProvideContactInput = {\n contactName: \"Alice\",\n scope: \"Ethereum\",\n identifier: IDENTIFIER,\n groupHandle: GROUP_HANDLE,\n hmacProof: HMAC_PROOF,\n hmacRest: HMAC_REST,\n blockchainFamily: \"ethereum\",\n chainId: 1n,\n};\n\n// Tags >= 0x80 use the DER long form (0x81 then the tag), hence the \"81\" prefix\n// on CONTACT_NAME (0xf0), SCOPE (0xf1), ACCOUNT_IDENTIFIER (0xf2),\n// GROUP_HANDLE (0xf6) and HMAC_REST (0xf7).\nconst STRUCT_TYPE_TLV = \"010133\";\nconst STRUCT_VERSION_TLV = \"020101\";\nconst CONTACT_NAME_TLV = \"81f005\" + \"416c696365\"; // \"Alice\"\nconst SCOPE_TLV = \"81f108\" + \"457468657265756d\"; // \"Ethereum\"\nconst IDENTIFIER_TLV = \"81f214\" + \"11\".repeat(20);\nconst GROUP_HANDLE_TLV = \"81f640\" + \"22\".repeat(64);\nconst CHAIN_ID_TLV = \"230101\";\nconst FAMILY_TLV = \"510101\";\nconst HMAC_PROOF_TLV = \"2920\" + \"33\".repeat(32);\nconst HMAC_REST_TLV = \"81f720\" + \"44\".repeat(32);\n\ndescribe(\"buildProvideContactPayload\", () => {\n it(\"encodes every tag in the order the spec lists\", () => {\n const payload = buildProvideContactPayload(INPUT);\n\n expect(bufferToHexaString(payload, false)).toBe(\n STRUCT_TYPE_TLV +\n STRUCT_VERSION_TLV +\n CONTACT_NAME_TLV +\n SCOPE_TLV +\n IDENTIFIER_TLV +\n GROUP_HANDLE_TLV +\n CHAIN_ID_TLV +\n FAMILY_TLV +\n HMAC_PROOF_TLV +\n HMAC_REST_TLV,\n );\n });\n\n it(\"fits an Ethereum contact in a single 255-byte chunk\", () => {\n expect(buildProvideContactPayload(INPUT).length).toBeLessThanOrEqual(253);\n });\n\n it(\"omits CHAIN_ID for a family that does not carry one\", () => {\n const payload = buildProvideContactPayload({\n ...INPUT,\n blockchainFamily: \"bitcoin\",\n chainId: undefined,\n });\n\n expect(bufferToHexaString(payload, false)).toContain(\n GROUP_HANDLE_TLV + \"510100\",\n );\n });\n\n it(\"encodes a multi-byte chain id big-endian with no leading zeroes\", () => {\n const payload = buildProvideContactPayload({ ...INPUT, chainId: 8453n });\n\n expect(bufferToHexaString(payload, false)).toContain(\"2302\" + \"2105\");\n });\n\n it(\"accepts the family name case-insensitively\", () => {\n expect(\n buildProvideContactPayload({ ...INPUT, blockchainFamily: \"Ethereum\" }),\n ).toStrictEqual(buildProvideContactPayload(INPUT));\n });\n\n it(\"throws on an unknown blockchain family\", () => {\n expect(() =>\n buildProvideContactPayload({ ...INPUT, blockchainFamily: \"dogecoin\" }),\n ).toThrow(\"Unsupported blockchain family: dogecoin\");\n });\n});\n\ndescribe(\"sendProvideContactPayload\", () => {\n it(\"sends one chunk under P2=0x00, prefixed with the 2-byte BE total length\", async () => {\n const api = makeDeviceActionInternalApiMock();\n api.sendCommand.mockResolvedValue(\n CommandResultFactory({ data: undefined }),\n );\n const payload = buildProvideContactPayload(INPUT);\n\n const result = await sendProvideContactPayload(api, { payload });\n\n expect(api.sendCommand).toHaveBeenCalledTimes(1);\n const command = api.sendCommand.mock.calls[0]![0] as ProvideContactCommand;\n expect(command).toBeInstanceOf(ProvideContactCommand);\n expect(command.args.p2).toBe(0x00);\n expect(command.args.data.slice(0, 2)).toStrictEqual(\n Uint8Array.from([(payload.length >> 8) & 0xff, payload.length & 0xff]),\n );\n expect(command.args.data.slice(2)).toStrictEqual(payload);\n expect(result.status).toBe(CommandResultStatus.Success);\n });\n\n it(\"propagates a device rejection instead of throwing\", async () => {\n const api = makeDeviceActionInternalApiMock();\n api.sendCommand.mockResolvedValue(\n CommandResultFactory({\n error: new ContactsCommandError({\n errorCode: \"6982\",\n message: \"wrong seed\",\n }),\n }),\n );\n\n const result = await sendProvideContactPayload(api, {\n payload: buildProvideContactPayload(INPUT),\n });\n\n expect(result.status).toBe(CommandResultStatus.Error);\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["bufferToHexaString", "CommandResultFactory", "CommandResultStatus", "ProvideContactCommand", "makeDeviceActionInternalApiMock", "ContactsCommandError", "buildProvideContactPayload", "sendProvideContactPayload", "IDENTIFIER", "GROUP_HANDLE", "HMAC_PROOF", "HMAC_REST", "INPUT", "STRUCT_TYPE_TLV", "STRUCT_VERSION_TLV", "CONTACT_NAME_TLV", "SCOPE_TLV", "IDENTIFIER_TLV", "GROUP_HANDLE_TLV", "CHAIN_ID_TLV", "FAMILY_TLV", "HMAC_PROOF_TLV", "HMAC_REST_TLV", "payload", "api", "result", "command"]
7
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/internal/app-binder/task/SendRegisterIdentityTask.ts"],
4
- "sourcesContent": ["// Builds the TLV payload for op 1 (Register Identity / Register External\n// Address) and dispatches it via the chunked-framing scheme shared with the\n// other address-book ops (2-byte BE total length + <=255B chunks). The device\n// returns the 129-byte register response on the final chunk.\n//\n// Reference: Address Book Final Specifications \u2014 Register Identity. Tag order:\n// STRUCT_TYPE, STRUCT_VERSION, CONTACT_NAME, SCOPE, ACCOUNT_IDENTIFIER,\n// CHAIN_ID (Ethereum only), BLOCKCHAIN_FAMILY, then optional\n// GROUP_HANDLE + HMAC_PROOF when extending an existing group.\n//\n// No DERIVATION_PATH: external-address ops carry no path. The current Ethereum\n// app rejects the tag when present (0x6a80) and no longer requires it; the tag\n// is Ledger-Account only (verified on-device, DSDK-1465).\nimport {\n ByteArrayBuilder,\n type CommandResult,\n DmkResultFactory,\n type InternalApi,\n InvalidStatusWordError,\n isSuccessCommandResult,\n type LoggerPublisherService,\n} from \"@ledgerhq/device-management-kit\";\n\nimport { type ExistingContactGroup } from \"@api/model/RegisterExternalAddress\";\nimport { RegisterIdentityCommand } from \"@internal/app-binder/command/RegisterIdentityCommand\";\nimport {\n BLOCKCHAIN_FAMILY_BY_NAME,\n SUB_CMD_REGISTER_IDENTITY,\n} from \"@internal/app-binder/model/contactsConstants\";\nimport { type ContactsErrorCodes } from \"@internal/app-binder/model/contactsErrors\";\nimport {\n CONTACTS_TLV_TAG,\n encodeTlvAscii,\n encodeTlvBuffer,\n encodeTlvChainId,\n encodeTlvUInt8,\n STRUCT_TYPE_REGISTER_IDENTITY,\n STRUCT_VERSION_VALUE,\n} from \"@internal/app-binder/services/contactsTlvSerializer\";\nimport { sendFramedContactsPayload } from \"@internal/app-binder/services/sendFramedContactsPayload\";\n\nexport type RegisterIdentityProofs = {\n readonly groupHandle: Uint8Array;\n readonly hmacProof: Uint8Array;\n readonly hmacRest: Uint8Array;\n};\n\nexport type SendRegisterIdentityTaskArgs = {\n readonly contactName: string;\n readonly scope: string;\n readonly identifier: Uint8Array;\n readonly blockchainFamily: string;\n readonly chainId?: bigint;\n readonly existingContactGroup?: ExistingContactGroup;\n readonly logger?: LoggerPublisherService;\n};\n\nexport class SendRegisterIdentityTask {\n constructor(\n private readonly api: InternalApi,\n private readonly args: SendRegisterIdentityTaskArgs,\n ) {}\n\n async run(): Promise<\n CommandResult<RegisterIdentityProofs, ContactsErrorCodes>\n > {\n const payload = this.buildPayload(this.args);\n\n const result = (await sendFramedContactsPayload(this.api, {\n payload,\n p1: SUB_CMD_REGISTER_IDENTITY,\n makeCommand: (chunk, p2) =>\n new RegisterIdentityCommand({ data: chunk, p2 }),\n logger: this.args.logger,\n commandTag: \"SendRegisterIdentityTask\",\n })) as CommandResult<\n {\n readonly groupHandle?: Uint8Array;\n readonly hmacProof?: Uint8Array;\n readonly hmacRest?: Uint8Array;\n },\n ContactsErrorCodes\n >;\n\n if (!isSuccessCommandResult(result)) {\n return result;\n }\n\n const { groupHandle, hmacProof, hmacRest } = result.data;\n if (!groupHandle || !hmacProof || !hmacRest) {\n return DmkResultFactory({\n error: new InvalidStatusWordError(\n \"RegisterIdentity final-chunk response was incomplete\",\n ),\n });\n }\n return DmkResultFactory({ data: { groupHandle, hmacProof, hmacRest } });\n }\n\n private buildPayload(args: SendRegisterIdentityTaskArgs): Uint8Array {\n const family =\n BLOCKCHAIN_FAMILY_BY_NAME[args.blockchainFamily.toLowerCase()];\n if (family === undefined) {\n throw new Error(\n `Unsupported blockchain family: ${args.blockchainFamily}`,\n );\n }\n\n const builder = new ByteArrayBuilder();\n encodeTlvUInt8(\n builder,\n CONTACTS_TLV_TAG.STRUCT_TYPE,\n STRUCT_TYPE_REGISTER_IDENTITY,\n );\n encodeTlvUInt8(\n builder,\n CONTACTS_TLV_TAG.STRUCT_VERSION,\n STRUCT_VERSION_VALUE,\n );\n encodeTlvAscii(builder, CONTACTS_TLV_TAG.CONTACT_NAME, args.contactName);\n encodeTlvAscii(builder, CONTACTS_TLV_TAG.SCOPE, args.scope);\n encodeTlvBuffer(\n builder,\n CONTACTS_TLV_TAG.ACCOUNT_IDENTIFIER,\n args.identifier,\n );\n if (args.chainId !== undefined) {\n encodeTlvChainId(builder, CONTACTS_TLV_TAG.CHAIN_ID, args.chainId);\n }\n encodeTlvUInt8(builder, CONTACTS_TLV_TAG.BLOCKCHAIN_FAMILY, family);\n\n if (args.existingContactGroup) {\n encodeTlvBuffer(\n builder,\n CONTACTS_TLV_TAG.GROUP_HANDLE,\n args.existingContactGroup.groupHandle,\n );\n encodeTlvBuffer(\n builder,\n CONTACTS_TLV_TAG.HMAC_PROOF,\n args.existingContactGroup.hmacProof,\n );\n }\n\n return builder.build();\n }\n}\n"],
5
- "mappings": "AAaA,OACE,oBAAAA,EAEA,oBAAAC,EAEA,0BAAAC,EACA,0BAAAC,MAEK,kCAGP,OAAS,2BAAAC,MAA+B,uDACxC,OACE,6BAAAC,EACA,6BAAAC,MACK,+CAEP,OACE,oBAAAC,EACA,kBAAAC,EACA,mBAAAC,EACA,oBAAAC,EACA,kBAAAC,EACA,iCAAAC,EACA,wBAAAC,MACK,sDACP,OAAS,6BAAAC,MAAiC,0DAkBnC,MAAMC,CAAyB,CACpC,YACmBC,EACAC,EACjB,CAFiB,SAAAD,EACA,UAAAC,CAChB,CAEH,MAAM,KAEJ,CACA,MAAMC,EAAU,KAAK,aAAa,KAAK,IAAI,EAErCC,EAAU,MAAML,EAA0B,KAAK,IAAK,CACxD,QAAAI,EACA,GAAIZ,EACJ,YAAa,CAACc,EAAOC,IACnB,IAAIjB,EAAwB,CAAE,KAAMgB,EAAO,GAAAC,CAAG,CAAC,EACjD,OAAQ,KAAK,KAAK,OAClB,WAAY,0BACd,CAAC,EASD,GAAI,CAAClB,EAAuBgB,CAAM,EAChC,OAAOA,EAGT,KAAM,CAAE,YAAAG,EAAa,UAAAC,EAAW,SAAAC,CAAS,EAAIL,EAAO,KACpD,MAAI,CAACG,GAAe,CAACC,GAAa,CAACC,EAC1BvB,EAAiB,CACtB,MAAO,IAAIC,EACT,sDACF,CACF,CAAC,EAEID,EAAiB,CAAE,KAAM,CAAE,YAAAqB,EAAa,UAAAC,EAAW,SAAAC,CAAS,CAAE,CAAC,CACxE,CAEQ,aAAaP,EAAgD,CACnE,MAAMQ,EACJpB,EAA0BY,EAAK,iBAAiB,YAAY,CAAC,EAC/D,GAAIQ,IAAW,OACb,MAAM,IAAI,MACR,kCAAkCR,EAAK,gBAAgB,EACzD,EAGF,MAAMS,EAAU,IAAI1B,EACpB,OAAAW,EACEe,EACAnB,EAAiB,YACjBK,CACF,EACAD,EACEe,EACAnB,EAAiB,eACjBM,CACF,EACAL,EAAekB,EAASnB,EAAiB,aAAcU,EAAK,WAAW,EACvET,EAAekB,EAASnB,EAAiB,MAAOU,EAAK,KAAK,EAC1DR,EACEiB,EACAnB,EAAiB,mBACjBU,EAAK,UACP,EACIA,EAAK,UAAY,QACnBP,EAAiBgB,EAASnB,EAAiB,SAAUU,EAAK,OAAO,EAEnEN,EAAee,EAASnB,EAAiB,kBAAmBkB,CAAM,EAE9DR,EAAK,uBACPR,EACEiB,EACAnB,EAAiB,aACjBU,EAAK,qBAAqB,WAC5B,EACAR,EACEiB,EACAnB,EAAiB,WACjBU,EAAK,qBAAqB,SAC5B,GAGKS,EAAQ,MAAM,CACvB,CACF",
4
+ "sourcesContent": ["// Builds the TLV payload for op 1 (Register Identity / Register External\n// Address) and dispatches it via the chunked-framing scheme shared with the\n// other address-book ops (2-byte BE total length + <=255B chunks). The device\n// returns the 129-byte register response on the final chunk.\n//\n// Reference: Address Book Final Specifications \u2014 Register Identity. Tag order:\n// STRUCT_TYPE, STRUCT_VERSION, CONTACT_NAME, SCOPE, ACCOUNT_IDENTIFIER,\n// CHAIN_ID (Ethereum only), BLOCKCHAIN_FAMILY, then optional GROUP_HANDLE +\n// HMAC_PROOF when extending an existing group.\n//\n// No DERIVATION_PATH. The tag's status changed three times in the BOLOS SDK,\n// and the address-book TLV parser is compiled into the app (app_features/), so\n// the SDK revision the app was *built* against decides \u2014 not the app version,\n// which reads 1.23.0-dev either way:\n// - before 2026-08-07: tag 0x69 present and MANDATORY, omitting it -> 0x6a80\n// - 8e7e7a4f (2026-08-07): made optional, both forms accepted\n// - a0bb21f5 (2026-08-10): removed, sending it -> 0x6a80 (unknown tag)\n// Omitting it is therefore correct for any app built from 2026-08-07 onward,\n// and wrong for one built before. Both ends verified on hardware: a Flex\n// running a pre-08-07 build of app-ethereum a79f9f8f rejects the payload\n// without the tag in 9ms and no review screen; Speculos running a post-08-10\n// build of the same commit rejects it *with* the tag, the same way.\n// The OS derives the HMAC key from a fixed internal path.\nimport {\n ByteArrayBuilder,\n type CommandResult,\n DmkResultFactory,\n type InternalApi,\n InvalidStatusWordError,\n isSuccessCommandResult,\n type LoggerPublisherService,\n} from \"@ledgerhq/device-management-kit\";\n\nimport { type ExistingContactGroup } from \"@api/model/RegisterExternalAddress\";\nimport { RegisterIdentityCommand } from \"@internal/app-binder/command/RegisterIdentityCommand\";\nimport {\n BLOCKCHAIN_FAMILY_BY_NAME,\n SUB_CMD_REGISTER_IDENTITY,\n} from \"@internal/app-binder/model/contactsConstants\";\nimport { type ContactsErrorCodes } from \"@internal/app-binder/model/contactsErrors\";\nimport {\n CONTACTS_TLV_TAG,\n encodeTlvAscii,\n encodeTlvBuffer,\n encodeTlvChainId,\n encodeTlvUInt8,\n STRUCT_TYPE_REGISTER_IDENTITY,\n STRUCT_VERSION_VALUE,\n} from \"@internal/app-binder/services/contactsTlvSerializer\";\nimport { sendFramedContactsPayload } from \"@internal/app-binder/services/sendFramedContactsPayload\";\n\nexport type RegisterIdentityProofs = {\n readonly groupHandle: Uint8Array;\n readonly hmacProof: Uint8Array;\n readonly hmacRest: Uint8Array;\n};\n\nexport type SendRegisterIdentityTaskArgs = {\n readonly contactName: string;\n readonly scope: string;\n readonly identifier: Uint8Array;\n readonly blockchainFamily: string;\n readonly chainId?: bigint;\n readonly existingContactGroup?: ExistingContactGroup;\n readonly logger?: LoggerPublisherService;\n};\n\nexport class SendRegisterIdentityTask {\n constructor(\n private readonly api: InternalApi,\n private readonly args: SendRegisterIdentityTaskArgs,\n ) {}\n\n async run(): Promise<\n CommandResult<RegisterIdentityProofs, ContactsErrorCodes>\n > {\n const payload = this.buildPayload(this.args);\n\n const result = (await sendFramedContactsPayload(this.api, {\n payload,\n p1: SUB_CMD_REGISTER_IDENTITY,\n makeCommand: (chunk, p2) =>\n new RegisterIdentityCommand({ data: chunk, p2 }),\n logger: this.args.logger,\n commandTag: \"SendRegisterIdentityTask\",\n })) as CommandResult<\n {\n readonly groupHandle?: Uint8Array;\n readonly hmacProof?: Uint8Array;\n readonly hmacRest?: Uint8Array;\n },\n ContactsErrorCodes\n >;\n\n if (!isSuccessCommandResult(result)) {\n return result;\n }\n\n const { groupHandle, hmacProof, hmacRest } = result.data;\n if (!groupHandle || !hmacProof || !hmacRest) {\n return DmkResultFactory({\n error: new InvalidStatusWordError(\n \"RegisterIdentity final-chunk response was incomplete\",\n ),\n });\n }\n return DmkResultFactory({ data: { groupHandle, hmacProof, hmacRest } });\n }\n\n private buildPayload(args: SendRegisterIdentityTaskArgs): Uint8Array {\n const family =\n BLOCKCHAIN_FAMILY_BY_NAME[args.blockchainFamily.toLowerCase()];\n if (family === undefined) {\n throw new Error(\n `Unsupported blockchain family: ${args.blockchainFamily}`,\n );\n }\n\n const builder = new ByteArrayBuilder();\n encodeTlvUInt8(\n builder,\n CONTACTS_TLV_TAG.STRUCT_TYPE,\n STRUCT_TYPE_REGISTER_IDENTITY,\n );\n encodeTlvUInt8(\n builder,\n CONTACTS_TLV_TAG.STRUCT_VERSION,\n STRUCT_VERSION_VALUE,\n );\n encodeTlvAscii(builder, CONTACTS_TLV_TAG.CONTACT_NAME, args.contactName);\n encodeTlvAscii(builder, CONTACTS_TLV_TAG.SCOPE, args.scope);\n encodeTlvBuffer(\n builder,\n CONTACTS_TLV_TAG.ACCOUNT_IDENTIFIER,\n args.identifier,\n );\n if (args.chainId !== undefined) {\n encodeTlvChainId(builder, CONTACTS_TLV_TAG.CHAIN_ID, args.chainId);\n }\n encodeTlvUInt8(builder, CONTACTS_TLV_TAG.BLOCKCHAIN_FAMILY, family);\n\n if (args.existingContactGroup) {\n encodeTlvBuffer(\n builder,\n CONTACTS_TLV_TAG.GROUP_HANDLE,\n args.existingContactGroup.groupHandle,\n );\n encodeTlvBuffer(\n builder,\n CONTACTS_TLV_TAG.HMAC_PROOF,\n args.existingContactGroup.hmacProof,\n );\n }\n\n return builder.build();\n }\n}\n"],
5
+ "mappings": "AAuBA,OACE,oBAAAA,EAEA,oBAAAC,EAEA,0BAAAC,EACA,0BAAAC,MAEK,kCAGP,OAAS,2BAAAC,MAA+B,uDACxC,OACE,6BAAAC,EACA,6BAAAC,MACK,+CAEP,OACE,oBAAAC,EACA,kBAAAC,EACA,mBAAAC,EACA,oBAAAC,EACA,kBAAAC,EACA,iCAAAC,EACA,wBAAAC,MACK,sDACP,OAAS,6BAAAC,MAAiC,0DAkBnC,MAAMC,CAAyB,CACpC,YACmBC,EACAC,EACjB,CAFiB,SAAAD,EACA,UAAAC,CAChB,CAEH,MAAM,KAEJ,CACA,MAAMC,EAAU,KAAK,aAAa,KAAK,IAAI,EAErCC,EAAU,MAAML,EAA0B,KAAK,IAAK,CACxD,QAAAI,EACA,GAAIZ,EACJ,YAAa,CAACc,EAAOC,IACnB,IAAIjB,EAAwB,CAAE,KAAMgB,EAAO,GAAAC,CAAG,CAAC,EACjD,OAAQ,KAAK,KAAK,OAClB,WAAY,0BACd,CAAC,EASD,GAAI,CAAClB,EAAuBgB,CAAM,EAChC,OAAOA,EAGT,KAAM,CAAE,YAAAG,EAAa,UAAAC,EAAW,SAAAC,CAAS,EAAIL,EAAO,KACpD,MAAI,CAACG,GAAe,CAACC,GAAa,CAACC,EAC1BvB,EAAiB,CACtB,MAAO,IAAIC,EACT,sDACF,CACF,CAAC,EAEID,EAAiB,CAAE,KAAM,CAAE,YAAAqB,EAAa,UAAAC,EAAW,SAAAC,CAAS,CAAE,CAAC,CACxE,CAEQ,aAAaP,EAAgD,CACnE,MAAMQ,EACJpB,EAA0BY,EAAK,iBAAiB,YAAY,CAAC,EAC/D,GAAIQ,IAAW,OACb,MAAM,IAAI,MACR,kCAAkCR,EAAK,gBAAgB,EACzD,EAGF,MAAMS,EAAU,IAAI1B,EACpB,OAAAW,EACEe,EACAnB,EAAiB,YACjBK,CACF,EACAD,EACEe,EACAnB,EAAiB,eACjBM,CACF,EACAL,EAAekB,EAASnB,EAAiB,aAAcU,EAAK,WAAW,EACvET,EAAekB,EAASnB,EAAiB,MAAOU,EAAK,KAAK,EAC1DR,EACEiB,EACAnB,EAAiB,mBACjBU,EAAK,UACP,EACIA,EAAK,UAAY,QACnBP,EAAiBgB,EAASnB,EAAiB,SAAUU,EAAK,OAAO,EAEnEN,EAAee,EAASnB,EAAiB,kBAAmBkB,CAAM,EAE9DR,EAAK,uBACPR,EACEiB,EACAnB,EAAiB,aACjBU,EAAK,qBAAqB,WAC5B,EACAR,EACEiB,EACAnB,EAAiB,WACjBU,EAAK,qBAAqB,SAC5B,GAGKS,EAAQ,MAAM,CACvB,CACF",
6
6
  "names": ["ByteArrayBuilder", "DmkResultFactory", "InvalidStatusWordError", "isSuccessCommandResult", "RegisterIdentityCommand", "BLOCKCHAIN_FAMILY_BY_NAME", "SUB_CMD_REGISTER_IDENTITY", "CONTACTS_TLV_TAG", "encodeTlvAscii", "encodeTlvBuffer", "encodeTlvChainId", "encodeTlvUInt8", "STRUCT_TYPE_REGISTER_IDENTITY", "STRUCT_VERSION_VALUE", "sendFramedContactsPayload", "SendRegisterIdentityTask", "api", "args", "payload", "result", "chunk", "p2", "groupHandle", "hmacProof", "hmacRest", "family", "builder"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/internal/app-binder/task/SendRegisterIdentityTask.test.ts"],
4
- "sourcesContent": ["// Validates that the TLV serializer + tag ordering + chunk framing produce the\n// expected wire bytes for op 1 (Register Identity): tags >= 0x80 use the 2-byte\n// DER form [0x81, tag], and Register Identity carries NO DERIVATION_PATH (the\n// Ethereum app rejects the tag on external-address ops). The framed chunk =\n// 2-byte BE total length + TLV.\nimport {\n CommandResultFactory,\n type InternalApi,\n InvalidStatusWordError,\n} from \"@ledgerhq/device-management-kit\";\n\nimport { RegisterIdentityCommand } from \"@internal/app-binder/command/RegisterIdentityCommand\";\n\nimport { SendRegisterIdentityTask } from \"./SendRegisterIdentityTask\";\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n out[i / 2] = parseInt(hex.slice(i, i + 2), 16);\n }\n return out;\n}\n\n// Framed chunk = \"00 <Lc>\" (2-byte BE total TLV length) + TLV. Tag order:\n// STRUCT_TYPE, STRUCT_VERSION, CONTACT_NAME, SCOPE, ACCOUNT_IDENTIFIER,\n// CHAIN_ID, BLOCKCHAIN_FAMILY, then optional GROUP_HANDLE + HMAC_PROOF. Tags\n// >= 0x80 are encoded as [0x81, tag]. No DERIVATION_PATH TLV.\nconst FRESH_FRAMED_CHUNK = hexToBytes(\n \"003601012d020101\" +\n \"81f005416c696365\" +\n \"81f108457468206d61696e\" +\n \"81f21400000000000000000000000000000000deadbeef\" +\n \"230101510101\",\n);\n\nconst EXTENSION_FRAMED_CHUNK = hexToBytes(\n \"009c01012d020101\" +\n \"81f005416c696365\" +\n \"81f108417262206d61696e\" +\n \"81f2144444444444444444444444444444444444444444\" +\n \"2302a4b1510101\" +\n \"81f640cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\" +\n \"2920dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\n);\n\nconst OK_PROOFS = {\n groupHandle: hexToBytes(\"cc\".repeat(64)),\n hmacProof: hexToBytes(\"dd\".repeat(32)),\n hmacRest: hexToBytes(\"aa\".repeat(32)),\n};\n\nfunction makeApiMock(): InternalApi & {\n sendCommand: ReturnType<typeof vi.fn>;\n} {\n return { sendCommand: vi.fn() } as unknown as InternalApi & {\n sendCommand: ReturnType<typeof vi.fn>;\n };\n}\n\ndescribe(\"SendRegisterIdentityTask\", () => {\n it(\"assembles a fresh-register framed chunk and dispatches RegisterIdentityCommand\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({ data: OK_PROOFS }),\n );\n\n const result = await new SendRegisterIdentityTask(api, {\n contactName: \"Alice\",\n scope: \"Eth main\",\n identifier: hexToBytes(\"00000000000000000000000000000000deadbeef\"),\n blockchainFamily: \"ethereum\",\n chainId: 1n,\n }).run();\n\n expect(api.sendCommand.mock.calls).toHaveLength(1);\n expect(api.sendCommand.mock.calls[0]![0]).toStrictEqual(\n new RegisterIdentityCommand({ data: FRESH_FRAMED_CHUNK, p2: 0x00 }),\n );\n expect(result).toStrictEqual(CommandResultFactory({ data: OK_PROOFS }));\n });\n\n it(\"appends GROUP_HANDLE + HMAC_PROOF on extension and frames the extension payload\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({ data: OK_PROOFS }),\n );\n\n await new SendRegisterIdentityTask(api, {\n contactName: \"Alice\",\n scope: \"Arb main\",\n identifier: hexToBytes(\"44\".repeat(20)),\n blockchainFamily: \"ethereum\",\n chainId: 42161n,\n existingContactGroup: {\n groupHandle: hexToBytes(\"cc\".repeat(64)),\n hmacProof: hexToBytes(\"dd\".repeat(32)),\n },\n }).run();\n\n expect(api.sendCommand.mock.calls).toHaveLength(1);\n expect(api.sendCommand.mock.calls[0]![0]).toStrictEqual(\n new RegisterIdentityCommand({ data: EXTENSION_FRAMED_CHUNK, p2: 0x00 }),\n );\n });\n\n it(\"omits the CHAIN_ID TLV when chainId is not provided\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({ data: OK_PROOFS }),\n );\n\n await new SendRegisterIdentityTask(api, {\n contactName: \"Alice\",\n scope: \"Eth main\",\n identifier: hexToBytes(\"00000000000000000000000000000000deadbeef\"),\n blockchainFamily: \"ethereum\",\n }).run();\n\n const command = api.sendCommand.mock\n .calls[0]![0] as RegisterIdentityCommand;\n // No `23 01 ..` CHAIN_ID TLV; BLOCKCHAIN_FAMILY 51 01 01 follows the path.\n const framedHex = Buffer.from(command.args.data).toString(\"hex\");\n expect(framedHex).not.toContain(\"230101\");\n expect(framedHex.endsWith(\"510101\")).toBe(true);\n });\n\n it(\"propagates command-level errors\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({\n error: new InvalidStatusWordError(\"user cancelled\"),\n }),\n );\n\n const result = await new SendRegisterIdentityTask(api, {\n contactName: \"Alice\",\n scope: \"Eth main\",\n identifier: hexToBytes(\"00000000000000000000000000000000deadbeef\"),\n blockchainFamily: \"ethereum\",\n chainId: 1n,\n }).run();\n\n expect(result).toStrictEqual(\n CommandResultFactory({\n error: new InvalidStatusWordError(\"user cancelled\"),\n }),\n );\n });\n\n it(\"throws on an unsupported blockchain family\", async () => {\n const api = makeApiMock();\n\n await expect(\n new SendRegisterIdentityTask(api, {\n contactName: \"Alice\",\n scope: \"Eth main\",\n identifier: hexToBytes(\"00000000000000000000000000000000deadbeef\"),\n blockchainFamily: \"dogecoin\",\n chainId: 1n,\n }).run(),\n ).rejects.toThrow(/Unsupported blockchain family/);\n expect(api.sendCommand).not.toHaveBeenCalled();\n });\n});\n"],
4
+ "sourcesContent": ["// Validates that the TLV serializer + tag ordering + chunk framing produce the\n// expected wire bytes for op 1 (Register Identity): tags >= 0x80 use the 2-byte\n// DER form [0x81, tag], and no DERIVATION_PATH is sent (the SDK removed tag\n// 0x69 from REGISTER_IDENTITY_TAGS on 2026-08-10 and rejects payloads carrying\n// it). The framed chunk = 2-byte BE total length + TLV.\nimport {\n CommandResultFactory,\n type InternalApi,\n InvalidStatusWordError,\n} from \"@ledgerhq/device-management-kit\";\n\nimport { RegisterIdentityCommand } from \"@internal/app-binder/command/RegisterIdentityCommand\";\n\nimport { SendRegisterIdentityTask } from \"./SendRegisterIdentityTask\";\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n out[i / 2] = parseInt(hex.slice(i, i + 2), 16);\n }\n return out;\n}\n\n// Framed chunk = \"00 <Lc>\" (2-byte BE total TLV length) + TLV. Tag order:\n// STRUCT_TYPE, STRUCT_VERSION, CONTACT_NAME, SCOPE, ACCOUNT_IDENTIFIER,\n// CHAIN_ID, BLOCKCHAIN_FAMILY, then optional GROUP_HANDLE + HMAC_PROOF. Tags\n// >= 0x80 are encoded as [0x81, tag].\nconst FRESH_FRAMED_CHUNK = hexToBytes(\n \"003601012d020101\" +\n \"81f005416c696365\" +\n \"81f108457468206d61696e\" +\n \"81f21400000000000000000000000000000000deadbeef\" +\n \"230101510101\",\n);\n\nconst EXTENSION_FRAMED_CHUNK = hexToBytes(\n \"009c01012d020101\" +\n \"81f005416c696365\" +\n \"81f108417262206d61696e\" +\n \"81f2144444444444444444444444444444444444444444\" +\n \"2302a4b1510101\" +\n \"81f640cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\" +\n \"2920dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\n);\n\nconst OK_PROOFS = {\n groupHandle: hexToBytes(\"cc\".repeat(64)),\n hmacProof: hexToBytes(\"dd\".repeat(32)),\n hmacRest: hexToBytes(\"aa\".repeat(32)),\n};\n\nfunction makeApiMock(): InternalApi & {\n sendCommand: ReturnType<typeof vi.fn>;\n} {\n return { sendCommand: vi.fn() } as unknown as InternalApi & {\n sendCommand: ReturnType<typeof vi.fn>;\n };\n}\n\ndescribe(\"SendRegisterIdentityTask\", () => {\n it(\"assembles a fresh-register framed chunk and dispatches RegisterIdentityCommand\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({ data: OK_PROOFS }),\n );\n\n const result = await new SendRegisterIdentityTask(api, {\n contactName: \"Alice\",\n scope: \"Eth main\",\n identifier: hexToBytes(\"00000000000000000000000000000000deadbeef\"),\n blockchainFamily: \"ethereum\",\n chainId: 1n,\n }).run();\n\n expect(api.sendCommand.mock.calls).toHaveLength(1);\n expect(api.sendCommand.mock.calls[0]![0]).toStrictEqual(\n new RegisterIdentityCommand({ data: FRESH_FRAMED_CHUNK, p2: 0x00 }),\n );\n expect(result).toStrictEqual(CommandResultFactory({ data: OK_PROOFS }));\n });\n\n it(\"appends GROUP_HANDLE + HMAC_PROOF on extension and frames the extension payload\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({ data: OK_PROOFS }),\n );\n\n await new SendRegisterIdentityTask(api, {\n contactName: \"Alice\",\n scope: \"Arb main\",\n identifier: hexToBytes(\"44\".repeat(20)),\n blockchainFamily: \"ethereum\",\n chainId: 42161n,\n existingContactGroup: {\n groupHandle: hexToBytes(\"cc\".repeat(64)),\n hmacProof: hexToBytes(\"dd\".repeat(32)),\n },\n }).run();\n\n expect(api.sendCommand.mock.calls).toHaveLength(1);\n expect(api.sendCommand.mock.calls[0]![0]).toStrictEqual(\n new RegisterIdentityCommand({ data: EXTENSION_FRAMED_CHUNK, p2: 0x00 }),\n );\n });\n\n it(\"omits the CHAIN_ID TLV when chainId is not provided\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({ data: OK_PROOFS }),\n );\n\n await new SendRegisterIdentityTask(api, {\n contactName: \"Alice\",\n scope: \"Eth main\",\n identifier: hexToBytes(\"00000000000000000000000000000000deadbeef\"),\n blockchainFamily: \"ethereum\",\n }).run();\n\n const command = api.sendCommand.mock\n .calls[0]![0] as RegisterIdentityCommand;\n // No `23 01 ..` CHAIN_ID TLV; BLOCKCHAIN_FAMILY 51 01 01 follows the path.\n const framedHex = Buffer.from(command.args.data).toString(\"hex\");\n expect(framedHex).not.toContain(\"230101\");\n expect(framedHex.endsWith(\"510101\")).toBe(true);\n });\n\n it(\"propagates command-level errors\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({\n error: new InvalidStatusWordError(\"user cancelled\"),\n }),\n );\n\n const result = await new SendRegisterIdentityTask(api, {\n contactName: \"Alice\",\n scope: \"Eth main\",\n identifier: hexToBytes(\"00000000000000000000000000000000deadbeef\"),\n blockchainFamily: \"ethereum\",\n chainId: 1n,\n }).run();\n\n expect(result).toStrictEqual(\n CommandResultFactory({\n error: new InvalidStatusWordError(\"user cancelled\"),\n }),\n );\n });\n\n it(\"throws on an unsupported blockchain family\", async () => {\n const api = makeApiMock();\n\n await expect(\n new SendRegisterIdentityTask(api, {\n contactName: \"Alice\",\n scope: \"Eth main\",\n identifier: hexToBytes(\"00000000000000000000000000000000deadbeef\"),\n blockchainFamily: \"dogecoin\",\n chainId: 1n,\n }).run(),\n ).rejects.toThrow(/Unsupported blockchain family/);\n expect(api.sendCommand).not.toHaveBeenCalled();\n });\n});\n"],
5
5
  "mappings": "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",
6
6
  "names": ["CommandResultFactory", "InvalidStatusWordError", "RegisterIdentityCommand", "SendRegisterIdentityTask", "hexToBytes", "hex", "out", "i", "FRESH_FRAMED_CHUNK", "EXTENSION_FRAMED_CHUNK", "OK_PROOFS", "makeApiMock", "api", "result", "command", "framedHex"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{ByteArrayBuilder as c,DmkResultFactory as n,InvalidStatusWordError as C,isSuccessCommandResult as l}from"@ledgerhq/device-management-kit";import{RenameContactCommand as T}from"../../app-binder/command/RenameContactCommand";import{RENAME_CONTACT_P1 as p}from"../../app-binder/model/contactsConstants";import{CONTACTS_TLV_TAG as e,encodeTlvAscii as m,encodeTlvBuffer as o,encodeTlvUInt8 as s,packDerivationPath as y,STRUCT_TYPE_EDIT_CONTACT_NAME as u,STRUCT_VERSION_VALUE as A}from"../../app-binder/services/contactsTlvSerializer";import{sendFramedContactsPayload as R}from"../../app-binder/services/sendFramedContactsPayload";class S{constructor(a,r){this.api=a;this.args=r}async run(){const a=this.buildPayload(this.args),r=await R(this.api,{payload:a,p1:p,makeCommand:(d,i)=>new T({data:d,p2:i}),logger:this.args.logger,commandTag:"SendRenameContactTask"});if(!l(r))return r;const{hmacProof:t}=r.data;return t?n({data:{hmacProof:t}}):n({error:new C("RenameContact final-chunk response did not carry hmac_name")})}buildPayload(a){const r=new c;return s(r,e.STRUCT_TYPE,u),s(r,e.STRUCT_VERSION,A),m(r,e.CONTACT_NAME,a.newContactName),m(r,e.PREVIOUS_CONTACT_NAME,a.previousContactName),o(r,e.GROUP_HANDLE,a.groupHandle),o(r,e.DERIVATION_PATH,y([2147483692,2147483708,2147483648,0,0])),o(r,e.HMAC_PROOF,a.hmacProof),r.build()}}export{S as SendRenameContactTask};
1
+ import{ByteArrayBuilder as c,DmkResultFactory as t,InvalidStatusWordError as C,isSuccessCommandResult as l}from"@ledgerhq/device-management-kit";import{RenameContactCommand as T}from"../../app-binder/command/RenameContactCommand";import{RENAME_CONTACT_P1 as p}from"../../app-binder/model/contactsConstants";import{CONTACTS_TLV_TAG as a,encodeTlvAscii as n,encodeTlvBuffer as m,encodeTlvUInt8 as s,STRUCT_TYPE_EDIT_CONTACT_NAME as y,STRUCT_VERSION_VALUE as u}from"../../app-binder/services/contactsTlvSerializer";import{sendFramedContactsPayload as A}from"../../app-binder/services/sendFramedContactsPayload";class S{constructor(e,r){this.api=e;this.args=r}async run(){const e=this.buildPayload(this.args),r=await A(this.api,{payload:e,p1:p,makeCommand:(d,i)=>new T({data:d,p2:i}),logger:this.args.logger,commandTag:"SendRenameContactTask"});if(!l(r))return r;const{hmacProof:o}=r.data;return o?t({data:{hmacProof:o}}):t({error:new C("RenameContact final-chunk response did not carry hmac_name")})}buildPayload(e){const r=new c;return s(r,a.STRUCT_TYPE,y),s(r,a.STRUCT_VERSION,u),n(r,a.CONTACT_NAME,e.newContactName),n(r,a.PREVIOUS_CONTACT_NAME,e.previousContactName),m(r,a.GROUP_HANDLE,e.groupHandle),m(r,a.HMAC_PROOF,e.hmacProof),r.build()}}export{S as SendRenameContactTask};
2
2
  //# sourceMappingURL=SendRenameContactTask.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/internal/app-binder/task/SendRenameContactTask.ts"],
4
- "sourcesContent": ["// Builds the TLV payload for the Rename Contact (EDIT CONTACT NAME) op and\n// dispatches it via the chunked-framing scheme shared with the other\n// address-book ops (2-byte BE total length + <=255B chunks). Rename is an\n// OS/dashboard command (CLA 0xE0 / INS 0x2E) \u2014 the caller runs this task after\n// navigating to the dashboard (GoToDashboardDeviceAction), never inside an app.\n//\n// The device rotates the contact-level hmac_name and returns the fresh proof on\n// the final chunk; per-entry hmac_rest values are untouched (they bind\n// gid|scope|id|family|chain_id, never the name), so a single APDU regardless of\n// the contact's entry count.\n//\n// Reference: Address Book Final Specifications \u2014 Edit Contact Name. Tag order:\n// STRUCT_TYPE, STRUCT_VERSION, CONTACT_NAME (new), PREVIOUS_CONTACT_NAME\n// (old), GROUP_HANDLE, DERIVATION_PATH, HMAC_PROOF.\nimport {\n ByteArrayBuilder,\n type CommandResult,\n DmkResultFactory,\n type InternalApi,\n InvalidStatusWordError,\n isSuccessCommandResult,\n type LoggerPublisherService,\n} from \"@ledgerhq/device-management-kit\";\n\nimport { RenameContactCommand } from \"@internal/app-binder/command/RenameContactCommand\";\nimport { RENAME_CONTACT_P1 } from \"@internal/app-binder/model/contactsConstants\";\nimport { type ContactsErrorCodes } from \"@internal/app-binder/model/contactsErrors\";\nimport {\n CONTACTS_TLV_TAG,\n encodeTlvAscii,\n encodeTlvBuffer,\n encodeTlvUInt8,\n packDerivationPath,\n STRUCT_TYPE_EDIT_CONTACT_NAME,\n STRUCT_VERSION_VALUE,\n} from \"@internal/app-binder/services/contactsTlvSerializer\";\nimport { sendFramedContactsPayload } from \"@internal/app-binder/services/sendFramedContactsPayload\";\n\nexport type RenameContactProof = {\n readonly hmacProof: Uint8Array;\n};\n\nexport type SendRenameContactTaskArgs = {\n readonly previousContactName: string;\n readonly newContactName: string;\n readonly groupHandle: Uint8Array;\n readonly hmacProof: Uint8Array;\n readonly logger?: LoggerPublisherService;\n};\n\nexport class SendRenameContactTask {\n constructor(\n private readonly api: InternalApi,\n private readonly args: SendRenameContactTaskArgs,\n ) {}\n\n async run(): Promise<CommandResult<RenameContactProof, ContactsErrorCodes>> {\n const payload = this.buildPayload(this.args);\n\n const result = (await sendFramedContactsPayload(this.api, {\n payload,\n p1: RENAME_CONTACT_P1,\n makeCommand: (chunk, p2) => new RenameContactCommand({ data: chunk, p2 }),\n logger: this.args.logger,\n commandTag: \"SendRenameContactTask\",\n })) as CommandResult<\n { readonly hmacProof?: Uint8Array },\n ContactsErrorCodes\n >;\n\n if (!isSuccessCommandResult(result)) {\n return result;\n }\n\n const { hmacProof } = result.data;\n if (!hmacProof) {\n return DmkResultFactory({\n error: new InvalidStatusWordError(\n \"RenameContact final-chunk response did not carry hmac_name\",\n ),\n });\n }\n return DmkResultFactory({ data: { hmacProof } });\n }\n\n private buildPayload(args: SendRenameContactTaskArgs): Uint8Array {\n const builder = new ByteArrayBuilder();\n encodeTlvUInt8(\n builder,\n CONTACTS_TLV_TAG.STRUCT_TYPE,\n STRUCT_TYPE_EDIT_CONTACT_NAME,\n );\n encodeTlvUInt8(\n builder,\n CONTACTS_TLV_TAG.STRUCT_VERSION,\n STRUCT_VERSION_VALUE,\n );\n encodeTlvAscii(builder, CONTACTS_TLV_TAG.CONTACT_NAME, args.newContactName);\n encodeTlvAscii(\n builder,\n CONTACTS_TLV_TAG.PREVIOUS_CONTACT_NAME,\n args.previousContactName,\n );\n encodeTlvBuffer(builder, CONTACTS_TLV_TAG.GROUP_HANDLE, args.groupHandle);\n // The Ethereum app requires DERIVATION_PATH on Edit Contact Name (0x6a80\n // without it), same as Register Identity. Default m/44'/60'/0'/0/0.\n encodeTlvBuffer(\n builder,\n CONTACTS_TLV_TAG.DERIVATION_PATH,\n packDerivationPath([0x8000002c, 0x8000003c, 0x80000000, 0, 0]),\n );\n encodeTlvBuffer(builder, CONTACTS_TLV_TAG.HMAC_PROOF, args.hmacProof);\n\n return builder.build();\n }\n}\n"],
5
- "mappings": "AAcA,OACE,oBAAAA,EAEA,oBAAAC,EAEA,0BAAAC,EACA,0BAAAC,MAEK,kCAEP,OAAS,wBAAAC,MAA4B,oDACrC,OAAS,qBAAAC,MAAyB,+CAElC,OACE,oBAAAC,EACA,kBAAAC,EACA,mBAAAC,EACA,kBAAAC,EACA,sBAAAC,EACA,iCAAAC,EACA,wBAAAC,MACK,sDACP,OAAS,6BAAAC,MAAiC,0DAcnC,MAAMC,CAAsB,CACjC,YACmBC,EACAC,EACjB,CAFiB,SAAAD,EACA,UAAAC,CAChB,CAEH,MAAM,KAAsE,CAC1E,MAAMC,EAAU,KAAK,aAAa,KAAK,IAAI,EAErCC,EAAU,MAAML,EAA0B,KAAK,IAAK,CACxD,QAAAI,EACA,GAAIZ,EACJ,YAAa,CAACc,EAAOC,IAAO,IAAIhB,EAAqB,CAAE,KAAMe,EAAO,GAAAC,CAAG,CAAC,EACxE,OAAQ,KAAK,KAAK,OAClB,WAAY,uBACd,CAAC,EAKD,GAAI,CAACjB,EAAuBe,CAAM,EAChC,OAAOA,EAGT,KAAM,CAAE,UAAAG,CAAU,EAAIH,EAAO,KAC7B,OAAKG,EAOEpB,EAAiB,CAAE,KAAM,CAAE,UAAAoB,CAAU,CAAE,CAAC,EANtCpB,EAAiB,CACtB,MAAO,IAAIC,EACT,4DACF,CACF,CAAC,CAGL,CAEQ,aAAac,EAA6C,CAChE,MAAMM,EAAU,IAAItB,EACpB,OAAAS,EACEa,EACAhB,EAAiB,YACjBK,CACF,EACAF,EACEa,EACAhB,EAAiB,eACjBM,CACF,EACAL,EAAee,EAAShB,EAAiB,aAAcU,EAAK,cAAc,EAC1ET,EACEe,EACAhB,EAAiB,sBACjBU,EAAK,mBACP,EACAR,EAAgBc,EAAShB,EAAiB,aAAcU,EAAK,WAAW,EAGxER,EACEc,EACAhB,EAAiB,gBACjBI,EAAmB,CAAC,WAAY,WAAY,WAAY,EAAG,CAAC,CAAC,CAC/D,EACAF,EAAgBc,EAAShB,EAAiB,WAAYU,EAAK,SAAS,EAE7DM,EAAQ,MAAM,CACvB,CACF",
6
- "names": ["ByteArrayBuilder", "DmkResultFactory", "InvalidStatusWordError", "isSuccessCommandResult", "RenameContactCommand", "RENAME_CONTACT_P1", "CONTACTS_TLV_TAG", "encodeTlvAscii", "encodeTlvBuffer", "encodeTlvUInt8", "packDerivationPath", "STRUCT_TYPE_EDIT_CONTACT_NAME", "STRUCT_VERSION_VALUE", "sendFramedContactsPayload", "SendRenameContactTask", "api", "args", "payload", "result", "chunk", "p2", "hmacProof", "builder"]
4
+ "sourcesContent": ["// Builds the TLV payload for the Rename Contact (EDIT CONTACT NAME) op and\n// dispatches it via the chunked-framing scheme shared with the other\n// address-book ops (2-byte BE total length + <=255B chunks). Rename is an\n// OS/dashboard command (CLA 0xE0 / INS 0x2E) \u2014 the caller runs this task after\n// navigating to the dashboard (GoToDashboardDeviceAction), never inside an app.\n//\n// The device rotates the contact-level hmac_name and returns the fresh proof on\n// the final chunk; per-entry hmac_rest values are untouched (they bind\n// gid|scope|id|family|chain_id, never the name), so a single APDU regardless of\n// the contact's entry count.\n//\n// Reference: Address Book Final Specifications \u2014 Edit Contact Name. Tag order:\n// STRUCT_TYPE, STRUCT_VERSION, CONTACT_NAME (new), PREVIOUS_CONTACT_NAME\n// (old), GROUP_HANDLE, HMAC_PROOF.\n//\n// No DERIVATION_PATH. The tag's status changed three times in the BOLOS SDK,\n// and the address-book TLV parser is compiled into the app (app_features/), so\n// the SDK revision the app was *built* against decides \u2014 not the app version,\n// which reads 1.23.0-dev either way:\n// - before 2026-08-07: tag 0x69 present and MANDATORY, omitting it -> 0x6a80\n// - 8e7e7a4f (2026-08-07): made optional, both forms accepted\n// - a0bb21f5 (2026-08-10): removed, sending it -> 0x6a80 (unknown tag)\n// Omitting it is therefore correct for any app built from 2026-08-07 onward,\n// and wrong for one built before. Both ends verified on hardware: a Flex\n// running a pre-08-07 build of app-ethereum a79f9f8f rejects the payload\n// without the tag in 9ms and no review screen; Speculos running a post-08-10\n// build of the same commit rejects it *with* the tag, the same way.\nimport {\n ByteArrayBuilder,\n type CommandResult,\n DmkResultFactory,\n type InternalApi,\n InvalidStatusWordError,\n isSuccessCommandResult,\n type LoggerPublisherService,\n} from \"@ledgerhq/device-management-kit\";\n\nimport { RenameContactCommand } from \"@internal/app-binder/command/RenameContactCommand\";\nimport { RENAME_CONTACT_P1 } from \"@internal/app-binder/model/contactsConstants\";\nimport { type ContactsErrorCodes } from \"@internal/app-binder/model/contactsErrors\";\nimport {\n CONTACTS_TLV_TAG,\n encodeTlvAscii,\n encodeTlvBuffer,\n encodeTlvUInt8,\n STRUCT_TYPE_EDIT_CONTACT_NAME,\n STRUCT_VERSION_VALUE,\n} from \"@internal/app-binder/services/contactsTlvSerializer\";\nimport { sendFramedContactsPayload } from \"@internal/app-binder/services/sendFramedContactsPayload\";\n\nexport type RenameContactProof = {\n readonly hmacProof: Uint8Array;\n};\n\nexport type SendRenameContactTaskArgs = {\n readonly previousContactName: string;\n readonly newContactName: string;\n readonly groupHandle: Uint8Array;\n readonly hmacProof: Uint8Array;\n readonly logger?: LoggerPublisherService;\n};\n\nexport class SendRenameContactTask {\n constructor(\n private readonly api: InternalApi,\n private readonly args: SendRenameContactTaskArgs,\n ) {}\n\n async run(): Promise<CommandResult<RenameContactProof, ContactsErrorCodes>> {\n const payload = this.buildPayload(this.args);\n\n const result = (await sendFramedContactsPayload(this.api, {\n payload,\n p1: RENAME_CONTACT_P1,\n makeCommand: (chunk, p2) => new RenameContactCommand({ data: chunk, p2 }),\n logger: this.args.logger,\n commandTag: \"SendRenameContactTask\",\n })) as CommandResult<\n { readonly hmacProof?: Uint8Array },\n ContactsErrorCodes\n >;\n\n if (!isSuccessCommandResult(result)) {\n return result;\n }\n\n const { hmacProof } = result.data;\n if (!hmacProof) {\n return DmkResultFactory({\n error: new InvalidStatusWordError(\n \"RenameContact final-chunk response did not carry hmac_name\",\n ),\n });\n }\n return DmkResultFactory({ data: { hmacProof } });\n }\n\n private buildPayload(args: SendRenameContactTaskArgs): Uint8Array {\n const builder = new ByteArrayBuilder();\n encodeTlvUInt8(\n builder,\n CONTACTS_TLV_TAG.STRUCT_TYPE,\n STRUCT_TYPE_EDIT_CONTACT_NAME,\n );\n encodeTlvUInt8(\n builder,\n CONTACTS_TLV_TAG.STRUCT_VERSION,\n STRUCT_VERSION_VALUE,\n );\n encodeTlvAscii(builder, CONTACTS_TLV_TAG.CONTACT_NAME, args.newContactName);\n encodeTlvAscii(\n builder,\n CONTACTS_TLV_TAG.PREVIOUS_CONTACT_NAME,\n args.previousContactName,\n );\n encodeTlvBuffer(builder, CONTACTS_TLV_TAG.GROUP_HANDLE, args.groupHandle);\n encodeTlvBuffer(builder, CONTACTS_TLV_TAG.HMAC_PROOF, args.hmacProof);\n\n return builder.build();\n }\n}\n"],
5
+ "mappings": "AA2BA,OACE,oBAAAA,EAEA,oBAAAC,EAEA,0BAAAC,EACA,0BAAAC,MAEK,kCAEP,OAAS,wBAAAC,MAA4B,oDACrC,OAAS,qBAAAC,MAAyB,+CAElC,OACE,oBAAAC,EACA,kBAAAC,EACA,mBAAAC,EACA,kBAAAC,EACA,iCAAAC,EACA,wBAAAC,MACK,sDACP,OAAS,6BAAAC,MAAiC,0DAcnC,MAAMC,CAAsB,CACjC,YACmBC,EACAC,EACjB,CAFiB,SAAAD,EACA,UAAAC,CAChB,CAEH,MAAM,KAAsE,CAC1E,MAAMC,EAAU,KAAK,aAAa,KAAK,IAAI,EAErCC,EAAU,MAAML,EAA0B,KAAK,IAAK,CACxD,QAAAI,EACA,GAAIX,EACJ,YAAa,CAACa,EAAOC,IAAO,IAAIf,EAAqB,CAAE,KAAMc,EAAO,GAAAC,CAAG,CAAC,EACxE,OAAQ,KAAK,KAAK,OAClB,WAAY,uBACd,CAAC,EAKD,GAAI,CAAChB,EAAuBc,CAAM,EAChC,OAAOA,EAGT,KAAM,CAAE,UAAAG,CAAU,EAAIH,EAAO,KAC7B,OAAKG,EAOEnB,EAAiB,CAAE,KAAM,CAAE,UAAAmB,CAAU,CAAE,CAAC,EANtCnB,EAAiB,CACtB,MAAO,IAAIC,EACT,4DACF,CACF,CAAC,CAGL,CAEQ,aAAaa,EAA6C,CAChE,MAAMM,EAAU,IAAIrB,EACpB,OAAAS,EACEY,EACAf,EAAiB,YACjBI,CACF,EACAD,EACEY,EACAf,EAAiB,eACjBK,CACF,EACAJ,EAAec,EAASf,EAAiB,aAAcS,EAAK,cAAc,EAC1ER,EACEc,EACAf,EAAiB,sBACjBS,EAAK,mBACP,EACAP,EAAgBa,EAASf,EAAiB,aAAcS,EAAK,WAAW,EACxEP,EAAgBa,EAASf,EAAiB,WAAYS,EAAK,SAAS,EAE7DM,EAAQ,MAAM,CACvB,CACF",
6
+ "names": ["ByteArrayBuilder", "DmkResultFactory", "InvalidStatusWordError", "isSuccessCommandResult", "RenameContactCommand", "RENAME_CONTACT_P1", "CONTACTS_TLV_TAG", "encodeTlvAscii", "encodeTlvBuffer", "encodeTlvUInt8", "STRUCT_TYPE_EDIT_CONTACT_NAME", "STRUCT_VERSION_VALUE", "sendFramedContactsPayload", "SendRenameContactTask", "api", "args", "payload", "result", "chunk", "p2", "hmacProof", "builder"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{CommandResultFactory as t,InvalidStatusWordError as d}from"@ledgerhq/device-management-kit";import{RenameContactCommand as p}from"../../app-binder/command/RenameContactCommand";import{SendRenameContactTask as o}from"./SendRenameContactTask";function s(e){const n=new Uint8Array(e.length/2);for(let a=0;a<e.length;a+=2)n[a/2]=parseInt(e.slice(a,a+2),16);return n}const r=s("cc".repeat(64)),c=s("dd".repeat(32)),i=s("ee".repeat(32)),l="6915058000002c8000003c800000000000000000000000",u=s("009001012e02010181f003426f6281f305416c69636581f640"+"cc".repeat(64)+l+"2920"+"dd".repeat(32));function m(){return{sendCommand:vi.fn()}}describe("SendRenameContactTask",()=>{it("assembles the rename framed chunk and dispatches RenameContactCommand",async()=>{const e=m();e.sendCommand.mockResolvedValueOnce(t({data:{hmacProof:i}}));const n=await new o(e,{previousContactName:"Alice",newContactName:"Bob",groupHandle:r,hmacProof:c}).run();expect(e.sendCommand.mock.calls).toHaveLength(1),expect(e.sendCommand.mock.calls[0][0]).toStrictEqual(new p({data:u,p2:0})),expect(n).toStrictEqual(t({data:{hmacProof:i}}))}),it("emits the DERIVATION_PATH tag immediately before HMAC_PROOF",async()=>{const e=m();e.sendCommand.mockResolvedValueOnce(t({data:{hmacProof:i}})),await new o(e,{previousContactName:"Alice",newContactName:"Bob",groupHandle:r,hmacProof:c}).run();const n=e.sendCommand.mock.calls[0][0],a=Buffer.from(n.args.data).toString("hex");expect(a.endsWith(l+"2920"+"dd".repeat(32))).toBe(!0)}),it("propagates command-level errors",async()=>{const e=m();e.sendCommand.mockResolvedValueOnce(t({error:new d("user cancelled")}));const n=await new o(e,{previousContactName:"Alice",newContactName:"Bob",groupHandle:r,hmacProof:c}).run();expect(n).toStrictEqual(t({error:new d("user cancelled")}))}),it("returns InvalidStatusWordError when the final response carries no hmac_name",async()=>{const e=m();e.sendCommand.mockResolvedValueOnce(t({data:{}}));const n=await new o(e,{previousContactName:"Alice",newContactName:"Bob",groupHandle:r,hmacProof:c}).run();expect(n.status).not.toBe(void 0),"error"in n&&expect(n.error).toBeInstanceOf(d)})});
1
+ import{CommandResultFactory as t,InvalidStatusWordError as d}from"@ledgerhq/device-management-kit";import{RenameContactCommand as l}from"../../app-binder/command/RenameContactCommand";import{SendRenameContactTask as o}from"./SendRenameContactTask";function s(e){const n=new Uint8Array(e.length/2);for(let a=0;a<e.length;a+=2)n[a/2]=parseInt(e.slice(a,a+2),16);return n}const r=s("cc".repeat(64)),c=s("dd".repeat(32)),i=s("ee".repeat(32)),p=s("007901012e02010181f003426f6281f305416c69636581f640"+"cc".repeat(64)+"2920"+"dd".repeat(32));function m(){return{sendCommand:vi.fn()}}describe("SendRenameContactTask",()=>{it("assembles the rename framed chunk and dispatches RenameContactCommand",async()=>{const e=m();e.sendCommand.mockResolvedValueOnce(t({data:{hmacProof:i}}));const n=await new o(e,{previousContactName:"Alice",newContactName:"Bob",groupHandle:r,hmacProof:c}).run();expect(e.sendCommand.mock.calls).toHaveLength(1),expect(e.sendCommand.mock.calls[0][0]).toStrictEqual(new l({data:p,p2:0})),expect(n).toStrictEqual(t({data:{hmacProof:i}}))}),it("does not emit a DERIVATION_PATH tag",async()=>{const e=m();e.sendCommand.mockResolvedValueOnce(t({data:{hmacProof:i}})),await new o(e,{previousContactName:"Alice",newContactName:"Bob",groupHandle:r,hmacProof:c}).run();const n=e.sendCommand.mock.calls[0][0],a=Buffer.from(n.args.data).toString("hex");expect(a).not.toContain("691505"),expect(a.endsWith("81f640"+"cc".repeat(64)+"2920"+"dd".repeat(32))).toBe(!0)}),it("propagates command-level errors",async()=>{const e=m();e.sendCommand.mockResolvedValueOnce(t({error:new d("user cancelled")}));const n=await new o(e,{previousContactName:"Alice",newContactName:"Bob",groupHandle:r,hmacProof:c}).run();expect(n).toStrictEqual(t({error:new d("user cancelled")}))}),it("returns InvalidStatusWordError when the final response carries no hmac_name",async()=>{const e=m();e.sendCommand.mockResolvedValueOnce(t({data:{}}));const n=await new o(e,{previousContactName:"Alice",newContactName:"Bob",groupHandle:r,hmacProof:c}).run();expect(n.status).not.toBe(void 0),"error"in n&&expect(n.error).toBeInstanceOf(d)})});
2
2
  //# sourceMappingURL=SendRenameContactTask.test.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/internal/app-binder/task/SendRenameContactTask.test.ts"],
4
- "sourcesContent": ["// Validates that the TLV serializer + tag ordering + chunk framing produce the\n// expected wire bytes for the Rename Contact (EDIT CONTACT NAME) op. The framed\n// chunk = 2-byte BE total length + TLV. Tag order: STRUCT_TYPE, STRUCT_VERSION,\n// CONTACT_NAME (new), PREVIOUS_CONTACT_NAME (old), GROUP_HANDLE, DERIVATION_PATH,\n// HMAC_PROOF \u2014 with tags >= 0x80 (0xf0/0xf3/0xf6) encoded as the 2-byte DER form\n// [0x81, tag].\nimport {\n CommandResultFactory,\n type InternalApi,\n InvalidStatusWordError,\n} from \"@ledgerhq/device-management-kit\";\n\nimport { RenameContactCommand } from \"@internal/app-binder/command/RenameContactCommand\";\n\nimport { SendRenameContactTask } from \"./SendRenameContactTask\";\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n out[i / 2] = parseInt(hex.slice(i, i + 2), 16);\n }\n return out;\n}\n\nconst GROUP_HANDLE = hexToBytes(\"cc\".repeat(64));\nconst HMAC_PROOF_IN = hexToBytes(\"dd\".repeat(32));\nconst HMAC_PROOF_OUT = hexToBytes(\"ee\".repeat(32));\n\n// Framed chunk = \"00 90\" (2-byte BE total TLV length = 144) + TLV:\n// 01 01 2e STRUCT_TYPE = EDIT_CONTACT_NAME (0x2e)\n// 02 01 01 STRUCT_VERSION = 1\n// 81 f0 03 \"Bob\" CONTACT_NAME (new), tag 0xf0 -> [81 f0]\n// 81 f3 05 \"Alice\" PREVIOUS_CONTACT_NAME (old), tag 0xf3 -> [81 f3]\n// 81 f6 40 <64x cc> GROUP_HANDLE, tag 0xf6 -> [81 f6]\n// 69 15 05 <5x path> DERIVATION_PATH (0x69), m/44'/60'/0'/0/0\n// 29 20 <32x dd> HMAC_PROOF\nconst DERIVATION_PATH_TLV = \"6915058000002c8000003c800000000000000000000000\";\nconst FRAMED_CHUNK = hexToBytes(\n \"0090\" +\n \"01012e\" +\n \"020101\" +\n \"81f003426f62\" +\n \"81f305416c696365\" +\n \"81f640\" +\n \"cc\".repeat(64) +\n DERIVATION_PATH_TLV +\n \"2920\" +\n \"dd\".repeat(32),\n);\n\nfunction makeApiMock(): InternalApi & {\n sendCommand: ReturnType<typeof vi.fn>;\n} {\n return { sendCommand: vi.fn() } as unknown as InternalApi & {\n sendCommand: ReturnType<typeof vi.fn>;\n };\n}\n\ndescribe(\"SendRenameContactTask\", () => {\n it(\"assembles the rename framed chunk and dispatches RenameContactCommand\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({ data: { hmacProof: HMAC_PROOF_OUT } }),\n );\n\n const result = await new SendRenameContactTask(api, {\n previousContactName: \"Alice\",\n newContactName: \"Bob\",\n groupHandle: GROUP_HANDLE,\n hmacProof: HMAC_PROOF_IN,\n }).run();\n\n expect(api.sendCommand.mock.calls).toHaveLength(1);\n expect(api.sendCommand.mock.calls[0]![0]).toStrictEqual(\n new RenameContactCommand({ data: FRAMED_CHUNK, p2: 0x00 }),\n );\n expect(result).toStrictEqual(\n CommandResultFactory({ data: { hmacProof: HMAC_PROOF_OUT } }),\n );\n });\n\n it(\"emits the DERIVATION_PATH tag immediately before HMAC_PROOF\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({ data: { hmacProof: HMAC_PROOF_OUT } }),\n );\n\n await new SendRenameContactTask(api, {\n previousContactName: \"Alice\",\n newContactName: \"Bob\",\n groupHandle: GROUP_HANDLE,\n hmacProof: HMAC_PROOF_IN,\n }).run();\n\n // The Ethereum app requires DERIVATION_PATH (0x69); it sits immediately\n // before HMAC_PROOF (0x29), matching Register Identity.\n const command = api.sendCommand.mock.calls[0]![0] as RenameContactCommand;\n const framedHex = Buffer.from(command.args.data).toString(\"hex\");\n expect(\n framedHex.endsWith(DERIVATION_PATH_TLV + \"2920\" + \"dd\".repeat(32)),\n ).toBe(true);\n });\n\n it(\"propagates command-level errors\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({\n error: new InvalidStatusWordError(\"user cancelled\"),\n }),\n );\n\n const result = await new SendRenameContactTask(api, {\n previousContactName: \"Alice\",\n newContactName: \"Bob\",\n groupHandle: GROUP_HANDLE,\n hmacProof: HMAC_PROOF_IN,\n }).run();\n\n expect(result).toStrictEqual(\n CommandResultFactory({\n error: new InvalidStatusWordError(\"user cancelled\"),\n }),\n );\n });\n\n it(\"returns InvalidStatusWordError when the final response carries no hmac_name\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(CommandResultFactory({ data: {} }));\n\n const result = await new SendRenameContactTask(api, {\n previousContactName: \"Alice\",\n newContactName: \"Bob\",\n groupHandle: GROUP_HANDLE,\n hmacProof: HMAC_PROOF_IN,\n }).run();\n\n expect(result.status).not.toBe(undefined);\n if (\"error\" in result) {\n expect(result.error).toBeInstanceOf(InvalidStatusWordError);\n }\n });\n});\n"],
5
- "mappings": "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",
6
- "names": ["CommandResultFactory", "InvalidStatusWordError", "RenameContactCommand", "SendRenameContactTask", "hexToBytes", "hex", "out", "i", "GROUP_HANDLE", "HMAC_PROOF_IN", "HMAC_PROOF_OUT", "DERIVATION_PATH_TLV", "FRAMED_CHUNK", "makeApiMock", "api", "result", "command", "framedHex"]
4
+ "sourcesContent": ["// Validates that the TLV serializer + tag ordering + chunk framing produce the\n// expected wire bytes for the Rename Contact (EDIT CONTACT NAME) op. The framed\n// chunk = 2-byte BE total length + TLV. Tag order: STRUCT_TYPE, STRUCT_VERSION,\n// CONTACT_NAME (new), PREVIOUS_CONTACT_NAME (old), GROUP_HANDLE, HMAC_PROOF \u2014\n// with tags >= 0x80 (0xf0/0xf3/0xf6) encoded as the 2-byte DER form [0x81, tag].\n// No DERIVATION_PATH: the SDK removed tag 0x69 from EDIT_CONTACT_NAME_TAGS on\n// 2026-08-10 and rejects payloads carrying it.\nimport {\n CommandResultFactory,\n type InternalApi,\n InvalidStatusWordError,\n} from \"@ledgerhq/device-management-kit\";\n\nimport { RenameContactCommand } from \"@internal/app-binder/command/RenameContactCommand\";\n\nimport { SendRenameContactTask } from \"./SendRenameContactTask\";\n\nfunction hexToBytes(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n out[i / 2] = parseInt(hex.slice(i, i + 2), 16);\n }\n return out;\n}\n\nconst GROUP_HANDLE = hexToBytes(\"cc\".repeat(64));\nconst HMAC_PROOF_IN = hexToBytes(\"dd\".repeat(32));\nconst HMAC_PROOF_OUT = hexToBytes(\"ee\".repeat(32));\n\n// Framed chunk = \"00 79\" (2-byte BE total TLV length = 121) + TLV:\n// 01 01 2e STRUCT_TYPE = EDIT_CONTACT_NAME (0x2e)\n// 02 01 01 STRUCT_VERSION = 1\n// 81 f0 03 \"Bob\" CONTACT_NAME (new), tag 0xf0 -> [81 f0]\n// 81 f3 05 \"Alice\" PREVIOUS_CONTACT_NAME (old), tag 0xf3 -> [81 f3]\n// 81 f6 40 <64x cc> GROUP_HANDLE, tag 0xf6 -> [81 f6]\n// 29 20 <32x dd> HMAC_PROOF\nconst FRAMED_CHUNK = hexToBytes(\n \"0079\" +\n \"01012e\" +\n \"020101\" +\n \"81f003426f62\" +\n \"81f305416c696365\" +\n \"81f640\" +\n \"cc\".repeat(64) +\n \"2920\" +\n \"dd\".repeat(32),\n);\n\nfunction makeApiMock(): InternalApi & {\n sendCommand: ReturnType<typeof vi.fn>;\n} {\n return { sendCommand: vi.fn() } as unknown as InternalApi & {\n sendCommand: ReturnType<typeof vi.fn>;\n };\n}\n\ndescribe(\"SendRenameContactTask\", () => {\n it(\"assembles the rename framed chunk and dispatches RenameContactCommand\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({ data: { hmacProof: HMAC_PROOF_OUT } }),\n );\n\n const result = await new SendRenameContactTask(api, {\n previousContactName: \"Alice\",\n newContactName: \"Bob\",\n groupHandle: GROUP_HANDLE,\n hmacProof: HMAC_PROOF_IN,\n }).run();\n\n expect(api.sendCommand.mock.calls).toHaveLength(1);\n expect(api.sendCommand.mock.calls[0]![0]).toStrictEqual(\n new RenameContactCommand({ data: FRAMED_CHUNK, p2: 0x00 }),\n );\n expect(result).toStrictEqual(\n CommandResultFactory({ data: { hmacProof: HMAC_PROOF_OUT } }),\n );\n });\n\n it(\"does not emit a DERIVATION_PATH tag\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({ data: { hmacProof: HMAC_PROOF_OUT } }),\n );\n\n await new SendRenameContactTask(api, {\n previousContactName: \"Alice\",\n newContactName: \"Bob\",\n groupHandle: GROUP_HANDLE,\n hmacProof: HMAC_PROOF_IN,\n }).run();\n\n // api-level 27 dropped tag 0x69 from EDIT_CONTACT_NAME_TAGS: a payload\n // carrying it fails to parse and the device answers 0x6a80. HMAC_PROOF\n // (0x29) therefore follows GROUP_HANDLE directly.\n const command = api.sendCommand.mock.calls[0]![0] as RenameContactCommand;\n const framedHex = Buffer.from(command.args.data).toString(\"hex\");\n expect(framedHex).not.toContain(\"691505\");\n expect(\n framedHex.endsWith(\"81f640\" + \"cc\".repeat(64) + \"2920\" + \"dd\".repeat(32)),\n ).toBe(true);\n });\n\n it(\"propagates command-level errors\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(\n CommandResultFactory({\n error: new InvalidStatusWordError(\"user cancelled\"),\n }),\n );\n\n const result = await new SendRenameContactTask(api, {\n previousContactName: \"Alice\",\n newContactName: \"Bob\",\n groupHandle: GROUP_HANDLE,\n hmacProof: HMAC_PROOF_IN,\n }).run();\n\n expect(result).toStrictEqual(\n CommandResultFactory({\n error: new InvalidStatusWordError(\"user cancelled\"),\n }),\n );\n });\n\n it(\"returns InvalidStatusWordError when the final response carries no hmac_name\", async () => {\n const api = makeApiMock();\n api.sendCommand.mockResolvedValueOnce(CommandResultFactory({ data: {} }));\n\n const result = await new SendRenameContactTask(api, {\n previousContactName: \"Alice\",\n newContactName: \"Bob\",\n groupHandle: GROUP_HANDLE,\n hmacProof: HMAC_PROOF_IN,\n }).run();\n\n expect(result.status).not.toBe(undefined);\n if (\"error\" in result) {\n expect(result.error).toBeInstanceOf(InvalidStatusWordError);\n }\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["CommandResultFactory", "InvalidStatusWordError", "RenameContactCommand", "SendRenameContactTask", "hexToBytes", "hex", "out", "i", "GROUP_HANDLE", "HMAC_PROOF_IN", "HMAC_PROOF_OUT", "FRAMED_CHUNK", "makeApiMock", "api", "result", "command", "framedHex"]
7
7
  }
@@ -36,7 +36,7 @@
36
36
  "license": "Apache-2.0",
37
37
  "name": "@ledgerhq/device-contacts-kit",
38
38
  "peerDependencies": {
39
- "@ledgerhq/device-management-kit": "^1.8.0"
39
+ "@ledgerhq/device-management-kit": "workspace:^"
40
40
  },
41
41
  "private": false,
42
42
  "repository": {
@@ -58,5 +58,5 @@
58
58
  "watch:builds": "pnpm ldmk-tool watch --entryPoints src/index.ts,src/**/*.ts --tsconfig tsconfig.prod.json",
59
59
  "watch:types": "concurrently \"tsc --watch -p tsconfig.prod.json\" \"tsc-alias --watch -p tsconfig.prod.json\""
60
60
  },
61
- "version": "0.3.1"
61
+ "version": "0.4.0"
62
62
  }
@@ -4,9 +4,12 @@ export { type RegisterExternalAddressDAError, type RegisterExternalAddressDAInpu
4
4
  export { type RenameContactDAError, type RenameContactDAInput, type RenameContactDAIntermediateValue, type RenameContactDAOutput, type RenameContactDARequiredInteraction, type RenameContactDAReturnType, type RenameContactDAState, } from "./app-binder/RenameContactDeviceActionTypes";
5
5
  export { type ContactsManager } from "./ContactsManager";
6
6
  export { ContactsManagerBuilder } from "./ContactsManagerBuilder";
7
- export { CONTACTS_VERSION_REQUIREMENTS, type ContactsModelRequirement, type ContactsModelSupport, type ContactsModelUnsupported, type ContactsSupportQuery, type ContactsVersionRequirements, ETHEREUM_APP_NAME, isContactsSupported, isVersionAtLeast, resolveContactsVersionRequirements, } from "./model/ContactsVersionRequirements";
7
+ export { CONTACTS_VERSION_REQUIREMENTS, type ContactsModelRequirement, type ContactsModelSupport, type ContactsModelUnsupported, type ContactsVersionRequirements, ETHEREUM_APP_NAME, isVersionAtLeast, resolveContactsVersionRequirements, } from "./model/ContactsVersionRequirements";
8
8
  export { type EditExternalAddressIdentifierInput, type EditExternalAddressIdentifierOutput, } from "./model/EditExternalAddressIdentifier";
9
9
  export { type EditExternalAddressScopeInput, type EditExternalAddressScopeOutput, } from "./model/EditExternalAddressScope";
10
+ export { type ProvideContactInput } from "./model/ProvideContact";
10
11
  export { type ExistingContactGroup, type RegisterExternalAddressInput, type RegisterExternalAddressMode, type RegisterExternalAddressOutput, } from "./model/RegisterExternalAddress";
11
12
  export { type RenameContactInput, type RenameContactOutput, } from "./model/RenameContact";
13
+ export { type ContactsErrorCodes } from "../internal/app-binder/model/contactsErrors";
14
+ export { buildProvideContactPayload, sendProvideContactPayload, type SendProvideContactPayloadArgs, } from "../internal/app-binder/services/provideContact";
12
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oCAAoC,EACzC,KAAK,oCAAoC,EACzC,KAAK,gDAAgD,EACrD,KAAK,qCAAqC,EAC1C,KAAK,kDAAkD,EACvD,KAAK,yCAAyC,EAC9C,KAAK,oCAAoC,GAC1C,MAAM,gEAAgE,CAAC;AACxE,OAAO,EACL,KAAK,+BAA+B,EACpC,KAAK,+BAA+B,EACpC,KAAK,2CAA2C,EAChD,KAAK,gCAAgC,EACrC,KAAK,6CAA6C,EAClD,KAAK,oCAAoC,EACzC,KAAK,+BAA+B,GACrC,MAAM,2DAA2D,CAAC;AACnE,OAAO,EACL,KAAK,8BAA8B,EACnC,KAAK,8BAA8B,EACnC,KAAK,0CAA0C,EAC/C,KAAK,+BAA+B,EACpC,KAAK,4CAA4C,EACjD,KAAK,mCAAmC,EACxC,KAAK,8BAA8B,GACpC,MAAM,0DAA0D,CAAC;AAClE,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,gCAAgC,EACrC,KAAK,qBAAqB,EAC1B,KAAK,kCAAkC,EACvC,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,GAC1B,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACL,6BAA6B,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,kCAAkC,GACnC,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,KAAK,kCAAkC,EACvC,KAAK,mCAAmC,GACzC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,8BAA8B,GACpC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,GACnC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oCAAoC,EACzC,KAAK,oCAAoC,EACzC,KAAK,gDAAgD,EACrD,KAAK,qCAAqC,EAC1C,KAAK,kDAAkD,EACvD,KAAK,yCAAyC,EAC9C,KAAK,oCAAoC,GAC1C,MAAM,gEAAgE,CAAC;AACxE,OAAO,EACL,KAAK,+BAA+B,EACpC,KAAK,+BAA+B,EACpC,KAAK,2CAA2C,EAChD,KAAK,gCAAgC,EACrC,KAAK,6CAA6C,EAClD,KAAK,oCAAoC,EACzC,KAAK,+BAA+B,GACrC,MAAM,2DAA2D,CAAC;AACnE,OAAO,EACL,KAAK,8BAA8B,EACnC,KAAK,8BAA8B,EACnC,KAAK,0CAA0C,EAC/C,KAAK,+BAA+B,EACpC,KAAK,4CAA4C,EACjD,KAAK,mCAAmC,EACxC,KAAK,8BAA8B,GACpC,MAAM,0DAA0D,CAAC;AAClE,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,gCAAgC,EACrC,KAAK,qBAAqB,EAC1B,KAAK,kCAAkC,EACvC,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,GAC1B,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACL,6BAA6B,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,iBAAiB,EACjB,gBAAgB,EAChB,kCAAkC,GACnC,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,KAAK,kCAAkC,EACvC,KAAK,mCAAmC,GACzC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,8BAA8B,GACpC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,GACnC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AACpF,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,KAAK,6BAA6B,GACnC,MAAM,8CAA8C,CAAC"}
@@ -31,8 +31,8 @@ export type ContactsVersionRequirements = Readonly<Record<DeviceModelId, Contact
31
31
  /** The name of the Ethereum embedded app (the only app supported in v1). */
32
32
  export declare const ETHEREUM_APP_NAME = "Ethereum";
33
33
  /**
34
- * Contacts APDUs are supported only on the touchscreen device models (Stax,
35
- * Flex, Apex). The Nano models do not support Contacts at all.
34
+ * Contacts APDUs are supported on the touchscreen device models (Stax, Flex,
35
+ * Apex) and on Nano X / Nano SP. Nano S does not support Contacts at all.
36
36
  */
37
37
  export declare const CONTACTS_VERSION_REQUIREMENTS: ContactsVersionRequirements;
38
38
  /**
@@ -42,29 +42,11 @@ export declare const CONTACTS_VERSION_REQUIREMENTS: ContactsVersionRequirements;
42
42
  export declare function resolveContactsVersionRequirements(deviceModelId: DeviceModelId): ContactsModelRequirement;
43
43
  /**
44
44
  * Whether `actual` is greater than or equal to `minimum`, comparing as semver.
45
- * Tolerant of non-strict version strings (coerces where possible) and returns
46
- * `false` (i.e. requirement not met) when either version cannot be parsed, so
47
- * an unknown version never passes a check.
45
+ * Tolerant of non-strict version strings and prerelease/build tags (coerces
46
+ * both sides, which drops them) so a release candidate such as `1.7.0-rc2`
47
+ * counts as meeting a `1.7.0` minimum. Returns `false` (i.e. requirement not
48
+ * met) when either version cannot be parsed, so an unknown version never
49
+ * passes a check.
48
50
  */
49
51
  export declare function isVersionAtLeast(actual: string, minimum: string): boolean;
50
- /** Inputs for {@link isContactsSupported}. */
51
- export type ContactsSupportQuery = {
52
- readonly deviceModelId: DeviceModelId;
53
- /** The device OS (firmware) version. */
54
- readonly osVersion: string;
55
- /** The name of the currently running embedded app. */
56
- readonly appName: string;
57
- /** The version of the currently running embedded app. */
58
- readonly appVersion: string;
59
- };
60
- /**
61
- * Whether Contacts app-owned operations are supported for the given device
62
- * model, OS version, and running app — checking the model is supported, the OS
63
- * meets its minimum, the app is known to Contacts, and the app meets its
64
- * minimum.
65
- *
66
- * Pure and dependency-light: hosts can call this directly, or read
67
- * {@link CONTACTS_VERSION_REQUIREMENTS} to build their own checks.
68
- */
69
- export declare function isContactsSupported({ deviceModelId, osVersion, appName, appVersion, }: ContactsSupportQuery): boolean;
70
52
  //# sourceMappingURL=ContactsVersionRequirements.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ContactsVersionRequirements.d.ts","sourceRoot":"","sources":["../../../../src/api/model/ContactsVersionRequirements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGhE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC1D,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAChC,oBAAoB,GACpB,wBAAwB,CAAC;AAE7B;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAChD,MAAM,CAAC,aAAa,EAAE,wBAAwB,CAAC,CAChD,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,aAAa,CAAC;AAY5C;;;GAGG;AACH,eAAO,MAAM,6BAA6B,EAAE,2BAmB3C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kCAAkC,CAChD,aAAa,EAAE,aAAa,GAC3B,wBAAwB,CAE1B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAKzE;AAED,8CAA8C;AAC9C,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,wCAAwC;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,SAAS,EACT,OAAO,EACP,UAAU,GACX,EAAE,oBAAoB,GAAG,OAAO,CAQhC"}
1
+ {"version":3,"file":"ContactsVersionRequirements.d.ts","sourceRoot":"","sources":["../../../../src/api/model/ContactsVersionRequirements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGhE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC1D,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAChC,oBAAoB,GACpB,wBAAwB,CAAC;AAE7B;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAChD,MAAM,CAAC,aAAa,EAAE,wBAAwB,CAAC,CAChD,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,aAAa,CAAC;AAY5C;;;GAGG;AACH,eAAO,MAAM,6BAA6B,EAAE,2BA2B3C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kCAAkC,CAChD,aAAa,EAAE,aAAa,GAC3B,wBAAwB,CAE1B;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAKzE"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Everything the device needs to accept a registered external contact as a
3
+ * decoration for an upcoming signing flow: the name-level material shared by
4
+ * the whole contact group, plus the address-level material of the one matched
5
+ * address.
6
+ */
7
+ export type ProvideContactInput = {
8
+ readonly contactName: string;
9
+ readonly scope: string;
10
+ /** Raw bytes, chain-dependent: a 20-byte address for Ethereum. */
11
+ readonly identifier: Uint8Array;
12
+ readonly groupHandle: Uint8Array;
13
+ readonly hmacProof: Uint8Array;
14
+ readonly hmacRest: Uint8Array;
15
+ /** Lowercase family name, e.g. `"ethereum"`. */
16
+ readonly blockchainFamily: string;
17
+ /** Mandatory for Ethereum, omitted for every other family. */
18
+ readonly chainId?: bigint;
19
+ };
20
+ //# sourceMappingURL=ProvideContact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProvideContact.d.ts","sourceRoot":"","sources":["../../../../src/api/model/ProvideContact.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,gDAAgD;IAChD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type Apdu, type ApduResponse, type Command, type CommandResult } from "@ledgerhq/device-management-kit";
2
+ import { type ContactsErrorCodes } from "../../app-binder/model/contactsErrors";
3
+ export type ProvideContactCommandArgs = {
4
+ /** One framed chunk built by sendProvideContactPayload. */
5
+ readonly data: Uint8Array;
6
+ /** 0x00 for the first/only chunk, 0x80 for continuation chunks. */
7
+ readonly p2: number;
8
+ };
9
+ export type ProvideContactCommandResponse = void;
10
+ export declare class ProvideContactCommand implements Command<ProvideContactCommandResponse, ProvideContactCommandArgs, ContactsErrorCodes> {
11
+ readonly name = "provideContact";
12
+ readonly args: ProvideContactCommandArgs;
13
+ private readonly errorHelper;
14
+ constructor(args: ProvideContactCommandArgs);
15
+ getApdu(): Apdu;
16
+ parseResponse(response: ApduResponse): CommandResult<ProvideContactCommandResponse, ContactsErrorCodes>;
17
+ }
18
+ //# sourceMappingURL=ProvideContactCommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProvideContactCommand.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/command/ProvideContactCommand.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,KAAK,IAAI,EAET,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,KAAK,aAAa,EAEnB,MAAM,iCAAiC,CAAC;AASzC,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,2CAA2C,CAAC;AAEnD,MAAM,MAAM,yBAAyB,GAAG;IACtC,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,IAAI,CAAC;AAEjD,qBAAa,qBACX,YACE,OAAO,CACL,6BAA6B,EAC7B,yBAAyB,EACzB,kBAAkB,CACnB;IAEH,QAAQ,CAAC,IAAI,oBAAoB;IACjC,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAGwB;gBAExC,IAAI,EAAE,yBAAyB;IAI3C,OAAO,IAAI,IAAI;IAWf,aAAa,CACX,QAAQ,EAAE,YAAY,GACrB,aAAa,CAAC,6BAA6B,EAAE,kBAAkB,CAAC;CASpE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ProvideContactCommand.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProvideContactCommand.test.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/command/ProvideContactCommand.test.ts"],"names":[],"mappings":""}
@@ -11,6 +11,8 @@ export declare const CONTACTS_APDU_CLA = 176;
11
11
  export declare const CONTACTS_APDU_INS = 16;
12
12
  /** P1 sub-command selector for REGISTER IDENTITY (register external address). */
13
13
  export declare const SUB_CMD_REGISTER_IDENTITY = 1;
14
+ /** P1 sub-command selector for PROVIDE CONTACT (pre-signing decoration). */
15
+ export declare const SUB_CMD_PROVIDE_CONTACT = 32;
14
16
  /**
15
17
  * P1 sub-command selector for EDIT IDENTIFIER — an address-book app sub-command
16
18
  * (CLA 0xB0 / INS 0x10) served by the coin app, never OS-dispatched.
@@ -1 +1 @@
1
- {"version":3,"file":"contactsConstants.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/model/contactsConstants.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,MAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,KAAO,CAAC;AAEtC,iFAAiF;AACjF,eAAO,MAAM,yBAAyB,IAAO,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,IAAO,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,IAAO,CAAC;AAEvC;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,MAAO,CAAC;AAC5C,eAAO,MAAM,uBAAuB,KAAO,CAAC;AAC5C,eAAO,MAAM,iBAAiB,IAAO,CAAC;AAEtC,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOtE,CAAC"}
1
+ {"version":3,"file":"contactsConstants.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/model/contactsConstants.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,MAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,KAAO,CAAC;AAEtC,iFAAiF;AACjF,eAAO,MAAM,yBAAyB,IAAO,CAAC;AAE9C,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,KAAO,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,IAAO,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,IAAO,CAAC;AAEvC;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,MAAO,CAAC;AAC5C,eAAO,MAAM,uBAAuB,KAAO,CAAC;AAC5C,eAAO,MAAM,iBAAiB,IAAO,CAAC;AAEtC,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOtE,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type CommandResult, type InternalApi, type LoggerPublisherService } from "@ledgerhq/device-management-kit";
2
+ import { type ProvideContactInput } from "../../../api/model/ProvideContact";
3
+ import { type ContactsErrorCodes } from "../../app-binder/model/contactsErrors";
4
+ /**
5
+ * Encode a matched contact as the PROVIDE CONTACT TLV payload, without the
6
+ * chunk framing. Pure, so callers can build it while matching a recipient
7
+ * against the address book, with no device involved.
8
+ */
9
+ export declare function buildProvideContactPayload(input: ProvideContactInput): Uint8Array;
10
+ export type SendProvideContactPayloadArgs = {
11
+ readonly payload: Uint8Array;
12
+ readonly logger?: LoggerPublisherService;
13
+ };
14
+ export declare function sendProvideContactPayload(api: InternalApi, { payload, logger }: SendProvideContactPayloadArgs): Promise<CommandResult<void, ContactsErrorCodes>>;
15
+ //# sourceMappingURL=provideContact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provideContact.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/services/provideContact.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAMrE,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAYpF;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,mBAAmB,GACzB,UAAU,CAkCZ;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAC1C,CAAC;AAEF,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,WAAW,EAChB,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,6BAA6B,GACjD,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAQlD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=provideContact.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provideContact.test.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/services/provideContact.test.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"SendRegisterIdentityTask.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/task/SendRegisterIdentityTask.ts"],"names":[],"mappings":"AAaA,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,WAAW,EAGhB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAM/E,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAYpF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAC1C,CAAC;AAEF,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,4BAA4B;IAG/C,GAAG,IAAI,OAAO,CAClB,aAAa,CAAC,sBAAsB,EAAE,kBAAkB,CAAC,CAC1D;IAkCD,OAAO,CAAC,YAAY;CA+CrB"}
1
+ {"version":3,"file":"SendRegisterIdentityTask.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/task/SendRegisterIdentityTask.ts"],"names":[],"mappings":"AAuBA,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,WAAW,EAGhB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAM/E,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAYpF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAC1C,CAAC;AAEF,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,4BAA4B;IAG/C,GAAG,IAAI,OAAO,CAClB,aAAa,CAAC,sBAAsB,EAAE,kBAAkB,CAAC,CAC1D;IAkCD,OAAO,CAAC,YAAY;CA+CrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"SendRenameContactTask.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/task/SendRenameContactTask.ts"],"names":[],"mappings":"AAcA,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,WAAW,EAGhB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAC;AAIzC,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAYpF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAC1C,CAAC;AAEF,qBAAa,qBAAqB;IAE9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,yBAAyB;IAG5C,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAC;IA6B3E,OAAO,CAAC,YAAY;CA8BrB"}
1
+ {"version":3,"file":"SendRenameContactTask.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/task/SendRenameContactTask.ts"],"names":[],"mappings":"AA2BA,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,WAAW,EAGhB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAC;AAIzC,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAWpF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAC1C,CAAC;AAEF,qBAAa,qBAAqB;IAE9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,yBAAyB;IAG5C,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAC;IA6B3E,OAAO,CAAC,YAAY;CAuBrB"}