diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index 653a4dc46..c32eb2557 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -10,12 +10,16 @@ import { Blockchain } from '@kiltprotocol/chain-helpers' import { multibaseKeyToDidKey } from '@kiltprotocol/did' - import type { + KeyringPair, + KiltAddress, + MultibaseKeyPair, DidHelpersAcceptedPublicKeyEncodings, SharedArguments, TransactionHandlers, + TransactionSigner, } from '@kiltprotocol/types' +import { Keyring, Multikey, Crypto } from '@kiltprotocol/utils' export async function submitImpl( getSubmittable: TransactionHandlers['getSubmittable'], @@ -24,7 +28,10 @@ export async function submitImpl( awaitFinalized?: boolean } ): ReturnType { - const submittable = await getSubmittable(options) + const submittable = await getSubmittable({ + ...options, + signSubmittable: true, + }) const { awaitFinalized = true } = options const result = await Blockchain.submitSignedTx( @@ -63,3 +70,52 @@ export function convertPublicKey(pk: DidHelpersAcceptedPublicKeyEncodings): { } return { publicKey, type } } + +export function extractSubmitterSignerAndAccount( + submitter: KeyringPair | TransactionSigner | MultibaseKeyPair | KiltAddress +): { + submitterSigner?: TransactionSigner | KeyringPair | undefined + submitterAccount: KiltAddress +} { + // KiltAddress + if (typeof submitter === 'string') { + return { + submitterAccount: submitter, + } + } + // KeyringPair + if ('address' in submitter) { + return { + submitterAccount: submitter.address as KiltAddress, + submitterSigner: submitter, + } + } + // Blockchain.TransactionSigner + if ('id' in submitter) { + return { + submitterAccount: submitter.id as KiltAddress, + submitterSigner: submitter, + } + } + // MultibaseKeyPair + if ('publicKeyMultibase' in submitter) { + const submitterAccount = Crypto.encodeAddress( + Multikey.decodeMultibaseKeypair(submitter).publicKey, + 38 + ) + const keypair = Multikey.decodeMultibaseKeypair(submitter) + if (keypair.type === 'x25519') { + throw new Error('x25519 keys are not supported') + } + const keyring = new Keyring().createFromPair( + keypair, + undefined, + keypair.type === 'secp256k1' ? 'ecdsa' : keypair.type + ) + return { + submitterAccount, + submitterSigner: keyring, + } + } + throw new Error('type of submitter is invalid') +} diff --git a/packages/sdk-js/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts index c5f63d76d..86f484ca4 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -14,7 +14,6 @@ import { } from '@kiltprotocol/did' import type { DidHelpersAcceptedPublicKeyEncodings, - KiltAddress, SharedArguments, SignerInterface, TransactionHandlers, @@ -22,7 +21,11 @@ import type { import { Crypto, Signers } from '@kiltprotocol/utils' import { checkResultImpl } from './checkResult.js' -import { convertPublicKey, submitImpl } from './common.js' +import { + convertPublicKey, + extractSubmitterSignerAndAccount, + submitImpl, +} from './common.js' function implementsSignerInterface(input: any): input is SignerInterface { return 'algorithm' in input && 'id' in input && 'sign' in input @@ -44,15 +47,14 @@ export function createDid( submitOptions = {} ) => { const { fromPublicKey, submitter, signers, api } = options - const { signSubmittable = true } = submitOptions + const { signSubmittable = false } = submitOptions const { publicKey, type } = convertPublicKey(fromPublicKey) if (!signingMethodTypes.includes(type)) { throw new Error(`unknown key type ${type}`) } - const submitterAccount = ( - 'address' in submitter ? submitter.address : submitter.id - ) as KiltAddress + const { submitterSigner, submitterAccount } = + extractSubmitterSignerAndAccount(submitter) const accountSigners = ( await Promise.all( @@ -77,7 +79,10 @@ export function createDid( ) if (signSubmittable) { - didCreation = await Blockchain.signTx(didCreation, submitter) + if (typeof submitterSigner === 'undefined') { + throw new Error('submitter does not include a secret key') + } + didCreation = await Blockchain.signTx(didCreation, submitterSigner) } return { diff --git a/packages/sdk-js/src/DidHelpers/transact.spec.ts b/packages/sdk-js/src/DidHelpers/transact.spec.ts index 1c4541e8f..3b919d78a 100644 --- a/packages/sdk-js/src/DidHelpers/transact.spec.ts +++ b/packages/sdk-js/src/DidHelpers/transact.spec.ts @@ -76,7 +76,7 @@ describe('transact', () => { expectedEvents: [ { section: 'attestation', method: 'AttestationCreated' }, ], - }).getSubmittable({ signSubmittable: false }) + }).getSubmittable() expect(txHex).toContain('0x') const parsed = mockedApi.tx(txHex) @@ -118,4 +118,26 @@ describe('transact', () => { value: confirmed.toJSON(), }) }) + + it('creates an unsigned submittable', async () => { + const { txHex } = await transact({ + didDocument, + api: mockedApi, + submitter: keypair.address, + signers: [keypair], + call: mockedApi.tx.attestation.add( + new Uint8Array(32).fill(1), + new Uint8Array(32).fill(1), + null + ), + expectedEvents: [ + { section: 'attestation', method: 'AttestationCreated' }, + ], + }).getSubmittable({ signSubmittable: false }) + + expect(txHex).toContain('0x') + const parsed = mockedApi.tx(txHex) + expect(parsed.method).toHaveProperty('section', 'did') + expect(parsed.method).toHaveProperty('method', 'submitDidCall') + }) }) diff --git a/packages/sdk-js/src/DidHelpers/transact.ts b/packages/sdk-js/src/DidHelpers/transact.ts index ad0474043..d8b1f33b8 100644 --- a/packages/sdk-js/src/DidHelpers/transact.ts +++ b/packages/sdk-js/src/DidHelpers/transact.ts @@ -6,18 +6,15 @@ */ import type { Extrinsic } from '@polkadot/types/interfaces' - import { Blockchain } from '@kiltprotocol/chain-helpers' import { authorizeTx, signersForDid } from '@kiltprotocol/did' import type { - KiltAddress, - SharedArguments, SubmittableExtrinsic, + SharedArguments, TransactionHandlers, } from '@kiltprotocol/types' - +import { extractSubmitterSignerAndAccount, submitImpl } from './common.js' import { checkResultImpl } from './checkResult.js' -import { submitImpl } from './common.js' /** * Instructs a transaction (state transition) as this DID (with this DID as the origin). @@ -35,7 +32,7 @@ export function transactInternal( const getSubmittable: TransactionHandlers['getSubmittable'] = async ( submitOptions: | { - signSubmittable?: boolean // default: true + signSubmittable?: boolean // default: false didNonce?: number | BigInt } | undefined = {} @@ -48,13 +45,12 @@ export function transactInternal( api, expectedEvents, } = options - const { didNonce, signSubmittable = true } = submitOptions + const { didNonce, signSubmittable = false } = submitOptions const call = await callFactory() const didSigners = await signersForDid(didDocument, ...signers) - const submitterAccount = ( - 'address' in submitter ? submitter.address : submitter.id - ) as KiltAddress + const { submitterSigner, submitterAccount } = + extractSubmitterSignerAndAccount(submitter) let authorized: SubmittableExtrinsic = await authorizeTx( didDocument, @@ -69,7 +65,10 @@ export function transactInternal( ) if (signSubmittable) { - authorized = await Blockchain.signTx(authorized, submitter) + if (typeof submitterSigner === 'undefined') { + throw new Error('submitter does not include a secret key') + } + authorized = await Blockchain.signTx(authorized, submitterSigner) } return { diff --git a/packages/types/src/Address.ts b/packages/types/src/Address.ts index 7bbd2b6b7..50a31f085 100644 --- a/packages/types/src/Address.ts +++ b/packages/types/src/Address.ts @@ -26,10 +26,10 @@ export type KiltAddress = KiltKeyringPair['address'] declare module '@polkadot/keyring' { function encodeAddress( key: HexString | Uint8Array | string, - ss58Format?: Prefix - ): string + ss58Format: 38 + ): KiltAddress function encodeAddress( key: HexString | Uint8Array | string, - ss58Format?: 38 - ): KiltAddress + ss58Format?: Prefix + ): string } diff --git a/packages/types/src/DidHelpers.ts b/packages/types/src/DidHelpers.ts index 2ea7f82de..a2f776ebb 100644 --- a/packages/types/src/DidHelpers.ts +++ b/packages/types/src/DidHelpers.ts @@ -5,21 +5,19 @@ * found in the LICENSE file in the root directory of this source tree. */ +import type { ApiPromise } from '@polkadot/api' +import type { SubmittableResultValue } from '@polkadot/api/types' +import type { GenericEvent } from '@polkadot/types' import type { Multikey } from '@kiltprotocol/utils' -import type { DidDocument } from './Did' -import type { - ApiPromise, - GenericEvent, - HexString, - KeyringPair, - SubmittableResultValue, -} from './Imported' import type { MultibaseKeyPair, MultibasePublicKey, SignerInterface, TransactionSigner, } from './Signers' +import { HexString, KeyringPair } from './Imported.js' +import { DidDocument } from './Did.js' +import { KiltAddress } from './Address.js' export interface TransactionResult { status: 'confirmed' | 'failed' | 'rejected' | 'unknown' @@ -105,7 +103,7 @@ export type SharedArguments = { didDocument: DidDocument api: ApiPromise signers: DidHelpersAcceptedSigners[] - submitter: KeyringPair | TransactionSigner + submitter: KeyringPair | TransactionSigner | MultibaseKeyPair | KiltAddress } type PublicKeyAndType = { diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts index 1d7936b99..3e7c04b01 100644 --- a/tests/integration/didHelpers.spec.ts +++ b/tests/integration/didHelpers.spec.ts @@ -409,6 +409,25 @@ describe('transact', () => { Verifier.verifyCredential({ credential: issued }) ).resolves.toMatchObject({ verified: true }) }, 30_000) + + it('creates signed submittable', async () => { + const serialized = CType.toChain(cType) + const call = api.tx.ctype.add(serialized) + + const { txHex } = await DidHelpers.transact({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + call, + expectedEvents: [{ section: 'ctype', method: 'CTypeCreated' }], + }).getSubmittable({ signSubmittable: true }) + expect(txHex).toContain('0x') + const parsed = api.tx(txHex) + expect(parsed.method).toHaveProperty('section', 'did') + expect(parsed.method).toHaveProperty('method', 'submitDidCall') + expect(parsed.isSigned).toBe(true) + }, 30_000) }) afterAll(async () => { diff --git a/tests/integration/utils.ts b/tests/integration/utils.ts index c718a7e0d..be42dd2cc 100644 --- a/tests/integration/utils.ts +++ b/tests/integration/utils.ts @@ -111,7 +111,7 @@ export const devBob = Crypto.makeKeypairFromUri('//Bob') export const devCharlie = Crypto.makeKeypairFromUri('//Charlie') export function addressFromRandom(): KiltAddress { - return encodeAddress(randomAsU8a()) + return encodeAddress(randomAsU8a(), 38) } export async function isCtypeOnChain(cType: ICType): Promise {