This repository has been archived by the owner on Jan 16, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #147 from xmtp/nm/open-frames-support
Add OpenFrames support
- Loading branch information
Showing
12 changed files
with
464 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@xmtp/frames-validator": minor | ||
--- | ||
|
||
Adds support for an Open Frames validator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ jobs: | |
- name: Install Yarn v3 | ||
run: | | ||
corepack enable | ||
corepack prepare [email protected].0 --activate | ||
corepack prepare [email protected].2 --activate | ||
- run: yarn | ||
- run: yarn build | ||
- run: yarn lint |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,7 @@ jobs: | |
- name: Install Yarn v3 | ||
run: | | ||
corepack enable | ||
corepack prepare [email protected].0 --activate | ||
corepack prepare [email protected].2 --activate | ||
- name: Install dependencies | ||
run: yarn | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ jobs: | |
- name: Install Yarn v4 | ||
run: | | ||
corepack enable | ||
corepack prepare [email protected].0 --activate | ||
corepack prepare [email protected].2 --activate | ||
- run: yarn | ||
- run: yarn build | ||
- run: yarn workspace @xmtp/bot-kit-pro run up | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,8 +24,10 @@ | |
"provenance": true | ||
}, | ||
"dependencies": { | ||
"@xmtp/proto": "3.41.0-beta.5", | ||
"@xmtp/xmtp-js": "^11.3.5" | ||
"@noble/hashes": "^1.3.3", | ||
"@noble/secp256k1": "^2.0.0", | ||
"@xmtp/proto": "3.41.0-beta.6", | ||
"viem": "^2.7.8" | ||
}, | ||
"scripts": { | ||
"clean": "rm -rf dist", | ||
|
@@ -41,8 +43,10 @@ | |
"homepage": "https://github.com/xmtp/xmtp-node-js-tools#readme", | ||
"packageManager": "[email protected]", | ||
"devDependencies": { | ||
"@open-frames/types": "^0.0.6", | ||
"@rollup/plugin-typescript": "^11.1.6", | ||
"@xmtp/frames-client": "^0.2.0", | ||
"@xmtp/frames-client": "^0.2.2", | ||
"@xmtp/xmtp-js": "^11.3.5", | ||
"ethers": "^6.10.0", | ||
"rollup": "^4.9.6", | ||
"rollup-plugin-dts": "^6.1.0", | ||
|
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,100 +1,2 @@ | ||
import { fetcher, frames } from "@xmtp/proto" | ||
import { Signature, SignedPublicKeyBundle } from "@xmtp/xmtp-js" | ||
|
||
import { sha256 } from "./crypto.js" | ||
import { FramePostPayload, FramePostUntrustedData } from "./types.js" | ||
export * from "./types.js" | ||
|
||
const { b64Decode } = fetcher | ||
|
||
export async function validateFramesPost(data: FramePostPayload) { | ||
const { untrustedData, trustedData } = data | ||
const { walletAddress } = untrustedData | ||
const { messageBytes: messageBytesString } = trustedData | ||
|
||
const messageBytes = b64Decode(messageBytesString) | ||
|
||
const { actionBody, actionBodyBytes, signature, signedPublicKeyBundle } = | ||
deserializeProtoMessage(messageBytes) | ||
|
||
const verifiedWalletAddress = await getVerifiedWalletAddress( | ||
actionBodyBytes, | ||
signature, | ||
signedPublicKeyBundle, | ||
) | ||
|
||
if (verifiedWalletAddress !== walletAddress) { | ||
throw new Error("Invalid wallet address") | ||
} | ||
|
||
await checkUntrustedData(untrustedData, actionBody) | ||
|
||
return { | ||
actionBody, | ||
verifiedWalletAddress, | ||
} | ||
} | ||
|
||
export function deserializeProtoMessage(messageBytes: Uint8Array) { | ||
const frameAction = frames.FrameAction.decode(messageBytes) | ||
if (!frameAction.signature || !frameAction.signedPublicKeyBundle) { | ||
throw new Error( | ||
"Invalid frame action: missing signature or signed public key bundle", | ||
) | ||
} | ||
const actionBody = frames.FrameActionBody.decode(frameAction.actionBody) | ||
|
||
return { | ||
actionBody, | ||
actionBodyBytes: frameAction.actionBody, | ||
signature: new Signature(frameAction.signature), | ||
signedPublicKeyBundle: new SignedPublicKeyBundle( | ||
frameAction.signedPublicKeyBundle, | ||
), | ||
} | ||
} | ||
|
||
async function getVerifiedWalletAddress( | ||
actionBodyBytes: Uint8Array, | ||
signature: Signature, | ||
signedPublicKeyBundle: SignedPublicKeyBundle, | ||
): Promise<string> { | ||
const isValid = signedPublicKeyBundle.identityKey.verify( | ||
signature, | ||
await sha256(actionBodyBytes), | ||
) | ||
|
||
if (!isValid) { | ||
throw new Error("Invalid signature") | ||
} | ||
|
||
return signedPublicKeyBundle.walletSignatureAddress() | ||
} | ||
|
||
async function checkUntrustedData( | ||
{ | ||
url, | ||
buttonIndex, | ||
opaqueConversationIdentifier, | ||
timestamp, | ||
}: FramePostUntrustedData, | ||
actionBody: frames.FrameActionBody, | ||
) { | ||
if (actionBody.frameUrl !== url) { | ||
throw new Error("Mismatched URL") | ||
} | ||
|
||
if (actionBody.buttonIndex !== buttonIndex) { | ||
throw new Error("Mismatched button index") | ||
} | ||
|
||
if ( | ||
actionBody.opaqueConversationIdentifier !== opaqueConversationIdentifier | ||
) { | ||
throw new Error("Mismatched conversation identifier") | ||
} | ||
|
||
if (actionBody.timestamp.toNumber() !== timestamp) { | ||
throw new Error("Mismatched timestamp") | ||
} | ||
} | ||
export * from "./openFrames.js" | ||
export * from "./validation.js" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { | ||
OpenFramesRequest, | ||
RequestValidator, | ||
ValidationResponse, | ||
} from "@open-frames/types" | ||
|
||
import { XmtpOpenFramesRequest, XmtpValidationResponse } from "./types" | ||
import { validateFramesPost } from "./validation" | ||
|
||
export class XmtpValidator | ||
implements | ||
RequestValidator<XmtpOpenFramesRequest, XmtpValidationResponse, "xmtp"> | ||
{ | ||
readonly protocolIdentifier = "xmtp" | ||
readonly minProtocolVersionDate = "2024-02-09" | ||
|
||
minProtocolVersion(): string { | ||
return `${this.protocolIdentifier}@${this.minProtocolVersionDate}` | ||
} | ||
|
||
isSupported(payload: OpenFramesRequest): payload is XmtpOpenFramesRequest { | ||
if (!payload.clientProtocol) { | ||
return false | ||
} | ||
|
||
const [protocol, version] = payload.clientProtocol.split("@") | ||
if (!protocol || !version) { | ||
return false | ||
} | ||
|
||
const isCorrectClientProtocol = protocol === "xmtp" | ||
const isCorrectVersion = version >= this.minProtocolVersionDate | ||
const isTrustedDataValid = | ||
typeof payload.trustedData?.messageBytes === "string" | ||
|
||
return isCorrectClientProtocol && isCorrectVersion && isTrustedDataValid | ||
} | ||
|
||
async validate( | ||
payload: XmtpOpenFramesRequest, | ||
): Promise< | ||
ValidationResponse<XmtpValidationResponse, typeof this.protocolIdentifier> | ||
> { | ||
try { | ||
const validationResponse = await validateFramesPost(payload) | ||
return { | ||
isValid: true, | ||
clientProtocol: payload.clientProtocol, | ||
message: validationResponse, | ||
} | ||
} catch (error) { | ||
return { | ||
isValid: false, | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,21 @@ | ||
export type FramePostUntrustedData = { | ||
import type { | ||
OpenFramesTrustedData, | ||
OpenFramesUntrustedData, | ||
} from "@open-frames/types" | ||
import { frames } from "@xmtp/proto" | ||
|
||
export type UntrustedData = OpenFramesUntrustedData & { | ||
walletAddress: string // Untrusted version of the wallet address | ||
url: string // Frame URL. May be different from the `post_url` this is being sent to | ||
timestamp: number // Timestamp in milliseconds | ||
buttonIndex: number // 1-indexed button that was clicked | ||
opaqueConversationIdentifier: string // A hash of the conversation topic and the participants | ||
} | ||
|
||
export type FramePostTrustedData = { | ||
messageBytes: string | ||
export type XmtpOpenFramesRequest = { | ||
clientProtocol: `xmtp@${string}` | ||
untrustedData: UntrustedData | ||
trustedData: OpenFramesTrustedData | ||
} | ||
|
||
export type FramePostPayload = { | ||
untrustedData: FramePostUntrustedData | ||
trustedData: FramePostTrustedData | ||
export type XmtpValidationResponse = { | ||
actionBody: frames.FrameActionBody | ||
verifiedWalletAddress: string | ||
} |
Oops, something went wrong.