Skip to content

Commit f90ee4d

Browse files
nialexsanjribbink
andauthored
PKG -- [sdk] TS voucher (#1803)
* Nialexsan/add types (#1710) * switch to uuid from @onflow/utils-uid * create tsconfigs * typedefs in ts * types for actor util * types for address util * Revert "switch to uuid from @onflow/utils-uid" This reverts commit 2a15ef5. * PKG -- [util-actor] converted to ts * updated tsconfig * fixed ts types generation * Resolve circular dependency * ts rlp * change type location * more types * build types during regular build * fix tests * VSN -- [root] Changeset * Merge remote-tracking branch 'origin/master' into nialexsan/add-types * Revert "Resolve circular dependency" This reverts commit 36efc7d. * update lock * VSN -- [root] changeset * Implement typescript for several branches & adjust configuration (#1750) Implements typescript support for: @onflow/rlp, @onflow/util-uid, @onflow/util-template, @onflow/util-logger, @onflow/util-invariant, @onflow/util-encode-key, @onflow/util-address * Convert @onflow/types to TS (#1760) * [WIP] Convert @onflow/types to TS * stash * kind of working * fix package.json * fix dictionary * fix tests * stash * strong type tests * remove any * rename * changeset * PKG -- [util-actor] Enhance TS support (#1761) * PKG -- [util-actor] Enhance TS support * rename handlerfnmap * PKG -- [types] Simplify generics for @onflow/types (#1772) * PKG -- [types] Simplify generics * Fix array * Fix Array * PKG -- [config] Convert @onflow/config to TS (#1731) * PKG -- [config] Add TypeScript * Changeset * fixup * Remove unnecessary generic from util-actor * remove non null assertions --------- Co-authored-by: Alex <[email protected]> * Fix JSDoc type generation (#1780) * restore changeset * align packages * pre typescript * any type for config * update package lock * PKG -- [util-encode-key] eslint ts config * more ts * move interaction types * fix types * more types * authz types * fix export * fix imports * fixed tests * fix paths * fix path * fix some tests * clean console log * fix logic * addressing PR comments * fix build arguments * shallow copy acct * convert encode * fix references * revert changes * ts resolve signature * fix resolve accounts and interaction types * fixed types * fix path to interactions * revert changes to changelogs * fix types for resolve-accounts * address comments * changeset * more descriptive names * PKG -- [sdk] rename interfaces * PKG -- [typedefs] fix merge conflict * PKG -- [typedefs] fix merge conflicts * PKG -- [sdk] fix interface imports --------- Co-authored-by: Jordan Ribbink <[email protected]>
1 parent 0d09d83 commit f90ee4d

File tree

20 files changed

+226
-139
lines changed

20 files changed

+226
-139
lines changed

packages/fcl/src/wallet-provider-spec/draft-v3.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ An authorization service is expected to know the Account and the Key that will b
471471
FCL will use the `method` provided to request an array of composite signature from authorization service (Wrapped in a `PollingResponse`).
472472
The authorization service will be sent a `Signable`.
473473
The service is expected to construct an encoded message to sign from `Signable.voucher`.
474-
It then needs to hash the encoded message, and prepend a required [transaction domain tag](https://github.com/onflow/flow-js-sdk/blob/master/packages/sdk/src/encode/encode.js#L12-L13).
474+
It then needs to hash the encoded message, and prepend a required [transaction domain tag](../../../sdk/src/encode/encode.ts#L12-L13).
475475
Finally it signs the payload with the user/s keys, producing a signature.
476476
This signature, as a HEX string, is sent back to FCL as part of the `CompositeSignature` which includes the user address and keyID in the data property of a `PollingResponse`.
477477

packages/fcl/src/wallet-provider-spec/draft-v4.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ An authorization service is expected to know the Account and the Key that will b
732732
FCL will use the `method` provided to request an array of composite signature from authorization service (Wrapped in a `PollingResponse`).
733733
The authorization service will be sent a `Signable`.
734734
The service is expected to construct an encoded message to sign from `Signable.voucher`.
735-
It then needs to hash the encoded message, and prepend a required [transaction domain tag](https://github.com/onflow/flow-js-sdk/blob/master/packages/sdk/src/encode/encode.js#L12-L13).
735+
It then needs to hash the encoded message, and prepend a required [transaction domain tag](../../../sdk/src/encode/encode.ts#L12-L13).
736736
Finally it signs the payload with the user/s keys, producing a signature.
737737
This signature, as a HEX string, is sent back to FCL as part of the `CompositeSignature` which includes the user address and keyID in the data property of a `PollingResponse`.
738738

packages/rlp/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {Buffer} from "buffer"
22

33
export {Buffer}
44

5-
type EncodeInput =
5+
export type EncodeInput =
66
| Buffer
77
| string
88
| number

packages/sdk/readme.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ const response = await sdk.send(await sdk.build([
509509
- [`sdk.getCollection`](./src/build/build-get-collection)
510510
- [`sdk.getEvents`](./src/build/build-get-events.js)
511511
- [`sdk.getEventsAtBlockHeightRange`](./src/build/build-get-events-at-block-height-range.js)
512-
- [`sdk.getEventsAtBlockIds`](./src/build/build-get-events-at-block-ids)
512+
- [`sdk.getEventsAtBlockIds`](./src/build/build-get-events-at-block-ids.js)
513513
- [`sdk.getLatestBlock`](./src/build/build-get-latest-block.js)
514514
- [`sdk.getTransactionStatus`](./src/build/build-get-transaction-status.js)
515515
- [`sdk.getTransaction`](./src/build/build-get-transaction.js)
@@ -526,15 +526,15 @@ const response = await sdk.send(await sdk.build([
526526
- [`sdk.validator`](./src/build/build-validator.js)
527527

528528
- [Resolvers](./src/resolve)
529-
- [`sdk.resolveAccounts`](./src/resolve/resolve-accounts.js)
529+
- [`sdk.resolveAccounts`](./src/resolve/resolve-accounts.ts)
530530
- [`sdk.resolveArguments`](./src/resolve/resolve-arguments.js)
531531
- [`sdk.resolveCadence`](./src/resolve/resolve-cadence.js)
532532
- [`sdk.resolveFinalNormalization`](./src/resolve/resolve-final-normalization.js)
533533
- [`sdk.resolveVoucherIntercept`](./src/resolve/resolve-voucher-intercept.js)
534534
- [`sdk.resolveProposerSequenceNumber`](./src/resolve/resolve-proposer-sequence-number.js)
535535
- [`sdk.resolveRefBlockId`](./src/resolve/resolve-ref-block-id.js)
536-
- [`sdk.resolveSignatures`](./src/resolve/resolve-signatures.js)
536+
- [`sdk.resolveSignatures`](./src/resolve/resolve-signatures.ts)
537537
- [`sdk.resolveValidators`](./src/resolve/resolve-validators.js)
538538

539539
- [Other Utils](./src/)
540-
- [`sdk.voucherToTxId`](./src/resolve/voucher.js)
540+
- [`sdk.voucherToTxId`](./src/resolve/voucher.ts)

packages/sdk/src/contract.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as root from "./sdk"
22
import * as decode from "./decode/decode.js"
3-
import * as encode from "./encode/encode.js"
3+
import * as encode from "./encode/encode"
44
import * as interaction from "./interaction/interaction"
55
import * as send from "./send/send.js"
66
import * as template from "@onflow/util-template"

packages/sdk/src/encode/encode.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import {
44
encodeTransactionPayload,
55
encodeTransactionEnvelope,
66
encodeTxIdFromVoucher,
7-
} from "./encode.js"
8-
import * as root from "./encode.js"
7+
} from "./encode"
8+
import * as root from "./encode"
99

1010
it("export contract interface", () => {
1111
expect(root).toStrictEqual(
Lines changed: 113 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,73 @@
11
import {SHA3} from "sha3"
2-
import {encode, Buffer} from "@onflow/rlp"
2+
import {encode, Buffer, EncodeInput} from "@onflow/rlp"
33
import {sansPrefix} from "@onflow/util-address"
44

5-
export const encodeTransactionPayload = tx =>
5+
export const encodeTransactionPayload = (tx: Transaction) =>
66
prependTransactionDomainTag(rlpEncode(preparePayload(tx)))
7-
export const encodeTransactionEnvelope = tx =>
7+
export const encodeTransactionEnvelope = (tx: Transaction) =>
88
prependTransactionDomainTag(rlpEncode(prepareEnvelope(tx)))
9-
export const encodeTxIdFromVoucher = voucher =>
9+
export const encodeTxIdFromVoucher = (voucher: Voucher) =>
1010
sha3_256(rlpEncode(prepareVoucher(voucher)))
1111

12-
const rightPaddedHexBuffer = (value, pad) =>
13-
Buffer.from(value.padEnd(pad * 2, 0), "hex")
12+
const rightPaddedHexBuffer = (value: string, pad: number) =>
13+
Buffer.from(value.padEnd(pad * 2, "0"), "hex")
1414

15-
const leftPaddedHexBuffer = (value, pad) =>
16-
Buffer.from(value.padStart(pad * 2, 0), "hex")
15+
const leftPaddedHexBuffer = (value: string, pad: number) =>
16+
Buffer.from(value.padStart(pad * 2, "0"), "hex")
1717

1818
const TRANSACTION_DOMAIN_TAG = rightPaddedHexBuffer(
1919
Buffer.from("FLOW-V0.0-transaction").toString("hex"),
2020
32
2121
).toString("hex")
22-
const prependTransactionDomainTag = tx => TRANSACTION_DOMAIN_TAG + tx
22+
const prependTransactionDomainTag = (tx: string) => TRANSACTION_DOMAIN_TAG + tx
2323

24-
const addressBuffer = addr => leftPaddedHexBuffer(addr, 8)
24+
const addressBuffer = (addr: string) => leftPaddedHexBuffer(addr, 8)
2525

26-
const blockBuffer = block => leftPaddedHexBuffer(block, 32)
26+
const blockBuffer = (block: string) => leftPaddedHexBuffer(block, 32)
2727

28-
const argumentToString = arg => Buffer.from(JSON.stringify(arg), "utf8")
28+
const argumentToString = (arg: Record<string, any>) => Buffer.from(JSON.stringify(arg), "utf8")
2929

30-
const scriptBuffer = script => Buffer.from(script, "utf8")
31-
const signatureBuffer = signature => Buffer.from(signature, "hex")
30+
const scriptBuffer = (script: string) => Buffer.from(script, "utf8")
31+
const signatureBuffer = (signature: string) => Buffer.from(signature, "hex")
3232

33-
const rlpEncode = v => {
33+
const rlpEncode = (v: EncodeInput) => {
3434
return encode(v).toString("hex")
3535
}
3636

37-
const sha3_256 = msg => {
37+
const sha3_256 = (msg: string) => {
3838
const sha = new SHA3(256)
3939
sha.update(Buffer.from(msg, "hex"))
4040
return sha.digest().toString("hex")
4141
}
4242

43-
const preparePayload = tx => {
43+
const preparePayload = (tx: Transaction) => {
4444
validatePayload(tx)
4545

4646
return [
47-
scriptBuffer(tx.cadence),
47+
scriptBuffer(tx.cadence || ''),
4848
tx.arguments.map(argumentToString),
49-
blockBuffer(tx.refBlock),
49+
blockBuffer(tx.refBlock || ''),
5050
tx.computeLimit,
51-
addressBuffer(sansPrefix(tx.proposalKey.address)),
51+
addressBuffer(sansPrefix(tx.proposalKey.address || '')),
5252
tx.proposalKey.keyId,
5353
tx.proposalKey.sequenceNum,
5454
addressBuffer(sansPrefix(tx.payer)),
5555
tx.authorizers.map(authorizer => addressBuffer(sansPrefix(authorizer))),
5656
]
5757
}
5858

59-
const prepareEnvelope = tx => {
59+
const prepareEnvelope = (tx: Transaction) => {
6060
validateEnvelope(tx)
6161

6262
return [preparePayload(tx), preparePayloadSignatures(tx)]
6363
}
6464

65-
const preparePayloadSignatures = tx => {
65+
const preparePayloadSignatures = (tx: Transaction) => {
6666
const signers = collectSigners(tx)
6767

68-
return tx.payloadSigs
69-
.map(sig => {
68+
return tx.payloadSigs?.map((sig: Sig) => {
7069
return {
71-
signerIndex: signers.get(sig.address),
70+
signerIndex: signers.get(sig.address) || '',
7271
keyId: sig.keyId,
7372
sig: sig.sig,
7473
}
@@ -79,45 +78,51 @@ const preparePayloadSignatures = tx => {
7978

8079
if (a.keyId > b.keyId) return 1
8180
if (a.keyId < b.keyId) return -1
81+
82+
return 0
8283
})
8384
.map(sig => {
8485
return [sig.signerIndex, sig.keyId, signatureBuffer(sig.sig)]
8586
})
8687
}
8788

88-
const collectSigners = tx => {
89-
const signers = new Map()
89+
const collectSigners = (tx: Voucher | Transaction) => {
90+
const signers = new Map<string, number>()
9091
let i = 0
9192

92-
const addSigner = addr => {
93+
const addSigner = (addr: string) => {
9394
if (!signers.has(addr)) {
9495
signers.set(addr, i)
9596
i++
9697
}
9798
}
9899

99-
addSigner(tx.proposalKey.address)
100+
if (tx.proposalKey.address){
101+
addSigner(tx.proposalKey.address)
102+
}
100103
addSigner(tx.payer)
101104
tx.authorizers.forEach(addSigner)
102105

103106
return signers
104107
}
105108

106-
const prepareVoucher = voucher => {
109+
const prepareVoucher = (voucher: Voucher) => {
107110
validateVoucher(voucher)
108111

109112
const signers = collectSigners(voucher)
110113

111-
const prepareSigs = sigs => {
114+
const prepareSigs = (sigs: Sig[]) => {
112115
return sigs
113-
.map(({address, keyId, sig}) => {
114-
return {signerIndex: signers.get(address), keyId, sig}
116+
.map(({ address, keyId, sig }) => {
117+
return { signerIndex: signers.get(address) || '', keyId, sig }
115118
})
116119
.sort((a, b) => {
117120
if (a.signerIndex > b.signerIndex) return 1
118121
if (a.signerIndex < b.signerIndex) return -1
119122
if (a.keyId > b.keyId) return 1
120123
if (a.keyId < b.keyId) return -1
124+
125+
return 0
121126
})
122127
.map(sig => {
123128
return [sig.signerIndex, sig.keyId, signatureBuffer(sig.sig)]
@@ -143,23 +148,23 @@ const prepareVoucher = voucher => {
143148
]
144149
}
145150

146-
const validatePayload = tx => {
151+
const validatePayload = (tx: Transaction) => {
147152
payloadFields.forEach(field => checkField(tx, field))
148153
proposalKeyFields.forEach(field =>
149154
checkField(tx.proposalKey, field, "proposalKey")
150155
)
151156
}
152157

153-
const validateEnvelope = tx => {
158+
const validateEnvelope = (tx: Transaction) => {
154159
payloadSigsFields.forEach(field => checkField(tx, field))
155-
tx.payloadSigs.forEach((sig, index) => {
160+
tx.payloadSigs?.forEach((sig, index) => {
156161
payloadSigFields.forEach(field =>
157162
checkField(sig, field, "payloadSigs", index)
158163
)
159164
})
160165
}
161166

162-
const validateVoucher = voucher => {
167+
const validateVoucher = (voucher: Voucher) => {
163168
payloadFields.forEach(field => checkField(voucher, field))
164169
proposalKeyFields.forEach(field =>
165170
checkField(voucher.proposalKey, field, "proposalKey")
@@ -178,12 +183,64 @@ const validateVoucher = voucher => {
178183
})
179184
}
180185

181-
const isNumber = v => typeof v === "number"
182-
const isString = v => typeof v === "string"
183-
const isObject = v => v !== null && typeof v === "object"
184-
const isArray = v => isObject(v) && v instanceof Array
186+
const isNumber = (v: any): v is number => typeof v === "number"
187+
const isString = (v: any): v is string => typeof v === "string"
188+
const isObject = (v: any) => v !== null && typeof v === "object"
189+
const isArray = (v: any) => isObject(v) && v instanceof Array
190+
191+
interface VoucherArgument {
192+
type: string
193+
value: string
194+
}
195+
196+
interface VoucherProposalKey {
197+
address: string
198+
keyId: number | null
199+
sequenceNum: number | null
200+
}
201+
202+
interface Sig {
203+
address: string,
204+
keyId: number | string,
205+
sig: string,
206+
}
207+
208+
export interface TransactionProposalKey {
209+
address?: string
210+
keyId?: number | string
211+
sequenceNum?: number
212+
}
213+
export interface Transaction {
214+
cadence: string | null;
215+
refBlock: string | null;
216+
computeLimit: string | null;
217+
arguments: VoucherArgument[]
218+
proposalKey: TransactionProposalKey
219+
payer: string
220+
authorizers: string[]
221+
payloadSigs?: Sig[]
222+
envelopeSigs?: TransactionProposalKey[]
223+
}
224+
225+
export interface Voucher {
226+
cadence: string;
227+
refBlock: string;
228+
computeLimit: number;
229+
arguments: VoucherArgument[]
230+
proposalKey: VoucherProposalKey
231+
payer: string
232+
authorizers: string[]
233+
payloadSigs: Sig[]
234+
envelopeSigs: Sig[]
235+
}
236+
237+
interface PayloadField {
238+
name: string,
239+
check: (v: any) => boolean,
240+
defaultVal?: string
241+
}
185242

186-
const payloadFields = [
243+
const payloadFields: PayloadField[] = [
187244
{name: "cadence", check: isString},
188245
{name: "arguments", check: isArray},
189246
{name: "refBlock", check: isString, defaultVal: "0"},
@@ -193,42 +250,47 @@ const payloadFields = [
193250
{name: "authorizers", check: isArray},
194251
]
195252

196-
const proposalKeyFields = [
253+
const proposalKeyFields: PayloadField[] = [
197254
{name: "address", check: isString},
198255
{name: "keyId", check: isNumber},
199256
{name: "sequenceNum", check: isNumber},
200257
]
201258

202-
const payloadSigsFields = [{name: "payloadSigs", check: isArray}]
259+
const payloadSigsFields: PayloadField[] = [{name: "payloadSigs", check: isArray}]
203260

204-
const payloadSigFields = [
261+
const payloadSigFields: PayloadField[] = [
205262
{name: "address", check: isString},
206263
{name: "keyId", check: isNumber},
207264
{name: "sig", check: isString},
208265
]
209266

210-
const envelopeSigsFields = [{name: "envelopeSigs", check: isArray}]
267+
const envelopeSigsFields: PayloadField[] = [{ name: "envelopeSigs", check: isArray }]
211268

212-
const envelopeSigFields = [
269+
const envelopeSigFields: PayloadField[] = [
213270
{name: "address", check: isString},
214271
{name: "keyId", check: isNumber},
215272
{name: "sig", check: isString},
216273
]
217274

218-
const checkField = (obj, field, base, index) => {
275+
const checkField = (
276+
obj: Record<string, any>,
277+
field: PayloadField,
278+
base?: string,
279+
index?: number
280+
) => {
219281
const {name, check, defaultVal} = field
220282
if (obj[name] == null && defaultVal != null) obj[name] = defaultVal
221283
if (obj[name] == null) throw missingFieldError(name, base, index)
222284
if (!check(obj[name])) throw invalidFieldError(name, base, index)
223285
}
224286

225-
const printFieldName = (field, base, index) => {
287+
const printFieldName = (field: string, base?: string, index?: number) => {
226288
if (!!base)
227289
return index == null ? `${base}.${field}` : `${base}.${index}.${field}`
228290
return field
229291
}
230292

231-
const missingFieldError = (field, base, index) =>
293+
const missingFieldError = (field: string, base?: string, index?: number) =>
232294
new Error(`Missing field ${printFieldName(field, base, index)}`)
233-
const invalidFieldError = (field, base, index) =>
295+
const invalidFieldError = (field: string, base?: string, index?: number) =>
234296
new Error(`Invalid field ${printFieldName(field, base, index)}`)

0 commit comments

Comments
 (0)