From f31eb7689ee6f7d17a5856c4ca82df97cdc05149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Rivera?= Date: Thu, 13 Aug 2026 16:02:14 +0200 Subject: [PATCH 1/6] feat(auth0): sign NEAR Intents via NEP-413 payloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a third signing payload type alongside `transaction` and `delegateAction`, so a client can ask the MPC to sign a NEP-413 off-chain message. Why: both existing payload types are NEAR transactions, and every NEAR transaction needs an on-chain account that pays gas. Intents do not — a signed intent is published to the solver relay, which executes it against the intents contract and pays the gas itself. Supporting NEP-413 therefore lets an app operate on NEAR Intents with no account creation and no gas for the user. The guard contract is untouched: it only checks that `fatxn` equals the bytes handed to the MPC and never inspects them, so the payload type is entirely an Auth0-side concern. Because the contract cannot vet the payload, the action does, and refuses to render anything it cannot describe to the user: - the NEP-413 domain tag (2^31 + 413) must be present, so the bytes can never double as a NEAR transaction; - the recipient must be the expected verifier, configurable per tenant via the INTENTS_RECIPIENT secret (defaults to intents.near); - the message must be JSON carrying a non-empty `intents` array. Also rejects requests carrying more than one payload type. Previously a transaction and a delegate action could arrive together and precedence decided silently — which would let a caller display one payload while a different one landed in `fatxn`. Replay protection needs nothing here: the verifier tracks spent nonces on-chain. Changes: - packages/auth0: `intent` payload handling, the Intent approval form, and intent renderers in the shared form helpers - packages/providers/javascript: NEP-413 serializer and `requestIntentSignature()` - packages/sdks/browser: expose it on the signer - packages/shared/core: optional `requestIntentSignature` on the provider interface, so providers that have not implemented it are unaffected Deploying needs a new INTENT_FORM secret pointing at the imported form, and optionally INTENTS_RECIPIENT on non-mainnet tenants. Co-Authored-By: Claude Opus 5 (1M context) --- .../auth0/src/actions/authorize-app.action.js | 128 ++++++++++++- packages/auth0/src/forms/build.js | 1 + .../delegate_action/delegate_action_form.json | 6 +- .../auth0/src/forms/intent/details/index.css | 153 +++++++++++++++ .../auth0/src/forms/intent/details/index.js | 25 +++ .../auth0/src/forms/intent/intent_form.json | 123 ++++++++++++ .../src/forms/intent/intent_form_base.json | 112 +++++++++++ .../auth0/src/forms/shared/helpers/index.js | 119 ++++++++++++ .../forms/transaction/transaction_form.json | 6 +- packages/auth0/test/fixtures/builders.js | 67 +++++++ packages/auth0/test/intent-decoding.spec.js | 125 +++++++++++++ packages/auth0/test/intent-handlers.spec.js | 177 ++++++++++++++++++ packages/auth0/test/intent-helpers.spec.js | 122 ++++++++++++ packages/providers/javascript/package.json | 1 + packages/providers/javascript/src/index.ts | 1 + packages/providers/javascript/src/nep413.ts | 84 +++++++++ packages/providers/javascript/src/provider.ts | 60 ++++++ packages/providers/javascript/src/types.ts | 15 ++ .../providers/javascript/test/nep413.spec.ts | 99 ++++++++++ .../javascript/test/provider.spec.ts | 88 +++++++++ packages/sdks/browser/src/signers/signer.ts | 12 ++ packages/shared/core/src/index.ts | 1 + packages/shared/core/src/provider.ts | 11 ++ pnpm-lock.yaml | 3 + 24 files changed, 1531 insertions(+), 8 deletions(-) create mode 100644 packages/auth0/src/forms/intent/details/index.css create mode 100644 packages/auth0/src/forms/intent/details/index.js create mode 100644 packages/auth0/src/forms/intent/intent_form.json create mode 100644 packages/auth0/src/forms/intent/intent_form_base.json create mode 100644 packages/auth0/test/intent-decoding.spec.js create mode 100644 packages/auth0/test/intent-handlers.spec.js create mode 100644 packages/auth0/test/intent-helpers.spec.js create mode 100644 packages/providers/javascript/src/nep413.ts create mode 100644 packages/providers/javascript/test/nep413.spec.ts diff --git a/packages/auth0/src/actions/authorize-app.action.js b/packages/auth0/src/actions/authorize-app.action.js index e4279d1..c1ae9dd 100644 --- a/packages/auth0/src/actions/authorize-app.action.js +++ b/packages/auth0/src/actions/authorize-app.action.js @@ -5,6 +5,22 @@ const { deserialize } = require("borsh"); const TRANSACTION_KEY = "transaction"; const DELEGATE_ACTION_KEY = "delegateAction"; +const INTENT_KEY = "intent"; + +// NEP-413 CONSTANTS + +/** + * Domain-separation tag mandated by NEP-413 (2^31 + 413). It is prepended to every signed + * off-chain message so the bytes can never be reinterpreted as a NEAR transaction, whose + * borsh encoding starts with the signerId length — a small u32. Verifying this tag is what + * keeps the new payload type from becoming a way to smuggle transaction bytes past the user. + * + * https://github.com/near/NEPs/blob/master/neps/nep-0413.md#how-to-ensure-the-message-is-not-a-transaction + */ +const NEP413_PREFIX_TAG = 2147484061; + +/** Default verifier the intents are allowed to target when no secret is configured. */ +const DEFAULT_INTENTS_RECIPIENT = "intents.near"; // SCHEMA definitions const SCHEMA = new (class BorshSchema { @@ -177,6 +193,20 @@ const SCHEMA = new (class BorshSchema { signature: this.Signature, }, }; + /** + * NEP-413 off-chain message payload. Field order is normative — it must match the + * serializer the client signs with, or the recovered bytes will not match `fatxn`. + * https://github.com/near/NEPs/blob/master/neps/nep-0413.md#input-interface + */ + NEP413Payload = { + struct: { + tag: "u32", + message: "string", + nonce: { array: { type: "u8", len: 32 } }, + recipient: "string", + callbackUrl: { option: "string" }, + }, + }; })(); // UTILS @@ -203,6 +233,62 @@ function decodeDelegateAction(encodedDelegateAction) { return delegateAction; } +/** + * Decode and validate a NEP-413 intent payload arriving on the authorize query string. + * + * Every check here is load-bearing for the security model. The guard contract only verifies + * that `fatxn` equals the bytes the MPC is asked to sign — it never inspects them — so this + * function is the only place that establishes *what* the user is being asked to approve: + * + * 1. The borsh payload must deserialize cleanly under the NEP-413 schema. + * 2. The domain tag must be exactly NEP413_PREFIX_TAG, so the bytes cannot also be a valid + * NEAR transaction. Never trust the caller to have set it. + * 3. The recipient must be the expected verifier, so a signed intent cannot be redirected + * to a different contract. + * 4. The message must be JSON carrying a non-empty `intents` array — otherwise there is + * nothing meaningful to show the user, and an unrenderable payload must not be signed. + * + * @param {string} encodedIntent Comma-separated byte string from the query. + * @param {string} expectedRecipient Verifier account the intents must target. + * @returns {{payload: object, message: object}} The decoded payload and parsed message. + * @throws {Error} With a user-facing reason when any check fails. + */ +function decodeIntent(encodedIntent, expectedRecipient) { + const bytes = Uint8Array.from(String(encodedIntent).split(",").map((value) => Number(value))); + + let payload; + try { + payload = deserialize(SCHEMA.NEP413Payload, bytes); + } catch (e) { + throw new Error("Intent payload is not a valid NEP-413 message"); + } + + if (payload.tag !== NEP413_PREFIX_TAG) { + throw new Error("Intent payload is missing the NEP-413 domain tag"); + } + + if (payload.recipient !== expectedRecipient) { + throw new Error(`Intent payload targets an unexpected recipient: ${payload.recipient}`); + } + + let message; + try { + message = JSON.parse(payload.message); + } catch (e) { + throw new Error("Intent message is not valid JSON"); + } + + if (!message || !Array.isArray(message.intents) || message.intents.length === 0) { + throw new Error("Intent message carries no intents to approve"); + } + + return { payload, message }; +} + +function stringifyIntents(intents) { + return JSON.stringify(intents, (_, value) => (typeof value === "bigint" ? value.toString() : value), 2); +} + function stringifyActions(actions) { return JSON.stringify( actions, @@ -230,7 +316,9 @@ exports.onExecutePostLogin = async (event, api) => { const isOnchainAudience = event.resource_server?.identifier === onchainAudience; const hasTxParams = TRANSACTION_KEY in query; const hasDelegateParams = DELEGATE_ACTION_KEY in query; - const hasSigningPayload = hasTxParams || hasDelegateParams; + const hasIntentParams = INTENT_KEY in query; + const payloadCount = [hasTxParams, hasDelegateParams, hasIntentParams].filter(Boolean).length; + const hasSigningPayload = payloadCount > 0; if (isOnchainAudience && !hasSigningPayload) { return api.access.deny("Signing audience requested without transaction payload"); @@ -240,6 +328,12 @@ exports.onExecutePostLogin = async (event, api) => { } if (!isOnchainAudience) return; + // Exactly one payload may be present. Accepting several and silently picking by precedence + // would let a caller show the user one payload while a different one lands in `fatxn`. + if (payloadCount > 1) { + return api.access.deny("Only one signing payload may be requested at a time"); + } + // Strip OIDC profile scopes from the issued access token. // // @auth0/auth0-spa-js unions per-call scope with its built-in default ("openid profile email"), @@ -280,7 +374,7 @@ exports.onExecutePostLogin = async (event, api) => { "fatxn", query.transaction.split(",").map((value) => Number(value)), ); - } else { + } else if (hasDelegateParams) { const delegateAction = decodeDelegateAction(query.delegateAction); api.prompt.render(event.secrets.DELEGATE_ACTION_FORM, { fields: { @@ -295,6 +389,32 @@ exports.onExecutePostLogin = async (event, api) => { "fatxn", query.delegateAction.split(",").map((value) => Number(value)), ); + } else { + const expectedRecipient = event.secrets.INTENTS_RECIPIENT || DEFAULT_INTENTS_RECIPIENT; + + let decoded; + try { + decoded = decodeIntent(query.intent, expectedRecipient); + } catch (error) { + // A payload we cannot decode is a payload we cannot show the user. Signing it would + // break the consent guarantee the whole flow rests on, so refuse instead. + return api.access.deny(error.message); + } + + const { payload, message } = decoded; + api.prompt.render(event.secrets.INTENT_FORM, { + fields: { + ...branding, + signerId: message.signer_id ?? "", + recipient: payload.recipient, + deadline: message.deadline ?? "", + intents: stringifyIntents(message.intents), + }, + }); + api.accessToken.setCustomClaim( + "fatxn", + query.intent.split(",").map((value) => Number(value)), + ); } }; @@ -317,5 +437,9 @@ exports.onContinuePostLogin = async (event, api) => { // `onContinuePostLogin`; extra exports are inert in production. exports.parseTransaction = parseTransaction; exports.decodeDelegateAction = decodeDelegateAction; +exports.decodeIntent = decodeIntent; exports.stringifyActions = stringifyActions; +exports.stringifyIntents = stringifyIntents; exports.SCHEMA = SCHEMA; +exports.NEP413_PREFIX_TAG = NEP413_PREFIX_TAG; +exports.DEFAULT_INTENTS_RECIPIENT = DEFAULT_INTENTS_RECIPIENT; diff --git a/packages/auth0/src/forms/build.js b/packages/auth0/src/forms/build.js index 7165640..5e5f520 100644 --- a/packages/auth0/src/forms/build.js +++ b/packages/auth0/src/forms/build.js @@ -32,6 +32,7 @@ const HELPERS_PATH = path.join(FORMS_DIR, "shared", "helpers", "index.js"); const FORMS = [ { name: "transaction", base: "transaction_form_base.json", out: "transaction_form.json" }, { name: "delegate_action", base: "delegate_action_form_base.json", out: "delegate_action_form.json" }, + { name: "intent", base: "intent_form_base.json", out: "intent_form.json" }, ]; function readHelpersPreamble() { diff --git a/packages/auth0/src/forms/delegate_action/delegate_action_form.json b/packages/auth0/src/forms/delegate_action/delegate_action_form.json index d73d335..b84aca8 100644 --- a/packages/auth0/src/forms/delegate_action/delegate_action_form.json +++ b/packages/auth0/src/forms/delegate_action/delegate_action_form.json @@ -24,7 +24,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in
_form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in _form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", "css": ".avatar {\n width: 4.5rem;\n height: 4.5rem;\n border-radius: 12px;\n object-fit: cover;\n}\n\n.layout {\n position: relative;\n display: flex;\n flex-direction: row;\n gap: 1.5rem;\n align-items: center;\n justify-content: center;\n}\n\n.circle {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 999px;\n background-color: #f6f6f6;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.icon {\n width: 1.2rem;\n height: 1.2rem;\n}\n", "params": { "iconUrl": "https://peersyst-public-production.s3.eu-west-1.amazonaws.com/ab6afba7-91a2-4c53-b2a3-90e4f8c3492a.png", @@ -49,7 +49,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the delegate action details (sender, receiver, max block height, actions).\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppDelegateActionDetails(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderDetails({\n fields: [\n { label: \"Sender ID\", value: params.senderId },\n { label: \"Receiver ID\", value: params.receiverId },\n { label: \"Max Block Height\", value: params.maxBlockHeight },\n ],\n actions: params.actions,\n options: { showYoctoConversion: false },\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the delegate action details (sender, receiver, max block height, actions).\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppDelegateActionDetails(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderDetails({\n fields: [\n { label: \"Sender ID\", value: params.senderId },\n { label: \"Receiver ID\", value: params.receiverId },\n { label: \"Max Block Height\", value: params.maxBlockHeight },\n ],\n actions: params.actions,\n options: { showYoctoConversion: false },\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", "css": ".box {\n padding: 1.25rem;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1.25rem;\n width: 100%;\n\n border-radius: 1rem;\n border: 1px solid #e5e5e5;\n}\n\n.text-content {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n}\n\n.label {\n font-size: 0.75rem;\n color: #999999;\n font-weight: 500;\n}\n\n.value {\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 600;\n}\n\n.json-content {\n display: flex;\n padding: 0.5rem;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n border-radius: 12px;\n background-color: #fafafa;\n\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 500;\n}\n\n.accordion {\n width: 100%;\n border: 1px solid #e5e5e5;\n border-radius: 6px;\n overflow: hidden;\n}\n\n.accordion-header {\n background: #fff;\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n}\n\n.accordion-header-content {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n}\n\n.accordion-left-content {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.accordion-header-label {\n font-size: 0.75rem;\n color: black;\n font-weight: 500;\n}\n\n.warning-icon {\n font-size: 1rem;\n color: #ff4444;\n}\n\n.expand-icon {\n font-size: 1.2rem;\n color: #666;\n font-weight: bold;\n line-height: 1;\n transition: transform 0.2s ease;\n user-select: none;\n min-width: 20px;\n text-align: center;\n}\n\n.accordion-content {\n max-height: 0;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #fafafa;\n color: #0a0a0a;\n font-weight: 400;\n transition:\n max-height 0.3s ease,\n padding 0.3s ease;\n padding: 0 1rem;\n}\n\n.accordion-content.open {\n padding: 12px 16px;\n max-height: 500px;\n}\n\n.actions-container {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n gap: 0.25rem;\n}\n\n.accordion-content .label {\n font-size: 0.75rem;\n}\n\n.accordion-content .value {\n font-size: 0.75rem;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.accordion-content > div {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.action-description {\n font-size: 0.75rem;\n color: #3f4246;\n margin: 0;\n}\n\n.warning-callout {\n background-color: #ffebee;\n padding: 16px;\n border-radius: 4px;\n margin: 0;\n color: #c62828;\n}\n", "params": { "actions": "{{ fields.actions }}", @@ -67,7 +67,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", "css": ".decision-layout {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n width: 100%;\n}\n\n.decision-button {\n width: 100%;\n padding: 0.75rem 1rem;\n border-radius: 0.75rem;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n border: 1px solid transparent;\n}\n\n.decision-button.approve {\n background-color: #0a0a0a;\n color: #ffffff;\n}\n\n.decision-button.deny {\n background-color: #ffffff;\n color: #3f4246;\n border-color: #e5e5e5;\n}\n", "params": { "approveText": "Approve", diff --git a/packages/auth0/src/forms/intent/details/index.css b/packages/auth0/src/forms/intent/details/index.css new file mode 100644 index 0000000..15bf99c --- /dev/null +++ b/packages/auth0/src/forms/intent/details/index.css @@ -0,0 +1,153 @@ +.box { + padding: 1.25rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1.25rem; + width: 100%; + + border-radius: 1rem; + border: 1px solid #e5e5e5; +} + +.text-content { + display: flex; + flex-direction: column; + align-items: left; + justify-content: left; + width: 100%; +} + +.label { + font-size: 0.75rem; + color: #999999; + font-weight: 500; +} + +.value { + color: #3f4246; + font-size: 0.875rem; + font-weight: 600; +} + +.json-content { + display: flex; + padding: 0.5rem; + flex-direction: column; + align-items: left; + justify-content: left; + width: 100%; + border-radius: 12px; + background-color: #fafafa; + + color: #3f4246; + font-size: 0.875rem; + font-weight: 500; +} + +.accordion { + width: 100%; + border: 1px solid #e5e5e5; + border-radius: 6px; + overflow: hidden; +} + +.accordion-header { + background: #fff; + padding: 12px 16px; + cursor: pointer; + user-select: none; +} + +.accordion-header-content { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.accordion-left-content { + display: flex; + align-items: center; + gap: 8px; +} + +.accordion-header-label { + font-size: 0.75rem; + color: black; + font-weight: 500; +} + +.warning-icon { + font-size: 1rem; + color: #ff4444; +} + +.expand-icon { + font-size: 1.2rem; + color: #666; + font-weight: bold; + line-height: 1; + transition: transform 0.2s ease; + user-select: none; + min-width: 20px; + text-align: center; +} + +.accordion-content { + max-height: 0; + overflow: hidden; + font-size: 0.75rem; + background-color: #fafafa; + color: #0a0a0a; + font-weight: 400; + transition: + max-height 0.3s ease, + padding 0.3s ease; + padding: 0 1rem; +} + +.accordion-content.open { + padding: 12px 16px; + max-height: 500px; +} + +.actions-container { + display: flex; + flex-direction: column; + align-items: left; + justify-content: left; + width: 100%; + gap: 0.25rem; +} + +.accordion-content .label { + font-size: 0.75rem; +} + +.accordion-content .value { + font-size: 0.75rem; + white-space: pre-wrap; + word-break: break-word; +} + +.accordion-content > div { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.action-description { + font-size: 0.75rem; + color: #3f4246; + margin: 0; +} + +.warning-callout { + background-color: #ffebee; + padding: 16px; + border-radius: 4px; + margin: 0; + color: #c62828; +} diff --git a/packages/auth0/src/forms/intent/details/index.js b/packages/auth0/src/forms/intent/details/index.js new file mode 100644 index 0000000..3b334f4 --- /dev/null +++ b/packages/auth0/src/forms/intent/details/index.js @@ -0,0 +1,25 @@ +/** + * Custom field that renders the NEP-413 intent details (signer, verifier, deadline, intents). + * + * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js + * (or shimmed by the playground via helpers-shim.js). + */ +function AuthorizeAppIntentDetails(context) { + return { + init: function () { + const params = context.custom.getParams(); + return __auth0FormHelpers.renderIntentDetails({ + fields: [ + { label: "Signer ID", value: params.signerId }, + { label: "Verifier", value: params.recipient }, + { label: "Deadline", value: params.deadline }, + ], + intents: params.intents, + }); + }, + getScripts: function () { return []; }, + block: function () {}, + unblock: function () {}, + getValue: function () {}, + }; +} diff --git a/packages/auth0/src/forms/intent/intent_form.json b/packages/auth0/src/forms/intent/intent_form.json new file mode 100644 index 0000000..3289bac --- /dev/null +++ b/packages/auth0/src/forms/intent/intent_form.json @@ -0,0 +1,123 @@ +{ + "version": "4.0.0", + "form": { + "name": "Intent form", + "languages": { + "primary": "en" + }, + "nodes": [ + { + "id": "step_in7K", + "type": "STEP", + "coordinates": { + "x": 463, + "y": -81 + }, + "alias": "New step", + "config": { + "components": [ + { + "id": "custom_4Mqg", + "category": "FIELD", + "type": "CUSTOM", + "required": false, + "sensitive": false, + "config": { + "schema": {}, + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in _form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", + "css": ".avatar {\n width: 4.5rem;\n height: 4.5rem;\n border-radius: 12px;\n object-fit: cover;\n}\n\n.layout {\n position: relative;\n display: flex;\n flex-direction: row;\n gap: 1.5rem;\n align-items: center;\n justify-content: center;\n}\n\n.circle {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 999px;\n background-color: #f6f6f6;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.icon {\n width: 1.2rem;\n height: 1.2rem;\n}\n", + "params": { + "iconUrl": "https://peersyst-public-production.s3.eu-west-1.amazonaws.com/1b54f479-7990-4834-9b37-95b26e2023fb.png", + "leftImageUrl": "https://peersyst-public-production.s3.eu-west-1.amazonaws.com/db9f38ff-53ea-4f76-a1c0-33c728386e5b.png", + "rightImageUrl": "{{ fields.imageUrl }}" + } + } + }, + { + "id": "rich_text_iN9x", + "category": "BLOCK", + "type": "RICH_TEXT", + "config": { + "content": "

{{ fields.name }} wants to sign an Intent

" + } + }, + { + "id": "custom_I420", + "category": "FIELD", + "type": "CUSTOM", + "required": false, + "sensitive": false, + "config": { + "schema": {}, + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the NEP-413 intent details (signer, verifier, deadline, intents).\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppIntentDetails(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderIntentDetails({\n fields: [\n { label: \"Signer ID\", value: params.signerId },\n { label: \"Verifier\", value: params.recipient },\n { label: \"Deadline\", value: params.deadline },\n ],\n intents: params.intents,\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "css": ".box {\n padding: 1.25rem;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1.25rem;\n width: 100%;\n\n border-radius: 1rem;\n border: 1px solid #e5e5e5;\n}\n\n.text-content {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n}\n\n.label {\n font-size: 0.75rem;\n color: #999999;\n font-weight: 500;\n}\n\n.value {\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 600;\n}\n\n.json-content {\n display: flex;\n padding: 0.5rem;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n border-radius: 12px;\n background-color: #fafafa;\n\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 500;\n}\n\n.accordion {\n width: 100%;\n border: 1px solid #e5e5e5;\n border-radius: 6px;\n overflow: hidden;\n}\n\n.accordion-header {\n background: #fff;\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n}\n\n.accordion-header-content {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n}\n\n.accordion-left-content {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.accordion-header-label {\n font-size: 0.75rem;\n color: black;\n font-weight: 500;\n}\n\n.warning-icon {\n font-size: 1rem;\n color: #ff4444;\n}\n\n.expand-icon {\n font-size: 1.2rem;\n color: #666;\n font-weight: bold;\n line-height: 1;\n transition: transform 0.2s ease;\n user-select: none;\n min-width: 20px;\n text-align: center;\n}\n\n.accordion-content {\n max-height: 0;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #fafafa;\n color: #0a0a0a;\n font-weight: 400;\n transition:\n max-height 0.3s ease,\n padding 0.3s ease;\n padding: 0 1rem;\n}\n\n.accordion-content.open {\n padding: 12px 16px;\n max-height: 500px;\n}\n\n.actions-container {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n gap: 0.25rem;\n}\n\n.accordion-content .label {\n font-size: 0.75rem;\n}\n\n.accordion-content .value {\n font-size: 0.75rem;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.accordion-content > div {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.action-description {\n font-size: 0.75rem;\n color: #3f4246;\n margin: 0;\n}\n\n.warning-callout {\n background-color: #ffebee;\n padding: 16px;\n border-radius: 4px;\n margin: 0;\n color: #c62828;\n}\n", + "params": { + "intents": "{{ fields.intents }}", + "signerId": "{{ fields.signerId }}", + "recipient": "{{ fields.recipient }}", + "deadline": "{{ fields.deadline }}" + } + } + }, + { + "id": "custom_decision_intent", + "category": "FIELD", + "type": "CUSTOM", + "required": false, + "sensitive": false, + "config": { + "schema": {}, + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "css": ".decision-layout {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n width: 100%;\n}\n\n.decision-button {\n width: 100%;\n padding: 0.75rem 1rem;\n border-radius: 0.75rem;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n border: 1px solid transparent;\n}\n\n.decision-button.approve {\n background-color: #0a0a0a;\n color: #ffffff;\n}\n\n.decision-button.deny {\n background-color: #ffffff;\n color: #3f4246;\n border-color: #e5e5e5;\n}\n", + "params": { + "approveText": "Approve", + "denyText": "Deny" + } + } + } + ], + "next_node": "$ending" + } + } + ], + "start": { + "hidden_fields": [ + { + "key": "intents" + }, + { + "key": "signerId" + }, + { + "key": "recipient" + }, + { + "key": "deadline" + }, + { + "key": "name" + }, + { + "key": "imageUrl" + }, + { + "key": "decision" + } + ], + "next_node": "step_in7K", + "coordinates": { + "x": 132, + "y": -102 + } + }, + "ending": { + "resume_flow": true, + "coordinates": { + "x": 1060, + "y": -118 + } + } + }, + "flows": {}, + "connections": {} +} diff --git a/packages/auth0/src/forms/intent/intent_form_base.json b/packages/auth0/src/forms/intent/intent_form_base.json new file mode 100644 index 0000000..4b005e4 --- /dev/null +++ b/packages/auth0/src/forms/intent/intent_form_base.json @@ -0,0 +1,112 @@ +{ + "version": "4.0.0", + "form": { + "name": "Intent form", + "languages": { + "primary": "en" + }, + "nodes": [ + { + "id": "step_in7K", + "type": "STEP", + "coordinates": { + "x": 463, + "y": -81 + }, + "alias": "New step", + "config": { + "components": [ + { + "$source": "shared/image", + "id": "custom_4Mqg", + "category": "FIELD", + "type": "CUSTOM", + "required": false, + "sensitive": false, + "config": { + "schema": {}, + "code": "", + "css": "", + "params": { + "iconUrl": "https://peersyst-public-production.s3.eu-west-1.amazonaws.com/1b54f479-7990-4834-9b37-95b26e2023fb.png", + "leftImageUrl": "https://peersyst-public-production.s3.eu-west-1.amazonaws.com/db9f38ff-53ea-4f76-a1c0-33c728386e5b.png", + "rightImageUrl": "{{ fields.imageUrl }}" + } + } + }, + { + "id": "rich_text_iN9x", + "category": "BLOCK", + "type": "RICH_TEXT", + "config": { + "content": "

{{ fields.name }} wants to sign an Intent

" + } + }, + { + "$source": "details", + "id": "custom_I420", + "category": "FIELD", + "type": "CUSTOM", + "required": false, + "sensitive": false, + "config": { + "schema": {}, + "code": "", + "css": "", + "params": { + "intents": "{{ fields.intents }}", + "signerId": "{{ fields.signerId }}", + "recipient": "{{ fields.recipient }}", + "deadline": "{{ fields.deadline }}" + } + } + }, + { + "$source": "shared/decision", + "id": "custom_decision_intent", + "category": "FIELD", + "type": "CUSTOM", + "required": false, + "sensitive": false, + "config": { + "schema": {}, + "code": "", + "css": "", + "params": { + "approveText": "Approve", + "denyText": "Deny" + } + } + } + ], + "next_node": "$ending" + } + } + ], + "start": { + "hidden_fields": [ + { "key": "intents" }, + { "key": "signerId" }, + { "key": "recipient" }, + { "key": "deadline" }, + { "key": "name" }, + { "key": "imageUrl" }, + { "key": "decision" } + ], + "next_node": "step_in7K", + "coordinates": { + "x": 132, + "y": -102 + } + }, + "ending": { + "resume_flow": true, + "coordinates": { + "x": 1060, + "y": -118 + } + } + }, + "flows": {}, + "connections": {} +} diff --git a/packages/auth0/src/forms/shared/helpers/index.js b/packages/auth0/src/forms/shared/helpers/index.js index 17c3e93..76f9fb7 100644 --- a/packages/auth0/src/forms/shared/helpers/index.js +++ b/packages/auth0/src/forms/shared/helpers/index.js @@ -500,6 +500,120 @@ function renderDetails(params) { return box; } +// --- NEP-413 intent content factories --- + +/** + * Render the token map of a transfer intent (`{ "": "" }`). + * + * Amounts stay in the token's smallest unit: the form has no token metadata, so converting + * would mean guessing decimals — and a wrong guess here understates what the user is + * approving. Showing the raw amount alongside its token id is honest and unambiguous. + */ +function intentTokensContent(tokens) { + const container = document.createElement("div"); + if (!tokens || typeof tokens !== "object") return container; + for (const tokenId of Object.keys(tokens)) { + container.appendChild(createTextContent(tokenId, String(tokens[tokenId]))); + } + return container; +} + +function transferIntentContent(intent) { + const container = document.createElement("div"); + container.appendChild(createDescription("By approving this request, the following tokens will be transferred.")); + container.appendChild(createTextContent("Receiver ID", intent.receiver_id)); + + const tokensLabel = document.createElement("div"); + tokensLabel.classList.add("label"); + tokensLabel.textContent = "Tokens"; + container.appendChild(tokensLabel); + container.appendChild(intentTokensContent(intent.tokens)); + return container; +} + +/** + * Fallback for intent kinds this form does not model explicitly. It is deliberately shown + * with a warning: the user is approving something the UI cannot describe in plain terms. + */ +function unknownIntentContent(intent) { + const container = document.createElement("div"); + container.appendChild(createDescription("This request contains an intent type this app does not recognize. Review it carefully.")); + let serialized; + try { + serialized = JSON.stringify(intent, null, 2); + } catch (e) { + serialized = ""; + } + container.appendChild(createTextContent("Intent", serialized)); + return container; +} + +const INTENT_DISPATCH = { + transfer: { + label: "Transfer", + warn: false, + render: function (i) { + return transferIntentContent(i); + }, + }, +}; + +function handleIntent(intent) { + const kind = intent && typeof intent === "object" ? intent.intent : null; + const entry = kind ? INTENT_DISPATCH[kind] : null; + if (!entry) { + return createAccordion(kind ? `Unknown: ${kind}` : "Unknown", unknownIntentContent(intent), true); + } + return createAccordion(entry.label, entry.render(intent), !!entry.warn); +} + +/** + * Build the details DOM tree for a NEP-413 intent approval. + * + * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of + * NEAR transaction actions. + * + * @param {object} params + * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline). + * @param {string} params.intents - JSON string with the intents array. + */ +function renderIntentDetails(params) { + ensureBufferPolyfill(); + const box = document.createElement("div"); + box.classList.add("box"); + + for (const field of params.fields || []) { + if (field.value === undefined || field.value === null || field.value === "") continue; + box.appendChild(createTextContent(field.label, field.value)); + } + + const intentsContainer = document.createElement("div"); + intentsContainer.classList.add("actions-container"); + const intentsLabel = document.createElement("div"); + intentsLabel.classList.add("label"); + intentsLabel.textContent = "Intents"; + intentsContainer.appendChild(intentsLabel); + + let parsedIntents = []; + try { + parsedIntents = JSON.parse(params.intents || "[]"); + } catch (e) { + const errorNode = document.createElement("div"); + errorNode.classList.add("warning-callout"); + errorNode.textContent = "Failed to parse intents payload."; + intentsContainer.appendChild(errorNode); + box.appendChild(intentsContainer); + return box; + } + + for (const intent of parsedIntents) { + intentsContainer.appendChild(handleIntent(intent)); + } + + box.appendChild(intentsContainer); + return box; +} + var __auth0FormHelpers = { ensureBufferPolyfill: ensureBufferPolyfill, base58Encode: base58Encode, @@ -524,6 +638,11 @@ var __auth0FormHelpers = { useGlobalContractContent: useGlobalContractContent, handleNearAction: handleNearAction, renderDetails: renderDetails, + intentTokensContent: intentTokensContent, + transferIntentContent: transferIntentContent, + unknownIntentContent: unknownIntentContent, + handleIntent: handleIntent, + renderIntentDetails: renderIntentDetails, }; if (typeof module !== "undefined" && module.exports) { diff --git a/packages/auth0/src/forms/transaction/transaction_form.json b/packages/auth0/src/forms/transaction/transaction_form.json index c131a5b..794b0c4 100644 --- a/packages/auth0/src/forms/transaction/transaction_form.json +++ b/packages/auth0/src/forms/transaction/transaction_form.json @@ -24,7 +24,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in _form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in _form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", "css": ".avatar {\n width: 4.5rem;\n height: 4.5rem;\n border-radius: 12px;\n object-fit: cover;\n}\n\n.layout {\n position: relative;\n display: flex;\n flex-direction: row;\n gap: 1.5rem;\n align-items: center;\n justify-content: center;\n}\n\n.circle {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 999px;\n background-color: #f6f6f6;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.icon {\n width: 1.2rem;\n height: 1.2rem;\n}\n", "params": { "iconUrl": "https://peersyst-public-production.s3.eu-west-1.amazonaws.com/1b54f479-7990-4834-9b37-95b26e2023fb.png", @@ -49,7 +49,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the transaction details (signer, receiver, actions).\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppTransactionDetails(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderDetails({\n fields: [\n { label: \"Signer ID\", value: params.signerId },\n { label: \"Receiver ID\", value: params.receiverId },\n ],\n actions: params.actions,\n options: { showYoctoConversion: true },\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the transaction details (signer, receiver, actions).\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppTransactionDetails(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderDetails({\n fields: [\n { label: \"Signer ID\", value: params.signerId },\n { label: \"Receiver ID\", value: params.receiverId },\n ],\n actions: params.actions,\n options: { showYoctoConversion: true },\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", "css": ".box {\n padding: 1.25rem;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1.25rem;\n width: 100%;\n\n border-radius: 1rem;\n border: 1px solid #e5e5e5;\n}\n\n.text-content {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n}\n\n.label {\n font-size: 0.75rem;\n color: #999999;\n font-weight: 500;\n}\n\n.value {\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 600;\n}\n\n.json-content {\n display: flex;\n padding: 0.5rem;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n border-radius: 12px;\n background-color: #fafafa;\n\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 500;\n}\n\n.accordion {\n width: 100%;\n border: 1px solid #e5e5e5;\n border-radius: 6px;\n overflow: hidden;\n}\n\n.accordion-header {\n background: #fff;\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n}\n\n.accordion-header-content {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n}\n\n.accordion-left-content {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.accordion-header-label {\n font-size: 0.75rem;\n color: black;\n font-weight: 500;\n}\n\n.warning-icon {\n font-size: 1rem;\n color: #ff4444;\n}\n\n.expand-icon {\n font-size: 1.2rem;\n color: #666;\n font-weight: bold;\n line-height: 1;\n transition: transform 0.2s ease;\n user-select: none;\n min-width: 20px;\n text-align: center;\n}\n\n.accordion-content {\n max-height: 0;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #fafafa;\n color: #0a0a0a;\n font-weight: 400;\n transition:\n max-height 0.3s ease,\n padding 0.3s ease;\n padding: 0 1rem;\n}\n\n.accordion-content.open {\n padding: 12px 16px;\n max-height: 500px;\n}\n\n.actions-container {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n gap: 0.25rem;\n}\n\n.accordion-content .label {\n font-size: 0.75rem;\n}\n\n.accordion-content .value {\n font-size: 0.75rem;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.accordion-content > div {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.action-description {\n font-size: 0.75rem;\n color: #3f4246;\n margin: 0;\n}\n\n.warning-callout {\n background-color: #ffebee;\n padding: 16px;\n border-radius: 4px;\n margin: 0;\n color: #c62828;\n}\n", "params": { "actions": "{{ fields.actions }}", @@ -66,7 +66,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", "css": ".decision-layout {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n width: 100%;\n}\n\n.decision-button {\n width: 100%;\n padding: 0.75rem 1rem;\n border-radius: 0.75rem;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n border: 1px solid transparent;\n}\n\n.decision-button.approve {\n background-color: #0a0a0a;\n color: #ffffff;\n}\n\n.decision-button.deny {\n background-color: #ffffff;\n color: #3f4246;\n border-color: #e5e5e5;\n}\n", "params": { "approveText": "Approve", diff --git a/packages/auth0/test/fixtures/builders.js b/packages/auth0/test/fixtures/builders.js index 312762d..b2856c1 100644 --- a/packages/auth0/test/fixtures/builders.js +++ b/packages/auth0/test/fixtures/builders.js @@ -25,6 +25,7 @@ const { GlobalContractIdentifier, } = require("@near-js/transactions"); const { PublicKey } = require("near-api-js").utils; +const { serialize: borshSerialize } = require("borsh"); // jest-environment-jsdom (jest 29) ships an older jsdom without TextEncoder. Fall back to util. const SafeTextEncoder = typeof TextEncoder !== "undefined" ? TextEncoder : require("util").TextEncoder; @@ -280,6 +281,66 @@ const DELEGATE_ACTION_TYPES = ALL_ACTION_TYPES.filter( (a) => !["signedDelegate", "deployGlobalContract", "useGlobalContract"].includes(a.name.split(" ")[0]), ); +// ----- NEP-413 intent payloads ----- + +// Domain-separation tag from NEP-413 (2^31 + 413). +const NEP413_PREFIX_TAG = Math.pow(2, 31) + 413; + +const DEFAULT_INTENTS_RECIPIENT = "intents.near"; + +/** + * Borsh schema for the NEP-413 payload, transcribed from the NEP rather than imported from + * the action under test. Duplicating it deliberately: if someone edits the action's schema, + * these fixtures keep encoding per the spec and the round-trip test fails — which is the + * point. Importing the action's own schema would make the test circular. + * + * https://github.com/near/NEPs/blob/master/neps/nep-0413.md#input-interface + */ +const NEP413_SCHEMA = { + struct: { + tag: "u32", + message: "string", + nonce: { array: { type: "u8", len: 32 } }, + recipient: "string", + callbackUrl: { option: "string" }, + }, +}; + +const SAMPLE_NONCE = Uint8Array.from(Array.from({ length: 32 }, (_, i) => (i * 5) % 256)); + +/** + * Build the JSON message body NEAR Intents expects inside a NEP-413 payload. + */ +function buildIntentMessage({ + signerId = "trader.near", + deadline = "2026-01-01T00:00:00.000Z", + intents = [{ intent: "transfer", receiver_id: "deposit.near", tokens: { "nep141:usdc.near": "1000000" } }], +} = {}) { + return { signer_id: signerId, deadline, intents }; +} + +/** + * Encode a NEP-413 payload the way the client SDK puts it on the authorize query string. + * + * @returns {{csv: string, bytes: Uint8Array, payload: object, message: object}} + */ +function buildIntentPayload({ + message, + tag = NEP413_PREFIX_TAG, + recipient = DEFAULT_INTENTS_RECIPIENT, + nonce = SAMPLE_NONCE, + callbackUrl = null, + rawMessage, +} = {}) { + const messageObject = message === undefined ? buildIntentMessage() : message; + const messageString = rawMessage !== undefined ? rawMessage : JSON.stringify(messageObject); + + const payload = { tag, message: messageString, nonce, recipient, callbackUrl }; + const bytes = borshSerialize(NEP413_SCHEMA, payload); + + return { csv: toCsv(bytes), bytes, payload, message: messageObject }; +} + module.exports = { DELEGATE_ACTION_PREFIX, ZERO_BLOCK_HASH, @@ -293,4 +354,10 @@ module.exports = { toCsv, ALL_ACTION_TYPES, DELEGATE_ACTION_TYPES, + NEP413_PREFIX_TAG, + DEFAULT_INTENTS_RECIPIENT, + NEP413_SCHEMA, + SAMPLE_NONCE, + buildIntentMessage, + buildIntentPayload, }; diff --git a/packages/auth0/test/intent-decoding.spec.js b/packages/auth0/test/intent-decoding.spec.js new file mode 100644 index 0000000..8b4b46c --- /dev/null +++ b/packages/auth0/test/intent-decoding.spec.js @@ -0,0 +1,125 @@ +/** + * @jest-environment node + * + * Tests for `decodeIntent` — the validation gate for NEP-413 payloads. + * + * The guard contract never inspects the bytes it signs; it only checks that they equal the + * `fatxn` claim. So every guarantee about *what* the user approved is established here, and + * each rejection below maps to a concrete way the consent guarantee could be bypassed: + * + * - wrong/absent domain tag → bytes that could double as a NEAR transaction + * - unexpected recipient → an intent redirected to a different verifier + * - unparseable message → a payload the approval screen cannot describe + * - empty intents → an approval that shows the user nothing + */ +const { decodeIntent, NEP413_PREFIX_TAG, DEFAULT_INTENTS_RECIPIENT } = require("../src/actions/authorize-app.action.js"); +const { buildIntentPayload, buildIntentMessage, buildTransaction, toCsv } = require("./fixtures/builders.js"); + +describe("decodeIntent — happy path", () => { + test("decodes a well-formed transfer intent", () => { + const { csv, message } = buildIntentPayload(); + const { payload, message: decoded } = decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT); + + expect(payload.tag).toBe(NEP413_PREFIX_TAG); + expect(payload.recipient).toBe(DEFAULT_INTENTS_RECIPIENT); + expect(decoded).toEqual(message); + expect(decoded.intents).toHaveLength(1); + expect(decoded.intents[0].intent).toBe("transfer"); + }); + + test("round-trips the exact byte string it was given", () => { + const { csv, bytes } = buildIntentPayload(); + // The action puts `query.intent` straight into `fatxn`, so the bytes the user approves + // must be byte-identical to what was decoded for display. + expect(csv.split(",").map(Number)).toEqual(Array.from(bytes)); + expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).not.toThrow(); + }); + + test("accepts multiple intents in a single payload", () => { + const message = buildIntentMessage({ + intents: [ + { intent: "transfer", receiver_id: "a.near", tokens: { "nep141:usdc.near": "1" } }, + { intent: "transfer", receiver_id: "b.near", tokens: { "nep141:usdt.near": "2" } }, + ], + }); + const { csv } = buildIntentPayload({ message }); + const { message: decoded } = decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT); + expect(decoded.intents).toHaveLength(2); + }); + + test("accepts a custom verifier when that is what the caller expects", () => { + const { csv } = buildIntentPayload({ recipient: "intents.testnet" }); + expect(() => decodeIntent(csv, "intents.testnet")).not.toThrow(); + }); +}); + +describe("decodeIntent — domain separation", () => { + test("rejects a payload whose tag is not the NEP-413 prefix", () => { + const { csv } = buildIntentPayload({ tag: 1 }); + expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/missing the NEP-413 domain tag/); + }); + + test("rejects the delegate-action prefix reused as a tag", () => { + const { csv } = buildIntentPayload({ tag: Math.pow(2, 30) + 366 }); + expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/missing the NEP-413 domain tag/); + }); + + test("rejects transaction bytes submitted as an intent", () => { + // A real NEAR transaction must never decode into an approvable intent, whether it + // fails at borsh or at the tag check. + const { csv } = buildTransaction(); + expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(); + }); +}); + +describe("decodeIntent — recipient anchoring", () => { + test("rejects an intent aimed at a different verifier", () => { + const { csv } = buildIntentPayload({ recipient: "evil.near" }); + expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/unexpected recipient: evil\.near/); + }); + + test("rejects an empty recipient", () => { + const { csv } = buildIntentPayload({ recipient: "" }); + expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/unexpected recipient/); + }); +}); + +describe("decodeIntent — message validation", () => { + test("rejects a message that is not JSON", () => { + const { csv } = buildIntentPayload({ rawMessage: "not json at all" }); + expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/not valid JSON/); + }); + + test("rejects a message with no intents array", () => { + const { csv } = buildIntentPayload({ rawMessage: JSON.stringify({ signer_id: "trader.near" }) }); + expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/no intents to approve/); + }); + + test("rejects an empty intents array", () => { + const { csv } = buildIntentPayload({ message: buildIntentMessage({ intents: [] }) }); + expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/no intents to approve/); + }); + + test("rejects intents that is a JSON value but not an array", () => { + const { csv } = buildIntentPayload({ rawMessage: JSON.stringify({ intents: { intent: "transfer" } }) }); + expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/no intents to approve/); + }); +}); + +describe("decodeIntent — malformed input", () => { + test("rejects bytes that are not a NEP-413 payload", () => { + expect(() => decodeIntent(toCsv(Uint8Array.from([1, 2, 3, 4])), DEFAULT_INTENTS_RECIPIENT)).toThrow( + /not a valid NEP-413 message/, + ); + }); + + test("rejects an empty payload", () => { + expect(() => decodeIntent("", DEFAULT_INTENTS_RECIPIENT)).toThrow(/not a valid NEP-413 message/); + }); + + test("rejects a truncated payload", () => { + const { bytes } = buildIntentPayload(); + const truncated = bytes.slice(0, Math.floor(bytes.length / 2)); + expect(() => decodeIntent(toCsv(truncated), DEFAULT_INTENTS_RECIPIENT)).toThrow(/not a valid NEP-413 message/); + }); +}); diff --git a/packages/auth0/test/intent-handlers.spec.js b/packages/auth0/test/intent-handlers.spec.js new file mode 100644 index 0000000..034af3f --- /dev/null +++ b/packages/auth0/test/intent-handlers.spec.js @@ -0,0 +1,177 @@ +/** + * @jest-environment node + * + * Tests for the intent branch of `onExecutePostLogin`: form dispatch, the `fields` shape the + * approval screen receives, the `fatxn` claim, denial on an undecodable payload, and the + * single-payload rule. + */ +const { onExecutePostLogin } = require("../src/actions/authorize-app.action.js"); +const { buildIntentPayload, buildIntentMessage, buildTransaction, buildDelegateAction } = require("./fixtures/builders.js"); + +const ONCHAIN_AUDIENCE = "https://onchain.example"; + +function makeApi() { + const calls = { deny: [], removedScopes: [], customClaims: {}, render: null }; + const api = { + access: { + deny: (msg) => { + calls.deny.push(msg); + }, + }, + accessToken: { + removeScope: (s) => { + calls.removedScopes.push(s); + }, + setCustomClaim: (k, v) => { + calls.customClaims[k] = v; + }, + }, + prompt: { + render: (modalId, opts) => { + calls.render = { modalId, opts }; + }, + }, + }; + return { api, calls }; +} + +function makeEvent({ query = {}, audience = ONCHAIN_AUDIENCE, intentsRecipient } = {}) { + return { + secrets: { + ONCHAIN_AUDIENCE, + TRANSACTION_FORM: "modal_tx", + DELEGATE_ACTION_FORM: "modal_delegate", + INTENT_FORM: "modal_intent", + ...(intentsRecipient ? { INTENTS_RECIPIENT: intentsRecipient } : {}), + }, + request: { query }, + resource_server: audience == null ? undefined : { identifier: audience }, + client: { name: "Test App", metadata: { logo_uri: "https://logo.example/x.png" } }, + }; +} + +describe("onExecutePostLogin — intent dispatch", () => { + test("renders the intent form with signer, verifier and deadline", async () => { + const { api, calls } = makeApi(); + const { csv, message } = buildIntentPayload(); + + await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + + expect(calls.deny).toEqual([]); + expect(calls.render.modalId).toBe("modal_intent"); + expect(calls.render.opts.fields).toMatchObject({ + name: "Test App", + imageUrl: "https://logo.example/x.png", + signerId: message.signer_id, + recipient: "intents.near", + deadline: message.deadline, + }); + }); + + test("hands the intents to the form as a JSON string", async () => { + const { api, calls } = makeApi(); + const { csv, message } = buildIntentPayload(); + + await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + + expect(JSON.parse(calls.render.opts.fields.intents)).toEqual(message.intents); + }); + + test("sets fatxn to the exact bytes received", async () => { + const { api, calls } = makeApi(); + const { csv, bytes } = buildIntentPayload(); + + await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + + // The guard compares fatxn to sign_payload byte for byte; any transformation here + // would make every signature fail on-chain. + expect(calls.customClaims.fatxn).toEqual(Array.from(bytes)); + }); + + test("strips the OIDC profile scopes like the other payload types", async () => { + const { api, calls } = makeApi(); + const { csv } = buildIntentPayload(); + + await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + + expect(calls.removedScopes).toEqual(["profile", "email", "offline_access"]); + }); + + test("honours a tenant-configured verifier", async () => { + const { api, calls } = makeApi(); + const { csv } = buildIntentPayload({ recipient: "intents.testnet" }); + + await onExecutePostLogin(makeEvent({ query: { intent: csv }, intentsRecipient: "intents.testnet" }), api); + + expect(calls.deny).toEqual([]); + expect(calls.render.opts.fields.recipient).toBe("intents.testnet"); + }); +}); + +describe("onExecutePostLogin — intent rejection", () => { + test("denies instead of rendering when the payload cannot be decoded", async () => { + const { api, calls } = makeApi(); + const { csv } = buildIntentPayload({ tag: 7 }); + + await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + + expect(calls.deny).toEqual(["Intent payload is missing the NEP-413 domain tag"]); + expect(calls.render).toBeNull(); + expect(calls.customClaims.fatxn).toBeUndefined(); + }); + + test("denies an intent aimed at another verifier", async () => { + const { api, calls } = makeApi(); + const { csv } = buildIntentPayload({ recipient: "evil.near" }); + + await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + + expect(calls.deny).toEqual(["Intent payload targets an unexpected recipient: evil.near"]); + expect(calls.customClaims.fatxn).toBeUndefined(); + }); + + test("denies an intent whose message shows the user nothing", async () => { + const { api, calls } = makeApi(); + const { csv } = buildIntentPayload({ message: buildIntentMessage({ intents: [] }) }); + + await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + + expect(calls.deny).toEqual(["Intent message carries no intents to approve"]); + expect(calls.customClaims.fatxn).toBeUndefined(); + }); + + test("denies an intent sent to a non-signing audience", async () => { + const { api, calls } = makeApi(); + const { csv } = buildIntentPayload(); + + await onExecutePostLogin(makeEvent({ query: { intent: csv }, audience: "https://other.example" }), api); + + expect(calls.deny).toEqual(["Transaction payload only allowed with signing audience"]); + }); +}); + +describe("onExecutePostLogin — single payload rule", () => { + test("denies when an intent arrives alongside a transaction", async () => { + const { api, calls } = makeApi(); + const { csv: intentCsv } = buildIntentPayload(); + const { csv: txCsv } = buildTransaction(); + + await onExecutePostLogin(makeEvent({ query: { intent: intentCsv, transaction: txCsv } }), api); + + // Otherwise the screen could show one payload while a different one lands in fatxn. + expect(calls.deny).toEqual(["Only one signing payload may be requested at a time"]); + expect(calls.render).toBeNull(); + expect(calls.customClaims.fatxn).toBeUndefined(); + }); + + test("denies when a transaction arrives alongside a delegate action", async () => { + const { api, calls } = makeApi(); + const { csv: txCsv } = buildTransaction(); + const { csv: delegateCsv } = buildDelegateAction(); + + await onExecutePostLogin(makeEvent({ query: { transaction: txCsv, delegateAction: delegateCsv } }), api); + + expect(calls.deny).toEqual(["Only one signing payload may be requested at a time"]); + expect(calls.render).toBeNull(); + }); +}); diff --git a/packages/auth0/test/intent-helpers.spec.js b/packages/auth0/test/intent-helpers.spec.js new file mode 100644 index 0000000..551ae90 --- /dev/null +++ b/packages/auth0/test/intent-helpers.spec.js @@ -0,0 +1,122 @@ +/** + * @jest-environment jsdom + * + * Tests for `renderIntentDetails` — the approval screen for NEP-413 intents. + * + * This is the half of the security model the contract cannot enforce: the user must be able + * to read what they are about to sign. These tests assert the transfer details actually reach + * the DOM, and that anything the form cannot describe is surfaced with a warning rather than + * rendered as if it were understood. + */ +const helpers = require("../src/forms/shared/helpers/index.js"); + +const TRANSFER = { intent: "transfer", receiver_id: "deposit.near", tokens: { "nep141:usdc.near": "1000000" } }; + +function render(intents, fields = []) { + return helpers.renderIntentDetails({ fields, intents: JSON.stringify(intents) }); +} + +describe("renderIntentDetails — top-level fields", () => { + test("renders the fields it is given", () => { + const box = render( + [TRANSFER], + [ + { label: "Signer ID", value: "trader.near" }, + { label: "Verifier", value: "intents.near" }, + { label: "Deadline", value: "2026-01-01T00:00:00.000Z" }, + ], + ); + const text = box.textContent; + expect(text).toContain("trader.near"); + expect(text).toContain("intents.near"); + expect(text).toContain("2026-01-01T00:00:00.000Z"); + }); + + test("skips empty fields instead of rendering blank rows", () => { + const box = render([TRANSFER], [{ label: "Deadline", value: "" }]); + expect(box.textContent).not.toContain("Deadline"); + }); + + test("labels the section as Intents", () => { + const box = render([TRANSFER]); + expect(box.querySelector(".actions-container").textContent).toContain("Intents"); + }); +}); + +describe("renderIntentDetails — transfer intents", () => { + test("shows the receiver and the token amount", () => { + const box = render([TRANSFER]); + const text = box.textContent; + expect(text).toContain("deposit.near"); + expect(text).toContain("nep141:usdc.near"); + expect(text).toContain("1000000"); + }); + + test("labels the accordion as Transfer without a warning", () => { + const box = render([TRANSFER]); + expect(box.querySelector(".accordion-header-label").textContent).toBe("Transfer"); + expect(box.querySelector(".warning-icon")).toBeNull(); + }); + + test("renders every token in a multi-token transfer", () => { + const box = render([{ ...TRANSFER, tokens: { "nep141:usdc.near": "1", "nep141:usdt.near": "2" } }]); + const text = box.textContent; + expect(text).toContain("nep141:usdc.near"); + expect(text).toContain("nep141:usdt.near"); + }); + + test("renders one accordion per intent", () => { + const box = render([TRANSFER, { ...TRANSFER, receiver_id: "other.near" }]); + expect(box.querySelectorAll(".accordion")).toHaveLength(2); + expect(box.textContent).toContain("other.near"); + }); + + test("survives a transfer with no tokens map", () => { + const box = render([{ intent: "transfer", receiver_id: "deposit.near" }]); + expect(box.textContent).toContain("deposit.near"); + }); +}); + +describe("renderIntentDetails — unrecognized intents", () => { + test("flags an unknown intent kind with a warning", () => { + const box = render([{ intent: "token_diff", diff: { "nep141:usdc.near": "-1" } }]); + expect(box.querySelector(".warning-icon")).not.toBeNull(); + expect(box.querySelector(".accordion-header-label").textContent).toBe("Unknown: token_diff"); + }); + + test("still shows the raw payload of an unknown intent", () => { + const box = render([{ intent: "token_diff", diff: { "nep141:usdc.near": "-1" } }]); + expect(box.textContent).toContain("token_diff"); + expect(box.textContent).toContain("nep141:usdc.near"); + }); + + test("flags an intent with no kind at all", () => { + const box = render([{ receiver_id: "deposit.near" }]); + expect(box.querySelector(".accordion-header-label").textContent).toBe("Unknown"); + expect(box.querySelector(".warning-icon")).not.toBeNull(); + }); + + test("warns per intent, leaving known ones unflagged", () => { + const box = render([TRANSFER, { intent: "mystery" }]); + const labels = Array.from(box.querySelectorAll(".accordion-header-label")).map((n) => n.textContent); + expect(labels).toEqual(["Transfer", "Unknown: mystery"]); + expect(box.querySelectorAll(".warning-icon")).toHaveLength(1); + }); +}); + +describe("renderIntentDetails — malformed payloads", () => { + test("shows a parse error instead of throwing", () => { + const box = helpers.renderIntentDetails({ fields: [], intents: "{not json" }); + expect(box.querySelector(".warning-callout").textContent).toBe("Failed to parse intents payload."); + }); + + test("renders an empty section when there are no intents", () => { + const box = render([]); + expect(box.querySelectorAll(".accordion")).toHaveLength(0); + }); + + test("treats a missing intents string as empty", () => { + const box = helpers.renderIntentDetails({ fields: [] }); + expect(box.querySelectorAll(".accordion")).toHaveLength(0); + }); +}); diff --git a/packages/providers/javascript/package.json b/packages/providers/javascript/package.json index f1da50b..4c1025c 100644 --- a/packages/providers/javascript/package.json +++ b/packages/providers/javascript/package.json @@ -22,6 +22,7 @@ "@auth0/auth0-spa-js": "^2.11.3", "@near-js/accounts": "^2.0.2", "@near-js/transactions": "^2.0.2", + "borsh": "^2.0.0", "elliptic": "^6.6.1", "jwt-decode": "^3.1.2", "near-api-js": "^5.1.1", diff --git a/packages/providers/javascript/src/index.ts b/packages/providers/javascript/src/index.ts index 84f64a1..14f87d8 100644 --- a/packages/providers/javascript/src/index.ts +++ b/packages/providers/javascript/src/index.ts @@ -1,3 +1,4 @@ export * from "./provider"; export * from "./types"; +export * from "./nep413"; export * from "@shared/core"; diff --git a/packages/providers/javascript/src/nep413.ts b/packages/providers/javascript/src/nep413.ts new file mode 100644 index 0000000..c10c4a2 --- /dev/null +++ b/packages/providers/javascript/src/nep413.ts @@ -0,0 +1,84 @@ +import { serialize } from "borsh"; +import type { Schema } from "borsh"; + +/** + * Domain-separation tag mandated by NEP-413 (2^31 + 413). Prepending it guarantees the signed + * bytes can never be reinterpreted as a NEAR transaction, whose borsh encoding begins with the + * signerId length — a small u32. + * + * https://github.com/near/NEPs/blob/master/neps/nep-0413.md#how-to-ensure-the-message-is-not-a-transaction + */ +export const NEP413_PREFIX_TAG = 2147484061; + +/** NEP-413 fixes the nonce at 32 bytes. */ +export const NEP413_NONCE_LENGTH = 32; + +/** + * Borsh schema of the NEP-413 payload. Field order is normative — it must match the schema the + * Auth0 action decodes with, or the bytes committed to `fatxn` will not match what was signed. + * + * https://github.com/near/NEPs/blob/master/neps/nep-0413.md#input-interface + */ +export const NEP413_PAYLOAD_SCHEMA: Schema = { + struct: { + tag: "u32", + message: "string", + nonce: { array: { type: "u8", len: NEP413_NONCE_LENGTH } }, + recipient: "string", + callbackUrl: { option: "string" }, + }, +}; + +export type NEP413Payload = { + /** + * The message to sign. For NEAR Intents this is the JSON-encoded intent body + * (`signer_id`, `deadline`, `intents`). + */ + message: string; + /** + * 32-byte nonce. Generated when omitted. + */ + nonce?: Uint8Array; + /** + * Account the message is addressed to — the intents verifier, e.g. `intents.near`. + */ + recipient: string; + callbackUrl?: string; +}; + +/** + * Generate a random 32-byte NEP-413 nonce. + * @returns The nonce. + */ +export function generateNep413Nonce(): Uint8Array { + return globalThis.crypto.getRandomValues(new Uint8Array(NEP413_NONCE_LENGTH)); +} + +/** + * Borsh-serialize a NEP-413 payload into the exact bytes that get signed and committed to the fatxn claim. + * @param payload The payload to encode. + * @returns The serialized payload. + */ +export function serializeNep413Payload(payload: NEP413Payload): Uint8Array { + const nonce = payload.nonce ?? generateNep413Nonce(); + if (nonce.length !== NEP413_NONCE_LENGTH) { + throw new Error(`NEP-413 nonce must be ${NEP413_NONCE_LENGTH} bytes, got ${nonce.length}`); + } + + return serialize(NEP413_PAYLOAD_SCHEMA, { + tag: NEP413_PREFIX_TAG, + message: payload.message, + nonce, + recipient: payload.recipient, + callbackUrl: payload.callbackUrl ?? null, + }); +} + +/** + * Encode a NEP-413 payload as the number array the Auth0 authorize query string carries. + * @param payload The payload to encode. + * @returns The encoded payload. + */ +export function encodeNep413Payload(payload: NEP413Payload): number[] { + return Array.from(serializeNep413Payload(payload)); +} diff --git a/packages/providers/javascript/src/provider.ts b/packages/providers/javascript/src/provider.ts index 4b0f25d..3d36c1b 100644 --- a/packages/providers/javascript/src/provider.ts +++ b/packages/providers/javascript/src/provider.ts @@ -10,13 +10,18 @@ import { JavascriptLoginOptions, JavascriptLoginWithRedirectOptions, JavascriptLoginWithPopupOptions, + JavascriptRequestIntentSignatureOptions, + JavascriptRequestIntentSignatureWithRedirectOptions, + JavascriptRequestIntentSignatureWithPopupOptions, } from "./types"; +import { encodeNep413Payload } from "./nep413"; import { FAST_AUTH_AUTH0_DEFAULTS, GetSignatureRequestResponse, IFastAuthProvider, LoginResponse, RequestDelegateActionSignatureResponse, + RequestIntentSignatureResponse, RequestTransactionSignatureResponse, User, } from "@shared/core"; @@ -276,6 +281,61 @@ export class JavascriptProvider implements IFastAuthProvider { return this.getUserId(); } + /** + * Request a NEP-413 intent signature with redirect. + * @param requestSignatureOptions The options for the request intent signature with redirect. + * @returns The void. + */ + private async requestIntentSignatureWithRedirect( + requestSignatureOptions: JavascriptRequestIntentSignatureWithRedirectOptions, + ): Promise { + const { redirectUri, intent, ...opts } = requestSignatureOptions; + await this.client.loginWithRedirect({ + authorizationParams: { + audience: this.options.signingAudience, + scope: "transaction:sign", + intent: encodeNep413Payload(intent), + redirect_uri: redirectUri, + }, + ...opts, + }); + } + + /** + * Request a NEP-413 intent signature with popup. + * @param requestSignatureOptions The options for the request intent signature with popup. + * @returns The void. + */ + private async requestIntentSignatureWithPopup( + requestSignatureOptions: JavascriptRequestIntentSignatureWithPopupOptions, + ): Promise { + const { intent, ...opts } = requestSignatureOptions; + await this.client.loginWithPopup({ + authorizationParams: { + audience: this.options.signingAudience, + scope: "transaction:sign", + intent: encodeNep413Payload(intent), + }, + ...opts, + }); + } + + /** + * Request a signature over a NEP-413 off-chain message. Unlike a transaction signature, the + * signed bytes are an intent published to the solver relay, which executes it against the + * intents contract and pays the gas, so the signer needs no on-chain NEAR account or balance. + * @param options The options for the request intent signature. + * @returns The user. + */ + async requestIntentSignature(options: JavascriptRequestIntentSignatureOptions): Promise { + if ("redirectUri" in options && options.redirectUri) { + await this.requestIntentSignatureWithRedirect(options as JavascriptRequestIntentSignatureWithRedirectOptions); + } else { + await this.requestIntentSignatureWithPopup(options); + } + return this.getUserId(); + } + /** * Get the signature request. * @returns The signature request. diff --git a/packages/providers/javascript/src/types.ts b/packages/providers/javascript/src/types.ts index b9a511a..7023d16 100644 --- a/packages/providers/javascript/src/types.ts +++ b/packages/providers/javascript/src/types.ts @@ -2,6 +2,7 @@ import { Transaction } from "near-api-js/lib/transaction"; import { DelegateAction } from "@near-js/transactions"; import { PopupLoginOptions, RedirectLoginOptions } from "@auth0/auth0-spa-js"; import { FastAuthNetwork } from "@shared/core"; +import { NEP413Payload } from "./nep413"; export type { FastAuthNetwork } from "@shared/core"; @@ -51,3 +52,17 @@ export type JavascriptRequestDelegateActionSignatureWithPopupOptions = Javascrip export type JavascriptRequestDelegateActionSignatureOptions = | JavascriptRequestDelegateActionSignatureWithRedirectOptions | JavascriptRequestDelegateActionSignatureWithPopupOptions; + +export type JavascriptBaseRequestIntentSignatureOptions = JavascriptBaseRequestSignatureOptions & { + intent: NEP413Payload; +}; + +export type JavascriptRequestIntentSignatureWithRedirectOptions = JavascriptBaseRequestIntentSignatureOptions & + Omit; + +export type JavascriptRequestIntentSignatureWithPopupOptions = JavascriptBaseRequestIntentSignatureOptions & + Omit; + +export type JavascriptRequestIntentSignatureOptions = + | JavascriptRequestIntentSignatureWithRedirectOptions + | JavascriptRequestIntentSignatureWithPopupOptions; diff --git a/packages/providers/javascript/test/nep413.spec.ts b/packages/providers/javascript/test/nep413.spec.ts new file mode 100644 index 0000000..5652bc3 --- /dev/null +++ b/packages/providers/javascript/test/nep413.spec.ts @@ -0,0 +1,99 @@ +import { deserialize } from "borsh"; +import { + NEP413_NONCE_LENGTH, + NEP413_PAYLOAD_SCHEMA, + NEP413_PREFIX_TAG, + encodeNep413Payload, + generateNep413Nonce, + serializeNep413Payload, +} from "../src/nep413"; + +const FIXED_NONCE = Uint8Array.from(Array.from({ length: NEP413_NONCE_LENGTH }, (_, i) => (i * 3) % 256)); + +const INTENT_MESSAGE = JSON.stringify({ + signer_id: "trader.near", + deadline: "2026-01-01T00:00:00.000Z", + intents: [{ intent: "transfer", receiver_id: "deposit.near", tokens: { "nep141:usdc.near": "1000000" } }], +}); + +describe("serializeNep413Payload", () => { + it("round-trips every field through the NEP-413 schema", () => { + const bytes = serializeNep413Payload({ + message: INTENT_MESSAGE, + nonce: FIXED_NONCE, + recipient: "intents.near", + }); + + const decoded = deserialize(NEP413_PAYLOAD_SCHEMA, bytes) as any; + + expect(decoded.tag).toBe(NEP413_PREFIX_TAG); + expect(decoded.message).toBe(INTENT_MESSAGE); + expect(Array.from(decoded.nonce)).toEqual(Array.from(FIXED_NONCE)); + expect(decoded.recipient).toBe("intents.near"); + expect(decoded.callbackUrl).toBeNull(); + }); + + it("always stamps the NEP-413 domain tag", () => { + // Domain separation is what keeps these bytes from being a valid NEAR transaction. + // It is not caller-supplied precisely so it cannot be omitted. + const bytes = serializeNep413Payload({ message: "{}", nonce: FIXED_NONCE, recipient: "intents.near" }); + const decoded = deserialize(NEP413_PAYLOAD_SCHEMA, bytes) as any; + expect(decoded.tag).toBe(NEP413_PREFIX_TAG); + expect(NEP413_PREFIX_TAG).toBe(Math.pow(2, 31) + 413); + }); + + it("includes the callback url when given", () => { + const bytes = serializeNep413Payload({ + message: "{}", + nonce: FIXED_NONCE, + recipient: "intents.near", + callbackUrl: "https://app.example/cb", + }); + const decoded = deserialize(NEP413_PAYLOAD_SCHEMA, bytes) as any; + expect(decoded.callbackUrl).toBe("https://app.example/cb"); + }); + + it("generates a nonce when none is supplied", () => { + const bytes = serializeNep413Payload({ message: "{}", recipient: "intents.near" }); + const decoded = deserialize(NEP413_PAYLOAD_SCHEMA, bytes) as any; + expect(decoded.nonce).toHaveLength(NEP413_NONCE_LENGTH); + }); + + it("is deterministic for a fixed nonce", () => { + const params = { message: INTENT_MESSAGE, nonce: FIXED_NONCE, recipient: "intents.near" }; + expect(Array.from(serializeNep413Payload(params))).toEqual(Array.from(serializeNep413Payload(params))); + }); + + it("produces different bytes for different recipients", () => { + const a = serializeNep413Payload({ message: "{}", nonce: FIXED_NONCE, recipient: "intents.near" }); + const b = serializeNep413Payload({ message: "{}", nonce: FIXED_NONCE, recipient: "intents.testnet" }); + expect(Array.from(a)).not.toEqual(Array.from(b)); + }); + + it("rejects a nonce of the wrong length", () => { + expect(() => serializeNep413Payload({ message: "{}", nonce: new Uint8Array(16), recipient: "intents.near" })).toThrow( + /must be 32 bytes/, + ); + }); +}); + +describe("generateNep413Nonce", () => { + it("returns 32 bytes", () => { + expect(generateNep413Nonce()).toHaveLength(NEP413_NONCE_LENGTH); + }); + + it("does not repeat across calls", () => { + expect(Array.from(generateNep413Nonce())).not.toEqual(Array.from(generateNep413Nonce())); + }); +}); + +describe("encodeNep413Payload", () => { + it("returns the serialized bytes as a plain number array", () => { + const params = { message: INTENT_MESSAGE, nonce: FIXED_NONCE, recipient: "intents.near" }; + const encoded = encodeNep413Payload(params); + + expect(Array.isArray(encoded)).toBe(true); + expect(encoded).toEqual(Array.from(serializeNep413Payload(params))); + expect(encoded.every((value) => Number.isInteger(value) && value >= 0 && value <= 255)).toBe(true); + }); +}); diff --git a/packages/providers/javascript/test/provider.spec.ts b/packages/providers/javascript/test/provider.spec.ts index 32845ec..37e66b5 100644 --- a/packages/providers/javascript/test/provider.spec.ts +++ b/packages/providers/javascript/test/provider.spec.ts @@ -678,4 +678,92 @@ describe("JavascriptProvider", () => { await expect(provider.getSignatureRequest()).rejects.toThrow("JWT decode failed"); }); }); + + describe("requestIntentSignature", () => { + const intent = { + message: JSON.stringify({ + signer_id: "trader.near", + deadline: "2026-01-01T00:00:00.000Z", + intents: [{ intent: "transfer", receiver_id: "deposit.near", tokens: { "nep141:usdc.near": "1000000" } }], + }), + nonce: Uint8Array.from(Array.from({ length: 32 }, (_, i) => i)), + recipient: "intents.near", + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockAuth0Client.getIdTokenClaims.mockResolvedValue({ sub: "test-user-id" }); + }); + + describe("with redirect", () => { + it("should call loginWithRedirect with the encoded intent", async () => { + mockAuth0Client.loginWithRedirect.mockResolvedValue(undefined); + + await provider.requestIntentSignature({ intent, redirectUri: "http://localhost:3000/callback" }); + + const params = mockAuth0Client.loginWithRedirect.mock.calls[0][0].authorizationParams; + expect(params.audience).toBe("auth0.jwt.fast-auth.testnet"); + expect(params.scope).toBe("transaction:sign"); + expect(params.redirect_uri).toBe("http://localhost:3000/callback"); + expect(Array.isArray(params.intent)).toBe(true); + expect(mockAuth0Client.loginWithPopup).not.toHaveBeenCalled(); + }); + + it("should encode the intent with the NEP-413 domain tag first", async () => { + mockAuth0Client.loginWithRedirect.mockResolvedValue(undefined); + + await provider.requestIntentSignature({ intent, redirectUri: "http://localhost:3000/callback" }); + + // The tag is a little-endian u32 at offset 0 — the first thing the action checks. + const encoded: number[] = mockAuth0Client.loginWithRedirect.mock.calls[0][0].authorizationParams.intent; + const tag = encoded[0] | (encoded[1] << 8) | (encoded[2] << 16) | (encoded[3] << 24); + expect(tag >>> 0).toBe(Math.pow(2, 31) + 413); + }); + + it("should propagate errors from loginWithRedirect", async () => { + mockAuth0Client.loginWithRedirect.mockRejectedValue(new Error("Login redirect failed")); + + await expect(provider.requestIntentSignature({ intent, redirectUri: "http://localhost:3000/callback" })).rejects.toThrow( + "Login redirect failed", + ); + }); + }); + + describe("with popup", () => { + it("should call loginWithPopup when no redirectUri is provided", async () => { + mockAuth0Client.loginWithPopup.mockResolvedValue(undefined); + + await provider.requestIntentSignature({ intent }); + + const params = mockAuth0Client.loginWithPopup.mock.calls[0][0].authorizationParams; + expect(params.audience).toBe("auth0.jwt.fast-auth.testnet"); + expect(params.scope).toBe("transaction:sign"); + expect(Array.isArray(params.intent)).toBe(true); + expect(mockAuth0Client.loginWithRedirect).not.toHaveBeenCalled(); + }); + + it("should propagate errors from loginWithPopup", async () => { + mockAuth0Client.loginWithPopup.mockRejectedValue(new Error("Login popup failed")); + + await expect(provider.requestIntentSignature({ intent })).rejects.toThrow("Login popup failed"); + }); + }); + + it("should return the user id after signing", async () => { + mockAuth0Client.loginWithPopup.mockResolvedValue(undefined); + + const result = await provider.requestIntentSignature({ intent }); + + expect(result).toEqual({ userId: "test-user-id" }); + }); + + it("should throw when the user is not logged in", async () => { + mockAuth0Client.loginWithPopup.mockResolvedValue(undefined); + mockAuth0Client.getIdTokenClaims.mockResolvedValue(undefined); + + await expect(provider.requestIntentSignature({ intent })).rejects.toThrow( + new JavascriptProviderError(JavascriptProviderErrorCodes.USER_NOT_LOGGED_IN), + ); + }); + }); }); diff --git a/packages/sdks/browser/src/signers/signer.ts b/packages/sdks/browser/src/signers/signer.ts index acc942c..37df3fe 100644 --- a/packages/sdks/browser/src/signers/signer.ts +++ b/packages/sdks/browser/src/signers/signer.ts @@ -105,6 +105,18 @@ export class FastAuthSigner

{ return await this.fastAuthProvider.requestDelegateActionSignature(...args); } + /** + * Request a signature over a NEP-413 off-chain message, used to authorize NEAR Intents without submitting a transaction. The method is optional on the provider interface, so providers that have not implemented the flow raise a clear error instead of failing on an undefined call. + * @param args The arguments to request an intent signature. + * @returns The signed intent response. + */ + async requestIntentSignature(...args: any[]) { + if (typeof this.fastAuthProvider.requestIntentSignature !== "function") { + throw new Error("The configured FastAuth provider does not support NEP-413 intent signatures"); + } + return await this.fastAuthProvider.requestIntentSignature(...args); + } + /** * Get a signature request. * @returns The signature request. diff --git a/packages/shared/core/src/index.ts b/packages/shared/core/src/index.ts index 04099fe..c796ee7 100644 --- a/packages/shared/core/src/index.ts +++ b/packages/shared/core/src/index.ts @@ -24,6 +24,7 @@ export type { LoginResponse, RequestTransactionSignatureResponse, RequestDelegateActionSignatureResponse, + RequestIntentSignatureResponse, GetSignatureRequestResponse, IFastAuthProvider, MPCContractAlgorithm, diff --git a/packages/shared/core/src/provider.ts b/packages/shared/core/src/provider.ts index 150a9aa..a4ed60c 100644 --- a/packages/shared/core/src/provider.ts +++ b/packages/shared/core/src/provider.ts @@ -20,6 +20,11 @@ export type RequestTransactionSignatureResponse = User; */ export type RequestDelegateActionSignatureResponse = User; +/** + * Response of a NEP-413 intent signature request. + */ +export type RequestIntentSignatureResponse = User; + /** * Response returned after a successful signature request */ @@ -31,6 +36,12 @@ export interface IFastAuthProvider { isLoggedIn(): Promise; requestTransactionSignature(...args: any[]): Promise; requestDelegateActionSignature(...args: any[]): Promise; + /** + * Request a signature over a NEP-413 off-chain message, used to authorize NEAR Intents + * without submitting a transaction. Optional: providers that have not implemented the + * flow simply omit it, and callers must check for its presence before use. + */ + requestIntentSignature?(...args: any[]): Promise; getSignatureRequest(): Promise; getPath(): Promise; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd029d8..8136286 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -718,6 +718,9 @@ importers: '@near-js/transactions': specifier: ^2.0.2 version: 2.2.3(@near-js/crypto@1.4.2)(@near-js/types@0.3.1)(@near-js/utils@1.1.0) + borsh: + specifier: ^2.0.0 + version: 2.0.0 elliptic: specifier: ^6.6.1 version: 6.6.1 From 9a7e0e1398785aab406d27868acbd5182c0dd45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Rivera?= Date: Thu, 13 Aug 2026 16:45:18 +0200 Subject: [PATCH 2/6] refactor(auth0): generalize the payload to all of NEP-413, not just intents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first pass was written from the NEAR Intents use case and anchored two things it had no business anchoring: `recipient` was pinned to intents.near, and the message had to carry a non-empty `intents` array. Between them, what shipped was not "sign NEP-413" but "sign NEAR Intents". NEP-413 is broader than that. The message is an arbitrary human-readable string — a wallet sign-in challenge ("Sign in to example.com") is as valid as an intents body — and `recipient` names whatever application the message is addressed to. The standard's protection is that the user *sees* the recipient, not that the wallet restricts it, which is how NEAR wallets behave. What changed: - recipient: any account by default; NEP413_ALLOWED_RECIPIENTS opts a tenant into a comma-separated allowlist. A list of only separators is treated as unset rather than as an allowlist that rejects everything. - message: any non-empty string. JSON is parsed opportunistically so a NEAR Intents body still gets the per-intent breakdown, and anything else is shown verbatim — which is what the standard expects of a signed string. - callbackUrl is surfaced on the approval screen; it is part of the signed payload and the user should see where the result goes. - state is accepted and echoed back, per the NEP. It is deliberately not part of the signed bytes: the Payload struct does not include it. - buildNep413SignedMessage() assembles the {accountId, publicKey, signature, state} response shape the standard defines. The domain tag check is untouched and non-negotiable — it is what keeps the bytes from doubling as a NEAR transaction, and it is unrelated to the recipient. Renamed throughout to match the scope: `intent` -> `nep413` (query key, form, secrets) and requestIntentSignature -> requestMessageSignature. Verified while writing this: the fa contract already applies env::sha256 to sign_payload before handing it to the MPC (fa/src/lib.rs:534 for eddsa), which is exactly what NEP-413 requires — sign the SHA-256 of the prefix plus serialized payload. No contract change needed for that either. 227 tests passing, covering plain-text messages, non-intents JSON, arbitrary recipients, the allowlist, and the fallback that shows the raw message whenever the structured view is unavailable. Co-Authored-By: Claude Opus 5 (1M context) --- .../auth0/src/actions/authorize-app.action.js | 126 ++++++++----- packages/auth0/src/forms/build.js | 2 +- .../delegate_action/delegate_action_form.json | 6 +- .../{intent => nep413}/details/index.css | 0 .../forms/{intent => nep413}/details/index.js | 13 +- .../nep413_form.json} | 24 ++- .../nep413_form_base.json} | 42 ++++- .../auth0/src/forms/shared/helpers/index.js | 70 +++++-- .../forms/transaction/transaction_form.json | 6 +- packages/auth0/test/fixtures/builders.js | 10 +- packages/auth0/test/intent-decoding.spec.js | 125 ------------- packages/auth0/test/nep413-decoding.spec.js | 171 ++++++++++++++++++ ...ndlers.spec.js => nep413-handlers.spec.js} | 72 ++++---- ...helpers.spec.js => nep413-helpers.spec.js} | 50 +++-- packages/providers/javascript/src/nep413.ts | 52 +++++- packages/providers/javascript/src/provider.ts | 46 ++--- packages/providers/javascript/src/types.ts | 22 ++- .../providers/javascript/test/nep413.spec.ts | 57 ++++++ .../javascript/test/provider.spec.ts | 28 +-- packages/sdks/browser/src/signers/signer.ts | 14 +- packages/shared/core/src/index.ts | 2 +- packages/shared/core/src/provider.ts | 13 +- 22 files changed, 623 insertions(+), 328 deletions(-) rename packages/auth0/src/forms/{intent => nep413}/details/index.css (100%) rename packages/auth0/src/forms/{intent => nep413}/details/index.js (52%) rename packages/auth0/src/forms/{intent/intent_form.json => nep413/nep413_form.json} (82%) rename packages/auth0/src/forms/{intent/intent_form_base.json => nep413/nep413_form_base.json} (81%) delete mode 100644 packages/auth0/test/intent-decoding.spec.js create mode 100644 packages/auth0/test/nep413-decoding.spec.js rename packages/auth0/test/{intent-handlers.spec.js => nep413-handlers.spec.js} (65%) rename packages/auth0/test/{intent-helpers.spec.js => nep413-helpers.spec.js} (69%) diff --git a/packages/auth0/src/actions/authorize-app.action.js b/packages/auth0/src/actions/authorize-app.action.js index c1ae9dd..23d23a5 100644 --- a/packages/auth0/src/actions/authorize-app.action.js +++ b/packages/auth0/src/actions/authorize-app.action.js @@ -5,7 +5,7 @@ const { deserialize } = require("borsh"); const TRANSACTION_KEY = "transaction"; const DELEGATE_ACTION_KEY = "delegateAction"; -const INTENT_KEY = "intent"; +const NEP413_KEY = "nep413"; // NEP-413 CONSTANTS @@ -19,9 +19,6 @@ const INTENT_KEY = "intent"; */ const NEP413_PREFIX_TAG = 2147484061; -/** Default verifier the intents are allowed to target when no secret is configured. */ -const DEFAULT_INTENTS_RECIPIENT = "intents.near"; - // SCHEMA definitions const SCHEMA = new (class BorshSchema { Ed25519Signature = { @@ -234,52 +231,75 @@ function decodeDelegateAction(encodedDelegateAction) { } /** - * Decode and validate a NEP-413 intent payload arriving on the authorize query string. + * Parse the optional recipient allowlist from tenant secrets. + * + * NEP-413 places no constraint on `recipient` — it is the application the message is addressed + * to, and the standard's protection is that the user *sees* it, not that the wallet restricts + * it. So an unset secret means "any recipient", matching how NEAR wallets behave. A tenant that + * wants to serve exactly one application can still pin it here. + * @param {string|undefined} secret Comma-separated account list, or undefined. + * @returns {string[]|null} The allowlist, or null when unrestricted. + */ +function parseRecipientAllowlist(secret) { + if (!secret) return null; + const entries = String(secret) + .split(",") + .map((value) => value.trim()) + .filter(Boolean); + return entries.length > 0 ? entries : null; +} + +/** + * Decode and validate a NEP-413 payload arriving on the authorize query string. * - * Every check here is load-bearing for the security model. The guard contract only verifies - * that `fatxn` equals the bytes the MPC is asked to sign — it never inspects them — so this - * function is the only place that establishes *what* the user is being asked to approve: + * The guard contract only verifies that `fatxn` equals the bytes the MPC is asked to sign — it + * never inspects them — so this function is the only place that establishes *what* the user is + * being asked to approve. Two checks carry that weight: * - * 1. The borsh payload must deserialize cleanly under the NEP-413 schema. - * 2. The domain tag must be exactly NEP413_PREFIX_TAG, so the bytes cannot also be a valid - * NEAR transaction. Never trust the caller to have set it. - * 3. The recipient must be the expected verifier, so a signed intent cannot be redirected - * to a different contract. - * 4. The message must be JSON carrying a non-empty `intents` array — otherwise there is - * nothing meaningful to show the user, and an unrenderable payload must not be signed. + * 1. The borsh payload must deserialize cleanly under the NEP-413 schema, and the domain tag + * must be exactly NEP413_PREFIX_TAG so the bytes cannot also be a valid NEAR transaction. + * Never trust the caller to have set the tag. + * 2. There must be a message to show. Signing something the approval screen cannot display + * would defeat the consent guarantee the whole flow rests on. * - * @param {string} encodedIntent Comma-separated byte string from the query. - * @param {string} expectedRecipient Verifier account the intents must target. - * @returns {{payload: object, message: object}} The decoded payload and parsed message. + * The recipient is only constrained when a tenant opts in via the allowlist; per the standard it + * is shown to the user rather than restricted. + * @param {string} encodedPayload Comma-separated byte string from the query. + * @param {string[]|null} recipientAllowlist Accounts the message may target, or null for any. + * @returns {{payload: object, message: object|null}} The payload, plus the message parsed as JSON when it is JSON. * @throws {Error} With a user-facing reason when any check fails. */ -function decodeIntent(encodedIntent, expectedRecipient) { - const bytes = Uint8Array.from(String(encodedIntent).split(",").map((value) => Number(value))); +function decodeNep413Payload(encodedPayload, recipientAllowlist) { + const bytes = Uint8Array.from(String(encodedPayload).split(",").map((value) => Number(value))); let payload; try { payload = deserialize(SCHEMA.NEP413Payload, bytes); } catch (e) { - throw new Error("Intent payload is not a valid NEP-413 message"); + throw new Error("Payload is not a valid NEP-413 message"); } if (payload.tag !== NEP413_PREFIX_TAG) { - throw new Error("Intent payload is missing the NEP-413 domain tag"); + throw new Error("Payload is missing the NEP-413 domain tag"); } - if (payload.recipient !== expectedRecipient) { - throw new Error(`Intent payload targets an unexpected recipient: ${payload.recipient}`); + if (typeof payload.message !== "string" || payload.message.length === 0) { + throw new Error("NEP-413 message is empty"); } - let message; - try { - message = JSON.parse(payload.message); - } catch (e) { - throw new Error("Intent message is not valid JSON"); + if (recipientAllowlist && !recipientAllowlist.includes(payload.recipient)) { + throw new Error(`NEP-413 message targets an unexpected recipient: ${payload.recipient}`); } - if (!message || !Array.isArray(message.intents) || message.intents.length === 0) { - throw new Error("Intent message carries no intents to approve"); + // A JSON message may be a structured payload the approval screen can render richly (NEAR + // Intents being the case we know about). Plain-text messages are equally valid and are + // shown verbatim, so failing to parse is not an error. + let message = null; + try { + const parsed = JSON.parse(payload.message); + if (parsed && typeof parsed === "object") message = parsed; + } catch (e) { + message = null; } return { payload, message }; @@ -289,6 +309,19 @@ function stringifyIntents(intents) { return JSON.stringify(intents, (_, value) => (typeof value === "bigint" ? value.toString() : value), 2); } +/** + * Extract the NEAR Intents body from a decoded message, when the message is one. + * + * Recognising it is what lets the approval screen show "transfer 1 USDC to X" instead of a wall + * of JSON. Anything else is not an error — it is just a message that gets displayed as text. + * @param {object|null} message The parsed message, or null when it was not JSON. + * @returns {Array|null} The intents array, or null when this is not an intents message. + */ +function extractIntents(message) { + if (!message || !Array.isArray(message.intents) || message.intents.length === 0) return null; + return message.intents; +} + function stringifyActions(actions) { return JSON.stringify( actions, @@ -316,8 +349,8 @@ exports.onExecutePostLogin = async (event, api) => { const isOnchainAudience = event.resource_server?.identifier === onchainAudience; const hasTxParams = TRANSACTION_KEY in query; const hasDelegateParams = DELEGATE_ACTION_KEY in query; - const hasIntentParams = INTENT_KEY in query; - const payloadCount = [hasTxParams, hasDelegateParams, hasIntentParams].filter(Boolean).length; + const hasNep413Params = NEP413_KEY in query; + const payloadCount = [hasTxParams, hasDelegateParams, hasNep413Params].filter(Boolean).length; const hasSigningPayload = payloadCount > 0; if (isOnchainAudience && !hasSigningPayload) { @@ -390,11 +423,11 @@ exports.onExecutePostLogin = async (event, api) => { query.delegateAction.split(",").map((value) => Number(value)), ); } else { - const expectedRecipient = event.secrets.INTENTS_RECIPIENT || DEFAULT_INTENTS_RECIPIENT; + const recipientAllowlist = parseRecipientAllowlist(event.secrets.NEP413_ALLOWED_RECIPIENTS); let decoded; try { - decoded = decodeIntent(query.intent, expectedRecipient); + decoded = decodeNep413Payload(query.nep413, recipientAllowlist); } catch (error) { // A payload we cannot decode is a payload we cannot show the user. Signing it would // break the consent guarantee the whole flow rests on, so refuse instead. @@ -402,18 +435,26 @@ exports.onExecutePostLogin = async (event, api) => { } const { payload, message } = decoded; - api.prompt.render(event.secrets.INTENT_FORM, { + const intents = extractIntents(message); + + api.prompt.render(event.secrets.NEP413_FORM, { fields: { ...branding, - signerId: message.signer_id ?? "", + // Always shown: per NEP-413 the recipient is the user's protection against a + // message being relayed to a third party, so it must be on screen either way. recipient: payload.recipient, - deadline: message.deadline ?? "", - intents: stringifyIntents(message.intents), + callbackUrl: payload.callbackUrl ?? "", + // The raw message is always passed through. The form prefers the structured + // intents view when present and falls back to showing this verbatim. + message: payload.message, + signerId: (message && message.signer_id) ?? "", + deadline: (message && message.deadline) ?? "", + intents: intents ? stringifyIntents(intents) : "", }, }); api.accessToken.setCustomClaim( "fatxn", - query.intent.split(",").map((value) => Number(value)), + query.nep413.split(",").map((value) => Number(value)), ); } }; @@ -437,9 +478,10 @@ exports.onContinuePostLogin = async (event, api) => { // `onContinuePostLogin`; extra exports are inert in production. exports.parseTransaction = parseTransaction; exports.decodeDelegateAction = decodeDelegateAction; -exports.decodeIntent = decodeIntent; +exports.decodeNep413Payload = decodeNep413Payload; +exports.parseRecipientAllowlist = parseRecipientAllowlist; +exports.extractIntents = extractIntents; exports.stringifyActions = stringifyActions; exports.stringifyIntents = stringifyIntents; exports.SCHEMA = SCHEMA; exports.NEP413_PREFIX_TAG = NEP413_PREFIX_TAG; -exports.DEFAULT_INTENTS_RECIPIENT = DEFAULT_INTENTS_RECIPIENT; diff --git a/packages/auth0/src/forms/build.js b/packages/auth0/src/forms/build.js index 5e5f520..897d24b 100644 --- a/packages/auth0/src/forms/build.js +++ b/packages/auth0/src/forms/build.js @@ -32,7 +32,7 @@ const HELPERS_PATH = path.join(FORMS_DIR, "shared", "helpers", "index.js"); const FORMS = [ { name: "transaction", base: "transaction_form_base.json", out: "transaction_form.json" }, { name: "delegate_action", base: "delegate_action_form_base.json", out: "delegate_action_form.json" }, - { name: "intent", base: "intent_form_base.json", out: "intent_form.json" }, + { name: "nep413", base: "nep413_form_base.json", out: "nep413_form.json" }, ]; function readHelpersPreamble() { diff --git a/packages/auth0/src/forms/delegate_action/delegate_action_form.json b/packages/auth0/src/forms/delegate_action/delegate_action_form.json index b84aca8..da241da 100644 --- a/packages/auth0/src/forms/delegate_action/delegate_action_form.json +++ b/packages/auth0/src/forms/delegate_action/delegate_action_form.json @@ -24,7 +24,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in _form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Render the message body of a NEP-413 request verbatim.\n *\n * This is the default view, and the one that matters most: NEP-413 messages are arbitrary\n * strings meant to be read by a human (\"Sign in to example.com\"), so showing the text exactly\n * as it will be signed is the honest presentation. Pretty-printing is applied only when the\n * message is JSON, purely for legibility.\n */\nfunction messageContent(message) {\n const container = document.createElement(\"div\");\n let display = message;\n try {\n const parsed = JSON.parse(message);\n if (parsed && typeof parsed === \"object\") display = JSON.stringify(parsed, null, 2);\n } catch (e) {\n display = message;\n }\n container.appendChild(createTextContent(\"Message\", display));\n return container;\n}\n\n/**\n * Build the details DOM tree for a NEP-413 signature approval.\n *\n * Two presentations, one payload: when the message carries NEAR Intents the intents are broken\n * out one by one, and otherwise the raw message is shown as text. Either way the top-level\n * fields — recipient above all — are rendered, because under NEP-413 the recipient is what\n * tells the user which application their signature is addressed to.\n * @param {object} params The render parameters.\n * @param {Array<{label: string, value: string|undefined}>} params.fields Top-level fields (recipient, callback URL, signer, deadline).\n * @param {string} params.message The raw NEP-413 message, shown when there are no intents to break out.\n * @param {string} [params.intents] JSON string with the intents array, when the message is a NEAR Intents body.\n * @returns {HTMLElement} The details node.\n */\nfunction renderNep413Details(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n let parsedIntents = null;\n if (params.intents) {\n try {\n const candidate = JSON.parse(params.intents);\n if (Array.isArray(candidate) && candidate.length > 0) parsedIntents = candidate;\n } catch (e) {\n parsedIntents = null;\n }\n }\n\n // No intents to break out (or they were unreadable) — show the message itself. Falling back\n // to the raw text keeps every NEP-413 message approvable, not just the ones we model.\n if (!parsedIntents) {\n box.appendChild(messageContent(params.message || \"\"));\n return box;\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n messageContent: messageContent,\n renderNep413Details: renderNep413Details,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in _form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", "css": ".avatar {\n width: 4.5rem;\n height: 4.5rem;\n border-radius: 12px;\n object-fit: cover;\n}\n\n.layout {\n position: relative;\n display: flex;\n flex-direction: row;\n gap: 1.5rem;\n align-items: center;\n justify-content: center;\n}\n\n.circle {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 999px;\n background-color: #f6f6f6;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.icon {\n width: 1.2rem;\n height: 1.2rem;\n}\n", "params": { "iconUrl": "https://peersyst-public-production.s3.eu-west-1.amazonaws.com/ab6afba7-91a2-4c53-b2a3-90e4f8c3492a.png", @@ -49,7 +49,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the delegate action details (sender, receiver, max block height, actions).\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppDelegateActionDetails(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderDetails({\n fields: [\n { label: \"Sender ID\", value: params.senderId },\n { label: \"Receiver ID\", value: params.receiverId },\n { label: \"Max Block Height\", value: params.maxBlockHeight },\n ],\n actions: params.actions,\n options: { showYoctoConversion: false },\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Render the message body of a NEP-413 request verbatim.\n *\n * This is the default view, and the one that matters most: NEP-413 messages are arbitrary\n * strings meant to be read by a human (\"Sign in to example.com\"), so showing the text exactly\n * as it will be signed is the honest presentation. Pretty-printing is applied only when the\n * message is JSON, purely for legibility.\n */\nfunction messageContent(message) {\n const container = document.createElement(\"div\");\n let display = message;\n try {\n const parsed = JSON.parse(message);\n if (parsed && typeof parsed === \"object\") display = JSON.stringify(parsed, null, 2);\n } catch (e) {\n display = message;\n }\n container.appendChild(createTextContent(\"Message\", display));\n return container;\n}\n\n/**\n * Build the details DOM tree for a NEP-413 signature approval.\n *\n * Two presentations, one payload: when the message carries NEAR Intents the intents are broken\n * out one by one, and otherwise the raw message is shown as text. Either way the top-level\n * fields — recipient above all — are rendered, because under NEP-413 the recipient is what\n * tells the user which application their signature is addressed to.\n * @param {object} params The render parameters.\n * @param {Array<{label: string, value: string|undefined}>} params.fields Top-level fields (recipient, callback URL, signer, deadline).\n * @param {string} params.message The raw NEP-413 message, shown when there are no intents to break out.\n * @param {string} [params.intents] JSON string with the intents array, when the message is a NEAR Intents body.\n * @returns {HTMLElement} The details node.\n */\nfunction renderNep413Details(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n let parsedIntents = null;\n if (params.intents) {\n try {\n const candidate = JSON.parse(params.intents);\n if (Array.isArray(candidate) && candidate.length > 0) parsedIntents = candidate;\n } catch (e) {\n parsedIntents = null;\n }\n }\n\n // No intents to break out (or they were unreadable) — show the message itself. Falling back\n // to the raw text keeps every NEP-413 message approvable, not just the ones we model.\n if (!parsedIntents) {\n box.appendChild(messageContent(params.message || \"\"));\n return box;\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n messageContent: messageContent,\n renderNep413Details: renderNep413Details,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the delegate action details (sender, receiver, max block height, actions).\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppDelegateActionDetails(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderDetails({\n fields: [\n { label: \"Sender ID\", value: params.senderId },\n { label: \"Receiver ID\", value: params.receiverId },\n { label: \"Max Block Height\", value: params.maxBlockHeight },\n ],\n actions: params.actions,\n options: { showYoctoConversion: false },\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", "css": ".box {\n padding: 1.25rem;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1.25rem;\n width: 100%;\n\n border-radius: 1rem;\n border: 1px solid #e5e5e5;\n}\n\n.text-content {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n}\n\n.label {\n font-size: 0.75rem;\n color: #999999;\n font-weight: 500;\n}\n\n.value {\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 600;\n}\n\n.json-content {\n display: flex;\n padding: 0.5rem;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n border-radius: 12px;\n background-color: #fafafa;\n\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 500;\n}\n\n.accordion {\n width: 100%;\n border: 1px solid #e5e5e5;\n border-radius: 6px;\n overflow: hidden;\n}\n\n.accordion-header {\n background: #fff;\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n}\n\n.accordion-header-content {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n}\n\n.accordion-left-content {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.accordion-header-label {\n font-size: 0.75rem;\n color: black;\n font-weight: 500;\n}\n\n.warning-icon {\n font-size: 1rem;\n color: #ff4444;\n}\n\n.expand-icon {\n font-size: 1.2rem;\n color: #666;\n font-weight: bold;\n line-height: 1;\n transition: transform 0.2s ease;\n user-select: none;\n min-width: 20px;\n text-align: center;\n}\n\n.accordion-content {\n max-height: 0;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #fafafa;\n color: #0a0a0a;\n font-weight: 400;\n transition:\n max-height 0.3s ease,\n padding 0.3s ease;\n padding: 0 1rem;\n}\n\n.accordion-content.open {\n padding: 12px 16px;\n max-height: 500px;\n}\n\n.actions-container {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n gap: 0.25rem;\n}\n\n.accordion-content .label {\n font-size: 0.75rem;\n}\n\n.accordion-content .value {\n font-size: 0.75rem;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.accordion-content > div {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.action-description {\n font-size: 0.75rem;\n color: #3f4246;\n margin: 0;\n}\n\n.warning-callout {\n background-color: #ffebee;\n padding: 16px;\n border-radius: 4px;\n margin: 0;\n color: #c62828;\n}\n", "params": { "actions": "{{ fields.actions }}", @@ -67,7 +67,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Render the message body of a NEP-413 request verbatim.\n *\n * This is the default view, and the one that matters most: NEP-413 messages are arbitrary\n * strings meant to be read by a human (\"Sign in to example.com\"), so showing the text exactly\n * as it will be signed is the honest presentation. Pretty-printing is applied only when the\n * message is JSON, purely for legibility.\n */\nfunction messageContent(message) {\n const container = document.createElement(\"div\");\n let display = message;\n try {\n const parsed = JSON.parse(message);\n if (parsed && typeof parsed === \"object\") display = JSON.stringify(parsed, null, 2);\n } catch (e) {\n display = message;\n }\n container.appendChild(createTextContent(\"Message\", display));\n return container;\n}\n\n/**\n * Build the details DOM tree for a NEP-413 signature approval.\n *\n * Two presentations, one payload: when the message carries NEAR Intents the intents are broken\n * out one by one, and otherwise the raw message is shown as text. Either way the top-level\n * fields — recipient above all — are rendered, because under NEP-413 the recipient is what\n * tells the user which application their signature is addressed to.\n * @param {object} params The render parameters.\n * @param {Array<{label: string, value: string|undefined}>} params.fields Top-level fields (recipient, callback URL, signer, deadline).\n * @param {string} params.message The raw NEP-413 message, shown when there are no intents to break out.\n * @param {string} [params.intents] JSON string with the intents array, when the message is a NEAR Intents body.\n * @returns {HTMLElement} The details node.\n */\nfunction renderNep413Details(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n let parsedIntents = null;\n if (params.intents) {\n try {\n const candidate = JSON.parse(params.intents);\n if (Array.isArray(candidate) && candidate.length > 0) parsedIntents = candidate;\n } catch (e) {\n parsedIntents = null;\n }\n }\n\n // No intents to break out (or they were unreadable) — show the message itself. Falling back\n // to the raw text keeps every NEP-413 message approvable, not just the ones we model.\n if (!parsedIntents) {\n box.appendChild(messageContent(params.message || \"\"));\n return box;\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n messageContent: messageContent,\n renderNep413Details: renderNep413Details,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", "css": ".decision-layout {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n width: 100%;\n}\n\n.decision-button {\n width: 100%;\n padding: 0.75rem 1rem;\n border-radius: 0.75rem;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n border: 1px solid transparent;\n}\n\n.decision-button.approve {\n background-color: #0a0a0a;\n color: #ffffff;\n}\n\n.decision-button.deny {\n background-color: #ffffff;\n color: #3f4246;\n border-color: #e5e5e5;\n}\n", "params": { "approveText": "Approve", diff --git a/packages/auth0/src/forms/intent/details/index.css b/packages/auth0/src/forms/nep413/details/index.css similarity index 100% rename from packages/auth0/src/forms/intent/details/index.css rename to packages/auth0/src/forms/nep413/details/index.css diff --git a/packages/auth0/src/forms/intent/details/index.js b/packages/auth0/src/forms/nep413/details/index.js similarity index 52% rename from packages/auth0/src/forms/intent/details/index.js rename to packages/auth0/src/forms/nep413/details/index.js index 3b334f4..f330f0e 100644 --- a/packages/auth0/src/forms/intent/details/index.js +++ b/packages/auth0/src/forms/nep413/details/index.js @@ -1,19 +1,24 @@ /** - * Custom field that renders the NEP-413 intent details (signer, verifier, deadline, intents). + * Custom field that renders a NEP-413 signature request (recipient, callback URL, message). + * + * When the message carries NEAR Intents the helper breaks them out one by one; otherwise the + * message is shown verbatim, which is what the standard expects of an arbitrary signed string. * * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js * (or shimmed by the playground via helpers-shim.js). */ -function AuthorizeAppIntentDetails(context) { +function AuthorizeAppNep413Details(context) { return { init: function () { const params = context.custom.getParams(); - return __auth0FormHelpers.renderIntentDetails({ + return __auth0FormHelpers.renderNep413Details({ fields: [ + { label: "Recipient", value: params.recipient }, + { label: "Callback URL", value: params.callbackUrl }, { label: "Signer ID", value: params.signerId }, - { label: "Verifier", value: params.recipient }, { label: "Deadline", value: params.deadline }, ], + message: params.message, intents: params.intents, }); }, diff --git a/packages/auth0/src/forms/intent/intent_form.json b/packages/auth0/src/forms/nep413/nep413_form.json similarity index 82% rename from packages/auth0/src/forms/intent/intent_form.json rename to packages/auth0/src/forms/nep413/nep413_form.json index 3289bac..fccdb5a 100644 --- a/packages/auth0/src/forms/intent/intent_form.json +++ b/packages/auth0/src/forms/nep413/nep413_form.json @@ -1,7 +1,7 @@ { "version": "4.0.0", "form": { - "name": "Intent form", + "name": "NEP-413 form", "languages": { "primary": "en" }, @@ -24,7 +24,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in _form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Render the message body of a NEP-413 request verbatim.\n *\n * This is the default view, and the one that matters most: NEP-413 messages are arbitrary\n * strings meant to be read by a human (\"Sign in to example.com\"), so showing the text exactly\n * as it will be signed is the honest presentation. Pretty-printing is applied only when the\n * message is JSON, purely for legibility.\n */\nfunction messageContent(message) {\n const container = document.createElement(\"div\");\n let display = message;\n try {\n const parsed = JSON.parse(message);\n if (parsed && typeof parsed === \"object\") display = JSON.stringify(parsed, null, 2);\n } catch (e) {\n display = message;\n }\n container.appendChild(createTextContent(\"Message\", display));\n return container;\n}\n\n/**\n * Build the details DOM tree for a NEP-413 signature approval.\n *\n * Two presentations, one payload: when the message carries NEAR Intents the intents are broken\n * out one by one, and otherwise the raw message is shown as text. Either way the top-level\n * fields — recipient above all — are rendered, because under NEP-413 the recipient is what\n * tells the user which application their signature is addressed to.\n * @param {object} params The render parameters.\n * @param {Array<{label: string, value: string|undefined}>} params.fields Top-level fields (recipient, callback URL, signer, deadline).\n * @param {string} params.message The raw NEP-413 message, shown when there are no intents to break out.\n * @param {string} [params.intents] JSON string with the intents array, when the message is a NEAR Intents body.\n * @returns {HTMLElement} The details node.\n */\nfunction renderNep413Details(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n let parsedIntents = null;\n if (params.intents) {\n try {\n const candidate = JSON.parse(params.intents);\n if (Array.isArray(candidate) && candidate.length > 0) parsedIntents = candidate;\n } catch (e) {\n parsedIntents = null;\n }\n }\n\n // No intents to break out (or they were unreadable) — show the message itself. Falling back\n // to the raw text keeps every NEP-413 message approvable, not just the ones we model.\n if (!parsedIntents) {\n box.appendChild(messageContent(params.message || \"\"));\n return box;\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n messageContent: messageContent,\n renderNep413Details: renderNep413Details,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in _form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", "css": ".avatar {\n width: 4.5rem;\n height: 4.5rem;\n border-radius: 12px;\n object-fit: cover;\n}\n\n.layout {\n position: relative;\n display: flex;\n flex-direction: row;\n gap: 1.5rem;\n align-items: center;\n justify-content: center;\n}\n\n.circle {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 999px;\n background-color: #f6f6f6;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.icon {\n width: 1.2rem;\n height: 1.2rem;\n}\n", "params": { "iconUrl": "https://peersyst-public-production.s3.eu-west-1.amazonaws.com/1b54f479-7990-4834-9b37-95b26e2023fb.png", @@ -38,7 +38,7 @@ "category": "BLOCK", "type": "RICH_TEXT", "config": { - "content": "

{{ fields.name }} wants to sign an Intent

" + "content": "

{{ fields.name }} wants you to sign a message

" } }, { @@ -49,12 +49,14 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the NEP-413 intent details (signer, verifier, deadline, intents).\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppIntentDetails(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderIntentDetails({\n fields: [\n { label: \"Signer ID\", value: params.signerId },\n { label: \"Verifier\", value: params.recipient },\n { label: \"Deadline\", value: params.deadline },\n ],\n intents: params.intents,\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Render the message body of a NEP-413 request verbatim.\n *\n * This is the default view, and the one that matters most: NEP-413 messages are arbitrary\n * strings meant to be read by a human (\"Sign in to example.com\"), so showing the text exactly\n * as it will be signed is the honest presentation. Pretty-printing is applied only when the\n * message is JSON, purely for legibility.\n */\nfunction messageContent(message) {\n const container = document.createElement(\"div\");\n let display = message;\n try {\n const parsed = JSON.parse(message);\n if (parsed && typeof parsed === \"object\") display = JSON.stringify(parsed, null, 2);\n } catch (e) {\n display = message;\n }\n container.appendChild(createTextContent(\"Message\", display));\n return container;\n}\n\n/**\n * Build the details DOM tree for a NEP-413 signature approval.\n *\n * Two presentations, one payload: when the message carries NEAR Intents the intents are broken\n * out one by one, and otherwise the raw message is shown as text. Either way the top-level\n * fields — recipient above all — are rendered, because under NEP-413 the recipient is what\n * tells the user which application their signature is addressed to.\n * @param {object} params The render parameters.\n * @param {Array<{label: string, value: string|undefined}>} params.fields Top-level fields (recipient, callback URL, signer, deadline).\n * @param {string} params.message The raw NEP-413 message, shown when there are no intents to break out.\n * @param {string} [params.intents] JSON string with the intents array, when the message is a NEAR Intents body.\n * @returns {HTMLElement} The details node.\n */\nfunction renderNep413Details(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n let parsedIntents = null;\n if (params.intents) {\n try {\n const candidate = JSON.parse(params.intents);\n if (Array.isArray(candidate) && candidate.length > 0) parsedIntents = candidate;\n } catch (e) {\n parsedIntents = null;\n }\n }\n\n // No intents to break out (or they were unreadable) — show the message itself. Falling back\n // to the raw text keeps every NEP-413 message approvable, not just the ones we model.\n if (!parsedIntents) {\n box.appendChild(messageContent(params.message || \"\"));\n return box;\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n messageContent: messageContent,\n renderNep413Details: renderNep413Details,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders a NEP-413 signature request (recipient, callback URL, message).\n *\n * When the message carries NEAR Intents the helper breaks them out one by one; otherwise the\n * message is shown verbatim, which is what the standard expects of an arbitrary signed string.\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppNep413Details(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderNep413Details({\n fields: [\n { label: \"Recipient\", value: params.recipient },\n { label: \"Callback URL\", value: params.callbackUrl },\n { label: \"Signer ID\", value: params.signerId },\n { label: \"Deadline\", value: params.deadline },\n ],\n message: params.message,\n intents: params.intents,\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", "css": ".box {\n padding: 1.25rem;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1.25rem;\n width: 100%;\n\n border-radius: 1rem;\n border: 1px solid #e5e5e5;\n}\n\n.text-content {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n}\n\n.label {\n font-size: 0.75rem;\n color: #999999;\n font-weight: 500;\n}\n\n.value {\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 600;\n}\n\n.json-content {\n display: flex;\n padding: 0.5rem;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n border-radius: 12px;\n background-color: #fafafa;\n\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 500;\n}\n\n.accordion {\n width: 100%;\n border: 1px solid #e5e5e5;\n border-radius: 6px;\n overflow: hidden;\n}\n\n.accordion-header {\n background: #fff;\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n}\n\n.accordion-header-content {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n}\n\n.accordion-left-content {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.accordion-header-label {\n font-size: 0.75rem;\n color: black;\n font-weight: 500;\n}\n\n.warning-icon {\n font-size: 1rem;\n color: #ff4444;\n}\n\n.expand-icon {\n font-size: 1.2rem;\n color: #666;\n font-weight: bold;\n line-height: 1;\n transition: transform 0.2s ease;\n user-select: none;\n min-width: 20px;\n text-align: center;\n}\n\n.accordion-content {\n max-height: 0;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #fafafa;\n color: #0a0a0a;\n font-weight: 400;\n transition:\n max-height 0.3s ease,\n padding 0.3s ease;\n padding: 0 1rem;\n}\n\n.accordion-content.open {\n padding: 12px 16px;\n max-height: 500px;\n}\n\n.actions-container {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n gap: 0.25rem;\n}\n\n.accordion-content .label {\n font-size: 0.75rem;\n}\n\n.accordion-content .value {\n font-size: 0.75rem;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.accordion-content > div {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.action-description {\n font-size: 0.75rem;\n color: #3f4246;\n margin: 0;\n}\n\n.warning-callout {\n background-color: #ffebee;\n padding: 16px;\n border-radius: 4px;\n margin: 0;\n color: #c62828;\n}\n", "params": { + "message": "{{ fields.message }}", "intents": "{{ fields.intents }}", - "signerId": "{{ fields.signerId }}", "recipient": "{{ fields.recipient }}", + "callbackUrl": "{{ fields.callbackUrl }}", + "signerId": "{{ fields.signerId }}", "deadline": "{{ fields.deadline }}" } } @@ -67,7 +69,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Render the message body of a NEP-413 request verbatim.\n *\n * This is the default view, and the one that matters most: NEP-413 messages are arbitrary\n * strings meant to be read by a human (\"Sign in to example.com\"), so showing the text exactly\n * as it will be signed is the honest presentation. Pretty-printing is applied only when the\n * message is JSON, purely for legibility.\n */\nfunction messageContent(message) {\n const container = document.createElement(\"div\");\n let display = message;\n try {\n const parsed = JSON.parse(message);\n if (parsed && typeof parsed === \"object\") display = JSON.stringify(parsed, null, 2);\n } catch (e) {\n display = message;\n }\n container.appendChild(createTextContent(\"Message\", display));\n return container;\n}\n\n/**\n * Build the details DOM tree for a NEP-413 signature approval.\n *\n * Two presentations, one payload: when the message carries NEAR Intents the intents are broken\n * out one by one, and otherwise the raw message is shown as text. Either way the top-level\n * fields — recipient above all — are rendered, because under NEP-413 the recipient is what\n * tells the user which application their signature is addressed to.\n * @param {object} params The render parameters.\n * @param {Array<{label: string, value: string|undefined}>} params.fields Top-level fields (recipient, callback URL, signer, deadline).\n * @param {string} params.message The raw NEP-413 message, shown when there are no intents to break out.\n * @param {string} [params.intents] JSON string with the intents array, when the message is a NEAR Intents body.\n * @returns {HTMLElement} The details node.\n */\nfunction renderNep413Details(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n let parsedIntents = null;\n if (params.intents) {\n try {\n const candidate = JSON.parse(params.intents);\n if (Array.isArray(candidate) && candidate.length > 0) parsedIntents = candidate;\n } catch (e) {\n parsedIntents = null;\n }\n }\n\n // No intents to break out (or they were unreadable) — show the message itself. Falling back\n // to the raw text keeps every NEP-413 message approvable, not just the ones we model.\n if (!parsedIntents) {\n box.appendChild(messageContent(params.message || \"\"));\n return box;\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n messageContent: messageContent,\n renderNep413Details: renderNep413Details,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", "css": ".decision-layout {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n width: 100%;\n}\n\n.decision-button {\n width: 100%;\n padding: 0.75rem 1rem;\n border-radius: 0.75rem;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n border: 1px solid transparent;\n}\n\n.decision-button.approve {\n background-color: #0a0a0a;\n color: #ffffff;\n}\n\n.decision-button.deny {\n background-color: #ffffff;\n color: #3f4246;\n border-color: #e5e5e5;\n}\n", "params": { "approveText": "Approve", @@ -83,14 +85,20 @@ "start": { "hidden_fields": [ { - "key": "intents" + "key": "message" }, { - "key": "signerId" + "key": "intents" }, { "key": "recipient" }, + { + "key": "callbackUrl" + }, + { + "key": "signerId" + }, { "key": "deadline" }, diff --git a/packages/auth0/src/forms/intent/intent_form_base.json b/packages/auth0/src/forms/nep413/nep413_form_base.json similarity index 81% rename from packages/auth0/src/forms/intent/intent_form_base.json rename to packages/auth0/src/forms/nep413/nep413_form_base.json index 4b005e4..473e911 100644 --- a/packages/auth0/src/forms/intent/intent_form_base.json +++ b/packages/auth0/src/forms/nep413/nep413_form_base.json @@ -1,7 +1,7 @@ { "version": "4.0.0", "form": { - "name": "Intent form", + "name": "NEP-413 form", "languages": { "primary": "en" }, @@ -39,7 +39,7 @@ "category": "BLOCK", "type": "RICH_TEXT", "config": { - "content": "

{{ fields.name }} wants to sign an Intent

" + "content": "

{{ fields.name }} wants you to sign a message

" } }, { @@ -54,9 +54,11 @@ "code": "", "css": "", "params": { + "message": "{{ fields.message }}", "intents": "{{ fields.intents }}", - "signerId": "{{ fields.signerId }}", "recipient": "{{ fields.recipient }}", + "callbackUrl": "{{ fields.callbackUrl }}", + "signerId": "{{ fields.signerId }}", "deadline": "{{ fields.deadline }}" } } @@ -85,13 +87,33 @@ ], "start": { "hidden_fields": [ - { "key": "intents" }, - { "key": "signerId" }, - { "key": "recipient" }, - { "key": "deadline" }, - { "key": "name" }, - { "key": "imageUrl" }, - { "key": "decision" } + { + "key": "message" + }, + { + "key": "intents" + }, + { + "key": "recipient" + }, + { + "key": "callbackUrl" + }, + { + "key": "signerId" + }, + { + "key": "deadline" + }, + { + "key": "name" + }, + { + "key": "imageUrl" + }, + { + "key": "decision" + } ], "next_node": "step_in7K", "coordinates": { diff --git a/packages/auth0/src/forms/shared/helpers/index.js b/packages/auth0/src/forms/shared/helpers/index.js index 76f9fb7..1e4cf54 100644 --- a/packages/auth0/src/forms/shared/helpers/index.js +++ b/packages/auth0/src/forms/shared/helpers/index.js @@ -568,16 +568,40 @@ function handleIntent(intent) { } /** - * Build the details DOM tree for a NEP-413 intent approval. + * Render the message body of a NEP-413 request verbatim. * - * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of - * NEAR transaction actions. + * This is the default view, and the one that matters most: NEP-413 messages are arbitrary + * strings meant to be read by a human ("Sign in to example.com"), so showing the text exactly + * as it will be signed is the honest presentation. Pretty-printing is applied only when the + * message is JSON, purely for legibility. + */ +function messageContent(message) { + const container = document.createElement("div"); + let display = message; + try { + const parsed = JSON.parse(message); + if (parsed && typeof parsed === "object") display = JSON.stringify(parsed, null, 2); + } catch (e) { + display = message; + } + container.appendChild(createTextContent("Message", display)); + return container; +} + +/** + * Build the details DOM tree for a NEP-413 signature approval. * - * @param {object} params - * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline). - * @param {string} params.intents - JSON string with the intents array. + * Two presentations, one payload: when the message carries NEAR Intents the intents are broken + * out one by one, and otherwise the raw message is shown as text. Either way the top-level + * fields — recipient above all — are rendered, because under NEP-413 the recipient is what + * tells the user which application their signature is addressed to. + * @param {object} params The render parameters. + * @param {Array<{label: string, value: string|undefined}>} params.fields Top-level fields (recipient, callback URL, signer, deadline). + * @param {string} params.message The raw NEP-413 message, shown when there are no intents to break out. + * @param {string} [params.intents] JSON string with the intents array, when the message is a NEAR Intents body. + * @returns {HTMLElement} The details node. */ -function renderIntentDetails(params) { +function renderNep413Details(params) { ensureBufferPolyfill(); const box = document.createElement("div"); box.classList.add("box"); @@ -587,6 +611,23 @@ function renderIntentDetails(params) { box.appendChild(createTextContent(field.label, field.value)); } + let parsedIntents = null; + if (params.intents) { + try { + const candidate = JSON.parse(params.intents); + if (Array.isArray(candidate) && candidate.length > 0) parsedIntents = candidate; + } catch (e) { + parsedIntents = null; + } + } + + // No intents to break out (or they were unreadable) — show the message itself. Falling back + // to the raw text keeps every NEP-413 message approvable, not just the ones we model. + if (!parsedIntents) { + box.appendChild(messageContent(params.message || "")); + return box; + } + const intentsContainer = document.createElement("div"); intentsContainer.classList.add("actions-container"); const intentsLabel = document.createElement("div"); @@ -594,18 +635,6 @@ function renderIntentDetails(params) { intentsLabel.textContent = "Intents"; intentsContainer.appendChild(intentsLabel); - let parsedIntents = []; - try { - parsedIntents = JSON.parse(params.intents || "[]"); - } catch (e) { - const errorNode = document.createElement("div"); - errorNode.classList.add("warning-callout"); - errorNode.textContent = "Failed to parse intents payload."; - intentsContainer.appendChild(errorNode); - box.appendChild(intentsContainer); - return box; - } - for (const intent of parsedIntents) { intentsContainer.appendChild(handleIntent(intent)); } @@ -642,7 +671,8 @@ var __auth0FormHelpers = { transferIntentContent: transferIntentContent, unknownIntentContent: unknownIntentContent, handleIntent: handleIntent, - renderIntentDetails: renderIntentDetails, + messageContent: messageContent, + renderNep413Details: renderNep413Details, }; if (typeof module !== "undefined" && module.exports) { diff --git a/packages/auth0/src/forms/transaction/transaction_form.json b/packages/auth0/src/forms/transaction/transaction_form.json index 794b0c4..79b0b1f 100644 --- a/packages/auth0/src/forms/transaction/transaction_form.json +++ b/packages/auth0/src/forms/transaction/transaction_form.json @@ -24,7 +24,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in _form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Render the message body of a NEP-413 request verbatim.\n *\n * This is the default view, and the one that matters most: NEP-413 messages are arbitrary\n * strings meant to be read by a human (\"Sign in to example.com\"), so showing the text exactly\n * as it will be signed is the honest presentation. Pretty-printing is applied only when the\n * message is JSON, purely for legibility.\n */\nfunction messageContent(message) {\n const container = document.createElement(\"div\");\n let display = message;\n try {\n const parsed = JSON.parse(message);\n if (parsed && typeof parsed === \"object\") display = JSON.stringify(parsed, null, 2);\n } catch (e) {\n display = message;\n }\n container.appendChild(createTextContent(\"Message\", display));\n return container;\n}\n\n/**\n * Build the details DOM tree for a NEP-413 signature approval.\n *\n * Two presentations, one payload: when the message carries NEAR Intents the intents are broken\n * out one by one, and otherwise the raw message is shown as text. Either way the top-level\n * fields — recipient above all — are rendered, because under NEP-413 the recipient is what\n * tells the user which application their signature is addressed to.\n * @param {object} params The render parameters.\n * @param {Array<{label: string, value: string|undefined}>} params.fields Top-level fields (recipient, callback URL, signer, deadline).\n * @param {string} params.message The raw NEP-413 message, shown when there are no intents to break out.\n * @param {string} [params.intents] JSON string with the intents array, when the message is a NEAR Intents body.\n * @returns {HTMLElement} The details node.\n */\nfunction renderNep413Details(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n let parsedIntents = null;\n if (params.intents) {\n try {\n const candidate = JSON.parse(params.intents);\n if (Array.isArray(candidate) && candidate.length > 0) parsedIntents = candidate;\n } catch (e) {\n parsedIntents = null;\n }\n }\n\n // No intents to break out (or they were unreadable) — show the message itself. Falling back\n // to the raw text keeps every NEP-413 message approvable, not just the ones we model.\n if (!parsedIntents) {\n box.appendChild(messageContent(params.message || \"\"));\n return box;\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n messageContent: messageContent,\n renderNep413Details: renderNep413Details,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders two app avatars (left = our wallet, right = requesting app)\n * separated by a small circle with a transfer/icon glyph in the middle.\n *\n * Params (configured per-form in _form_base.json):\n * - leftImageUrl: URL for the left avatar (our wallet/identity logo)\n * - rightImageUrl: URL for the right avatar (the requesting app's logo, e.g. {{ fields.imageUrl }})\n * - iconUrl: URL for the center circle icon\n *\n * If `rightImageUrl` is empty/missing (e.g. the client has no logo_uri configured), the\n * right avatar is hidden so we don't render a broken-image placeholder.\n */\nfunction AuthorizeAppImage(context) {\n return {\n /** Invoked once when the field is created */\n init() {\n const layout = document.createElement(\"div\");\n layout.classList.add(\"layout\");\n\n const { leftImageUrl, rightImageUrl, iconUrl } = context.custom.getParams();\n\n const leftAvatar = document.createElement(\"img\");\n leftAvatar.classList.add(\"avatar\");\n leftAvatar.setAttribute(\"alt\", \"\");\n leftAvatar.setAttribute(\"src\", leftImageUrl);\n\n layout.appendChild(leftAvatar);\n\n const trimmedRight = (rightImageUrl || \"\").trim();\n if (trimmedRight) {\n const circle = document.createElement(\"div\");\n circle.classList.add(\"circle\");\n\n const icon = document.createElement(\"img\");\n icon.classList.add(\"icon\");\n icon.setAttribute(\"alt\", \"\");\n icon.setAttribute(\"src\", iconUrl);\n circle.appendChild(icon);\n\n const rightAvatar = document.createElement(\"img\");\n rightAvatar.classList.add(\"avatar\");\n rightAvatar.setAttribute(\"alt\", \"\");\n rightAvatar.setAttribute(\"src\", trimmedRight);\n\n layout.appendChild(circle);\n layout.appendChild(rightAvatar);\n }\n\n return layout;\n },\n\n /** Returns a list of URLs that the SDK guarantees that will loaded before init() is invoked */\n getScripts() {\n return [];\n },\n\n /** Invoked when field has to be blocked */\n block() {},\n\n /** Invoked when field has to be unblocked */\n unblock() {},\n\n /** Invoked when the SDK needs to get the value (possibly several times) */\n getValue() {},\n };\n}\n\n);\n})()\n", "css": ".avatar {\n width: 4.5rem;\n height: 4.5rem;\n border-radius: 12px;\n object-fit: cover;\n}\n\n.layout {\n position: relative;\n display: flex;\n flex-direction: row;\n gap: 1.5rem;\n align-items: center;\n justify-content: center;\n}\n\n.circle {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 999px;\n background-color: #f6f6f6;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.icon {\n width: 1.2rem;\n height: 1.2rem;\n}\n", "params": { "iconUrl": "https://peersyst-public-production.s3.eu-west-1.amazonaws.com/1b54f479-7990-4834-9b37-95b26e2023fb.png", @@ -49,7 +49,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the transaction details (signer, receiver, actions).\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppTransactionDetails(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderDetails({\n fields: [\n { label: \"Signer ID\", value: params.signerId },\n { label: \"Receiver ID\", value: params.receiverId },\n ],\n actions: params.actions,\n options: { showYoctoConversion: true },\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Render the message body of a NEP-413 request verbatim.\n *\n * This is the default view, and the one that matters most: NEP-413 messages are arbitrary\n * strings meant to be read by a human (\"Sign in to example.com\"), so showing the text exactly\n * as it will be signed is the honest presentation. Pretty-printing is applied only when the\n * message is JSON, purely for legibility.\n */\nfunction messageContent(message) {\n const container = document.createElement(\"div\");\n let display = message;\n try {\n const parsed = JSON.parse(message);\n if (parsed && typeof parsed === \"object\") display = JSON.stringify(parsed, null, 2);\n } catch (e) {\n display = message;\n }\n container.appendChild(createTextContent(\"Message\", display));\n return container;\n}\n\n/**\n * Build the details DOM tree for a NEP-413 signature approval.\n *\n * Two presentations, one payload: when the message carries NEAR Intents the intents are broken\n * out one by one, and otherwise the raw message is shown as text. Either way the top-level\n * fields — recipient above all — are rendered, because under NEP-413 the recipient is what\n * tells the user which application their signature is addressed to.\n * @param {object} params The render parameters.\n * @param {Array<{label: string, value: string|undefined}>} params.fields Top-level fields (recipient, callback URL, signer, deadline).\n * @param {string} params.message The raw NEP-413 message, shown when there are no intents to break out.\n * @param {string} [params.intents] JSON string with the intents array, when the message is a NEAR Intents body.\n * @returns {HTMLElement} The details node.\n */\nfunction renderNep413Details(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n let parsedIntents = null;\n if (params.intents) {\n try {\n const candidate = JSON.parse(params.intents);\n if (Array.isArray(candidate) && candidate.length > 0) parsedIntents = candidate;\n } catch (e) {\n parsedIntents = null;\n }\n }\n\n // No intents to break out (or they were unreadable) — show the message itself. Falling back\n // to the raw text keeps every NEP-413 message approvable, not just the ones we model.\n if (!parsedIntents) {\n box.appendChild(messageContent(params.message || \"\"));\n return box;\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n messageContent: messageContent,\n renderNep413Details: renderNep413Details,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the transaction details (signer, receiver, actions).\n *\n * Depends on `__auth0FormHelpers`, which is inlined at build time by build.js\n * (or shimmed by the playground via helpers-shim.js).\n */\nfunction AuthorizeAppTransactionDetails(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n return __auth0FormHelpers.renderDetails({\n fields: [\n { label: \"Signer ID\", value: params.signerId },\n { label: \"Receiver ID\", value: params.receiverId },\n ],\n actions: params.actions,\n options: { showYoctoConversion: true },\n });\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", "css": ".box {\n padding: 1.25rem;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1.25rem;\n width: 100%;\n\n border-radius: 1rem;\n border: 1px solid #e5e5e5;\n}\n\n.text-content {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n}\n\n.label {\n font-size: 0.75rem;\n color: #999999;\n font-weight: 500;\n}\n\n.value {\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 600;\n}\n\n.json-content {\n display: flex;\n padding: 0.5rem;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n border-radius: 12px;\n background-color: #fafafa;\n\n color: #3f4246;\n font-size: 0.875rem;\n font-weight: 500;\n}\n\n.accordion {\n width: 100%;\n border: 1px solid #e5e5e5;\n border-radius: 6px;\n overflow: hidden;\n}\n\n.accordion-header {\n background: #fff;\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n}\n\n.accordion-header-content {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n}\n\n.accordion-left-content {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.accordion-header-label {\n font-size: 0.75rem;\n color: black;\n font-weight: 500;\n}\n\n.warning-icon {\n font-size: 1rem;\n color: #ff4444;\n}\n\n.expand-icon {\n font-size: 1.2rem;\n color: #666;\n font-weight: bold;\n line-height: 1;\n transition: transform 0.2s ease;\n user-select: none;\n min-width: 20px;\n text-align: center;\n}\n\n.accordion-content {\n max-height: 0;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #fafafa;\n color: #0a0a0a;\n font-weight: 400;\n transition:\n max-height 0.3s ease,\n padding 0.3s ease;\n padding: 0 1rem;\n}\n\n.accordion-content.open {\n padding: 12px 16px;\n max-height: 500px;\n}\n\n.actions-container {\n display: flex;\n flex-direction: column;\n align-items: left;\n justify-content: left;\n width: 100%;\n gap: 0.25rem;\n}\n\n.accordion-content .label {\n font-size: 0.75rem;\n}\n\n.accordion-content .value {\n font-size: 0.75rem;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.accordion-content > div {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.action-description {\n font-size: 0.75rem;\n color: #3f4246;\n margin: 0;\n}\n\n.warning-callout {\n background-color: #ffebee;\n padding: 16px;\n border-radius: 4px;\n margin: 0;\n color: #c62828;\n}\n", "params": { "actions": "{{ fields.actions }}", @@ -66,7 +66,7 @@ "sensitive": false, "config": { "schema": {}, - "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Build the details DOM tree for a NEP-413 intent approval.\n *\n * Mirrors `renderDetails`, but walks `intents` (the NEAR Intents message body) instead of\n * NEAR transaction actions.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields (signer, recipient, deadline).\n * @param {string} params.intents - JSON string with the intents array.\n */\nfunction renderIntentDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n let parsedIntents = [];\n try {\n parsedIntents = JSON.parse(params.intents || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse intents payload.\";\n intentsContainer.appendChild(errorNode);\n box.appendChild(intentsContainer);\n return box;\n }\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n renderIntentDetails: renderIntentDetails,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", + "code": "(function () {\n// --- BEGIN __auth0FormHelpers (auto-inlined from shared/helpers/index.js) ---\n/**\n * Shared helpers for Auth0 form custom components.\n *\n * Runs in two environments:\n * - Auth0 form runtime: build.js inlines this file into each component's `config.code`,\n * exposing `__auth0FormHelpers` as a top-level variable in the same scope as the component.\n * - Node (tests / playground via require): consumed as a CommonJS module via module.exports.\n *\n * Keep this file free of `require` and ES imports so it can be concatenated as plain text.\n */\n\nfunction ensureBufferPolyfill() {\n if (typeof Buffer !== \"undefined\") return;\n if (typeof globalThis === \"undefined\") return;\n globalThis.Buffer = {\n from: function (data) {\n if (Array.isArray(data)) return new Uint8Array(data);\n if (typeof data === \"string\") return new TextEncoder().encode(data);\n return data;\n },\n };\n}\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nfunction base58Encode(bytes) {\n let result = \"\";\n let num = BigInt(0);\n for (const byte of bytes) {\n num = (num << BigInt(8)) + BigInt(byte);\n }\n while (num > BigInt(0)) {\n const remainder = num % BigInt(58);\n num = num / BigInt(58);\n result = BASE58_ALPHABET[Number(remainder)] + result;\n }\n for (const byte of bytes) {\n if (byte === 0) result = BASE58_ALPHABET[0] + result;\n else break;\n }\n return result;\n}\n\n/**\n * Format a yocto-NEAR BigInt string as NEAR with up to 8 decimals.\n * \"1000000000000000000000000\" -> \"1\"\n * \"1500000000000000000000000\" -> \"1.5\"\n * \"1\" -> \"0.00000000\" trimmed -> \"0.0\"\n */\nfunction yoctoToNear(bigIntStr) {\n const trimmed = String(bigIntStr).replace(/^0+/, \"\") || \"0\";\n const length = trimmed.length;\n\n if (length <= 24) {\n const zerosNeeded = 24 - length;\n const withZeros = \"0\".repeat(zerosNeeded) + trimmed;\n const decimals = withZeros.slice(0, 8).replace(/0+$/, \"\") || \"0\";\n return `0.${decimals}`;\n }\n\n const splitPos = length - 24;\n const integerPart = trimmed.slice(0, splitPos);\n const decimalPart = trimmed.slice(splitPos, splitPos + 8);\n const trimmedDecimals = decimalPart.replace(/0+$/, \"\");\n if (trimmedDecimals === \"\") return integerPart;\n return `${integerPart}.${trimmedDecimals}`;\n}\n\nfunction formatPublicKey(publicKey) {\n if (publicKey && publicKey.secp256k1Key !== undefined) {\n return `secp256k1:${base58Encode(publicKey.secp256k1Key.data)}`;\n }\n if (publicKey && publicKey.ed25519Key !== undefined) {\n return `ed25519:${base58Encode(publicKey.ed25519Key.data)}`;\n }\n return \"\";\n}\n\n/**\n * Return the canonical action type discriminator (the single non-undefined key of the action).\n * Useful for dispatch and for tests that don't need DOM rendering.\n */\nfunction getActionType(action) {\n if (!action || typeof action !== \"object\") return null;\n for (const key of Object.keys(action)) {\n if (action[key] !== undefined) return key;\n }\n return null;\n}\n\n// --- DOM helpers (require document/window — jsdom in tests, real DOM in browser) ---\n\nfunction createTextContent(label, value, link = false) {\n const textContent = document.createElement(\"div\");\n textContent.classList.add(\"text-content\");\n\n const labelElement = document.createElement(\"div\");\n labelElement.classList.add(\"label\");\n labelElement.textContent = label;\n\n const valueElement = document.createElement(\"div\");\n valueElement.classList.add(\"value\");\n valueElement.textContent = value;\n\n if (link) {\n valueElement.classList.add(\"link\");\n valueElement.setAttribute(\"href\", link);\n valueElement.setAttribute(\"target\", \"_blank\");\n }\n\n textContent.appendChild(labelElement);\n textContent.appendChild(valueElement);\n return textContent;\n}\n\nfunction createDescription(text) {\n const node = document.createElement(\"p\");\n node.classList.add(\"action-description\");\n node.textContent = text;\n return node;\n}\n\nfunction createAccordion(label, content, showWarning = false) {\n const accordion = document.createElement(\"div\");\n accordion.classList.add(\"accordion\");\n\n const header = document.createElement(\"div\");\n header.classList.add(\"accordion-header\");\n\n const headerContent = document.createElement(\"div\");\n headerContent.classList.add(\"accordion-header-content\");\n\n const leftContent = document.createElement(\"div\");\n leftContent.classList.add(\"accordion-left-content\");\n\n if (showWarning) {\n const warningIcon = document.createElement(\"span\");\n warningIcon.classList.add(\"warning-icon\");\n warningIcon.innerHTML = \"⚠️\";\n leftContent.appendChild(warningIcon);\n }\n\n const labelElement = document.createElement(\"span\");\n labelElement.classList.add(\"accordion-header-label\");\n labelElement.textContent = label;\n leftContent.appendChild(labelElement);\n\n const expandIcon = document.createElement(\"span\");\n expandIcon.classList.add(\"expand-icon\");\n expandIcon.innerHTML = \"+\";\n\n headerContent.appendChild(leftContent);\n headerContent.appendChild(expandIcon);\n header.appendChild(headerContent);\n\n const contentElement = document.createElement(\"div\");\n contentElement.classList.add(\"accordion-content\");\n contentElement.appendChild(content);\n\n accordion.appendChild(header);\n accordion.appendChild(contentElement);\n\n header.addEventListener(\"click\", function () {\n contentElement.classList.toggle(\"open\");\n expandIcon.innerHTML = contentElement.classList.contains(\"open\") ? \"−\" : \"+\";\n });\n\n return accordion;\n}\n\n// --- Action content factories ---\n\nfunction createAccountContent() {\n return createDescription(\"By approving this request, a new account will be created.\");\n}\n\nfunction deployContractContent(action) {\n ensureBufferPolyfill();\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen = action.deployContract && action.deployContract.code ? `${action.deployContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\n/**\n * Decode functionCall `args` (a byte array / Uint8Array) to a readable string. NEAR contract\n * args are almost always UTF-8 JSON, so decode the bytes and pretty-print when they parse as\n * JSON; otherwise fall back to the raw decoded text. TextDecoder is used directly because it\n * exists in the browser and the Auth0 form runtime (unlike Buffer, which the runtime lacks).\n */\nfunction decodeFunctionCallArgs(args) {\n if (args == null) return \"\";\n let text;\n try {\n if (typeof TextDecoder !== \"undefined\") {\n text = new TextDecoder().decode(Uint8Array.from(args));\n } else if (typeof Buffer !== \"undefined\" && typeof Buffer.from === \"function\") {\n text = Buffer.from(args).toString(\"utf8\");\n } else {\n text = String.fromCharCode.apply(null, Array.from(args));\n }\n } catch (e) {\n return \"\";\n }\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch (e) {\n return text;\n }\n}\n\n/**\n * Format a yocto-NEAR amount for display. In transaction mode (showYoctoConversion) amounts at or\n * above 0.00000000001 NEAR are shown as NEAR; smaller amounts and delegate-action mode stay in\n * yoctoNEAR so no precision is hidden.\n */\nfunction formatNearAmount(value, options) {\n const showYoctoConversion = !!(options && options.showYoctoConversion);\n if (showYoctoConversion && value != null && BigInt(value) >= BigInt(\"10000000000000000\")) {\n return `${yoctoToNear(value.toString())} NEAR`;\n }\n return `${value != null ? value.toString() : \"0\"} yoctoNEAR`;\n}\n\nfunction functionCallContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following function will be called.\");\n\n const fc = action.functionCall || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Method Name\", fc.methodName));\n container.appendChild(createTextContent(\"Args\", decodeFunctionCallArgs(fc.args)));\n container.appendChild(createTextContent(\"Gas\", fc.gas != null ? fc.gas.toString() : \"\"));\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(fc.deposit, options)));\n return container;\n}\n\nfunction transferContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be transferred to the receiver.\");\n\n const deposit = action.transfer ? action.transfer.deposit : undefined;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Deposit\", formatNearAmount(deposit, options)));\n return container;\n}\n\nfunction stakeContent(action, options) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following amount will be staked to the public key.\");\n const stake = action.stake || {};\n container.appendChild(text);\n container.appendChild(createTextContent(\"Stake\", formatNearAmount(stake.stake, options)));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(stake.publicKey)));\n return container;\n}\n\nfunction addKeyContent(action) {\n const container = document.createElement(\"div\");\n const ak = action.addKey || {};\n const accessKey = ak.accessKey || {};\n const permission = accessKey.permission || {};\n\n if (permission.fullAccess !== undefined) {\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"This key will have full access to your account. Only approve if you trust the recipient.\";\n container.appendChild(warning);\n }\n\n container.appendChild(createDescription(\"By approving this request, the following access key will be added to your account.\"));\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(ak.publicKey)));\n container.appendChild(createTextContent(\"Nonce\", accessKey.nonce != null ? accessKey.nonce.toString() : \"\"));\n\n if (permission.fullAccess === undefined) {\n container.appendChild(createTextContent(\"Permission\", JSON.stringify(permission.functionCall, null, 2)));\n }\n return container;\n}\n\nfunction deleteKeyContent(action) {\n const container = document.createElement(\"div\");\n const dk = action.deleteKey || {};\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the key cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(\n createDescription(\"This action will delete an access key from your account. Only approve if you trust the recipient.\"),\n );\n container.appendChild(createTextContent(\"Public Key\", formatPublicKey(dk.publicKey)));\n return container;\n}\n\nfunction deleteAccountContent(action) {\n const container = document.createElement(\"div\");\n\n const warning = document.createElement(\"div\");\n warning.classList.add(\"warning-callout\");\n warning.textContent = \"Once deleted, the account cannot be recovered.\";\n\n container.appendChild(warning);\n container.appendChild(createDescription(\"By approving this request, the account will be deleted.\"));\n container.appendChild(createTextContent(\"Beneficiary ID\", action.deleteAccount && action.deleteAccount.beneficiaryId));\n return container;\n}\n\nfunction signedDelegateContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following delegate action will be executed.\");\n\n const sd = action.signedDelegate || {};\n let serialized;\n try {\n serialized = JSON.stringify(\n sd.delegateAction,\n function (_, value) {\n return typeof value === \"bigint\" ? value.toString() : value;\n },\n 2,\n );\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Delegate Action\", serialized));\n return container;\n}\n\nfunction deployGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the compiled smart contract will be deployed.\");\n const codeLen =\n action.deployGlobalContract && action.deployGlobalContract.code ? `${action.deployGlobalContract.code.length} bytes` : null;\n container.appendChild(text);\n container.appendChild(createTextContent(\"Code\", codeLen));\n return container;\n}\n\nfunction useGlobalContractContent(action) {\n const container = document.createElement(\"div\");\n const text = createDescription(\"By approving this request, the following global contract will be used.\");\n let identifier;\n try {\n identifier = JSON.stringify(action.useGlobalContract && action.useGlobalContract.contractIdentifier);\n } catch (e) {\n identifier = \"\";\n }\n container.appendChild(text);\n container.appendChild(createTextContent(\"Contract Identifier\", identifier));\n return container;\n}\n\nconst ACTION_DISPATCH = {\n createAccount: {\n label: \"CreateAccount\",\n warn: false,\n render: function () {\n return createAccountContent();\n },\n },\n deployContract: {\n label: \"DeployContract\",\n warn: true,\n render: function (a) {\n return deployContractContent(a);\n },\n },\n functionCall: {\n label: \"FunctionCall\",\n warn: false,\n render: function (a, opts) {\n return functionCallContent(a, opts);\n },\n },\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (a, opts) {\n return transferContent(a, opts);\n },\n },\n stake: {\n label: \"Stake\",\n warn: false,\n render: function (a, opts) {\n return stakeContent(a, opts);\n },\n },\n addKey: {\n label: \"AddKey\",\n warn: false,\n render: function (a) {\n return addKeyContent(a);\n },\n warnsWhen: function (a) {\n return !!(\n a.addKey &&\n a.addKey.accessKey &&\n a.addKey.accessKey.permission &&\n a.addKey.accessKey.permission.fullAccess !== undefined\n );\n },\n },\n deleteKey: {\n label: \"DeleteKey\",\n warn: true,\n render: function (a) {\n return deleteKeyContent(a);\n },\n },\n deleteAccount: {\n label: \"DeleteAccount\",\n warn: true,\n render: function (a) {\n return deleteAccountContent(a);\n },\n },\n signedDelegate: {\n label: \"SignedDelegate\",\n warn: false,\n render: function (a) {\n return signedDelegateContent(a);\n },\n },\n deployGlobalContract: {\n label: \"DeployGlobalContract\",\n warn: true,\n render: function (a) {\n return deployGlobalContractContent(a);\n },\n },\n useGlobalContract: {\n label: \"UseGlobalContract\",\n warn: false,\n render: function (a) {\n return useGlobalContractContent(a);\n },\n },\n};\n\nfunction handleNearAction(action, options) {\n const actionKey = getActionType(action);\n const entry = actionKey ? ACTION_DISPATCH[actionKey] : null;\n if (!entry) {\n const unknown = document.createElement(\"div\");\n unknown.textContent = `Unknown action: ${actionKey || \"(empty)\"}`;\n return createAccordion(\"Unknown\", unknown, true);\n }\n const showWarning = entry.warnsWhen ? entry.warnsWhen(action) : !!entry.warn;\n return createAccordion(entry.label, entry.render(action, options), showWarning);\n}\n\n/**\n * Build the full details DOM tree for a form custom field.\n *\n * @param {object} params\n * @param {Array<{label: string, value: string|undefined}>} params.fields - top-level fields like Signer/Receiver/MaxBlockHeight.\n * @param {string} params.actions - JSON string with the actions array (as Auth0 form fields deliver them).\n * @param {{ showYoctoConversion?: boolean }} [params.options]\n */\nfunction renderDetails(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n const actionsContainer = document.createElement(\"div\");\n actionsContainer.classList.add(\"actions-container\");\n const actionsLabel = document.createElement(\"div\");\n actionsLabel.classList.add(\"label\");\n actionsLabel.textContent = \"Actions\";\n actionsContainer.appendChild(actionsLabel);\n\n let parsedActions = [];\n try {\n parsedActions = JSON.parse(params.actions || \"[]\");\n } catch (e) {\n const errorNode = document.createElement(\"div\");\n errorNode.classList.add(\"warning-callout\");\n errorNode.textContent = \"Failed to parse actions payload.\";\n actionsContainer.appendChild(errorNode);\n box.appendChild(actionsContainer);\n return box;\n }\n\n for (const action of parsedActions) {\n actionsContainer.appendChild(handleNearAction(action, params.options));\n }\n\n box.appendChild(actionsContainer);\n return box;\n}\n\n// --- NEP-413 intent content factories ---\n\n/**\n * Render the token map of a transfer intent (`{ \"\": \"\" }`).\n *\n * Amounts stay in the token's smallest unit: the form has no token metadata, so converting\n * would mean guessing decimals — and a wrong guess here understates what the user is\n * approving. Showing the raw amount alongside its token id is honest and unambiguous.\n */\nfunction intentTokensContent(tokens) {\n const container = document.createElement(\"div\");\n if (!tokens || typeof tokens !== \"object\") return container;\n for (const tokenId of Object.keys(tokens)) {\n container.appendChild(createTextContent(tokenId, String(tokens[tokenId])));\n }\n return container;\n}\n\nfunction transferIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"By approving this request, the following tokens will be transferred.\"));\n container.appendChild(createTextContent(\"Receiver ID\", intent.receiver_id));\n\n const tokensLabel = document.createElement(\"div\");\n tokensLabel.classList.add(\"label\");\n tokensLabel.textContent = \"Tokens\";\n container.appendChild(tokensLabel);\n container.appendChild(intentTokensContent(intent.tokens));\n return container;\n}\n\n/**\n * Fallback for intent kinds this form does not model explicitly. It is deliberately shown\n * with a warning: the user is approving something the UI cannot describe in plain terms.\n */\nfunction unknownIntentContent(intent) {\n const container = document.createElement(\"div\");\n container.appendChild(createDescription(\"This request contains an intent type this app does not recognize. Review it carefully.\"));\n let serialized;\n try {\n serialized = JSON.stringify(intent, null, 2);\n } catch (e) {\n serialized = \"\";\n }\n container.appendChild(createTextContent(\"Intent\", serialized));\n return container;\n}\n\nconst INTENT_DISPATCH = {\n transfer: {\n label: \"Transfer\",\n warn: false,\n render: function (i) {\n return transferIntentContent(i);\n },\n },\n};\n\nfunction handleIntent(intent) {\n const kind = intent && typeof intent === \"object\" ? intent.intent : null;\n const entry = kind ? INTENT_DISPATCH[kind] : null;\n if (!entry) {\n return createAccordion(kind ? `Unknown: ${kind}` : \"Unknown\", unknownIntentContent(intent), true);\n }\n return createAccordion(entry.label, entry.render(intent), !!entry.warn);\n}\n\n/**\n * Render the message body of a NEP-413 request verbatim.\n *\n * This is the default view, and the one that matters most: NEP-413 messages are arbitrary\n * strings meant to be read by a human (\"Sign in to example.com\"), so showing the text exactly\n * as it will be signed is the honest presentation. Pretty-printing is applied only when the\n * message is JSON, purely for legibility.\n */\nfunction messageContent(message) {\n const container = document.createElement(\"div\");\n let display = message;\n try {\n const parsed = JSON.parse(message);\n if (parsed && typeof parsed === \"object\") display = JSON.stringify(parsed, null, 2);\n } catch (e) {\n display = message;\n }\n container.appendChild(createTextContent(\"Message\", display));\n return container;\n}\n\n/**\n * Build the details DOM tree for a NEP-413 signature approval.\n *\n * Two presentations, one payload: when the message carries NEAR Intents the intents are broken\n * out one by one, and otherwise the raw message is shown as text. Either way the top-level\n * fields — recipient above all — are rendered, because under NEP-413 the recipient is what\n * tells the user which application their signature is addressed to.\n * @param {object} params The render parameters.\n * @param {Array<{label: string, value: string|undefined}>} params.fields Top-level fields (recipient, callback URL, signer, deadline).\n * @param {string} params.message The raw NEP-413 message, shown when there are no intents to break out.\n * @param {string} [params.intents] JSON string with the intents array, when the message is a NEAR Intents body.\n * @returns {HTMLElement} The details node.\n */\nfunction renderNep413Details(params) {\n ensureBufferPolyfill();\n const box = document.createElement(\"div\");\n box.classList.add(\"box\");\n\n for (const field of params.fields || []) {\n if (field.value === undefined || field.value === null || field.value === \"\") continue;\n box.appendChild(createTextContent(field.label, field.value));\n }\n\n let parsedIntents = null;\n if (params.intents) {\n try {\n const candidate = JSON.parse(params.intents);\n if (Array.isArray(candidate) && candidate.length > 0) parsedIntents = candidate;\n } catch (e) {\n parsedIntents = null;\n }\n }\n\n // No intents to break out (or they were unreadable) — show the message itself. Falling back\n // to the raw text keeps every NEP-413 message approvable, not just the ones we model.\n if (!parsedIntents) {\n box.appendChild(messageContent(params.message || \"\"));\n return box;\n }\n\n const intentsContainer = document.createElement(\"div\");\n intentsContainer.classList.add(\"actions-container\");\n const intentsLabel = document.createElement(\"div\");\n intentsLabel.classList.add(\"label\");\n intentsLabel.textContent = \"Intents\";\n intentsContainer.appendChild(intentsLabel);\n\n for (const intent of parsedIntents) {\n intentsContainer.appendChild(handleIntent(intent));\n }\n\n box.appendChild(intentsContainer);\n return box;\n}\n\nvar __auth0FormHelpers = {\n ensureBufferPolyfill: ensureBufferPolyfill,\n base58Encode: base58Encode,\n yoctoToNear: yoctoToNear,\n formatNearAmount: formatNearAmount,\n decodeFunctionCallArgs: decodeFunctionCallArgs,\n formatPublicKey: formatPublicKey,\n getActionType: getActionType,\n createTextContent: createTextContent,\n createDescription: createDescription,\n createAccordion: createAccordion,\n createAccountContent: createAccountContent,\n deployContractContent: deployContractContent,\n functionCallContent: functionCallContent,\n transferContent: transferContent,\n stakeContent: stakeContent,\n addKeyContent: addKeyContent,\n deleteKeyContent: deleteKeyContent,\n deleteAccountContent: deleteAccountContent,\n signedDelegateContent: signedDelegateContent,\n deployGlobalContractContent: deployGlobalContractContent,\n useGlobalContractContent: useGlobalContractContent,\n handleNearAction: handleNearAction,\n renderDetails: renderDetails,\n intentTokensContent: intentTokensContent,\n transferIntentContent: transferIntentContent,\n unknownIntentContent: unknownIntentContent,\n handleIntent: handleIntent,\n messageContent: messageContent,\n renderNep413Details: renderNep413Details,\n};\n\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = __auth0FormHelpers;\n}\n\n// --- END __auth0FormHelpers ---\n\n\nreturn (\n/**\n * Custom field that renders the Approve / Deny buttons for the authorize flow.\n *\n * Auth0 Forms buttons can only navigate — they cannot record a choice. So instead of the\n * native NEXT_BUTTON / PREVIOUS_BUTTON, this field owns both buttons and writes the user's\n * choice into the `decision` hidden field before advancing. The resuming action reads\n * `event.prompt.fields.decision` in onContinuePostLogin and denies access when it is \"denied\".\n *\n * Params (configured per-form in _form_base.json):\n * - approveText: label for the approve button (default \"Approve\")\n * - denyText: label for the deny button (default \"Deny\")\n */\nfunction AuthorizeAppDecision(context) {\n return {\n init: function () {\n const params = context.custom.getParams();\n\n const layout = document.createElement(\"div\");\n layout.classList.add(\"decision-layout\");\n\n const approve = document.createElement(\"button\");\n approve.setAttribute(\"type\", \"button\");\n approve.classList.add(\"decision-button\", \"approve\");\n approve.textContent = params.approveText || \"Approve\";\n approve.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"approved\");\n context.form.goForward();\n });\n\n const deny = document.createElement(\"button\");\n deny.setAttribute(\"type\", \"button\");\n deny.classList.add(\"decision-button\", \"deny\");\n deny.textContent = params.denyText || \"Deny\";\n deny.addEventListener(\"click\", function () {\n context.form.setHiddenField(\"decision\", \"denied\");\n context.form.goForward();\n });\n\n layout.appendChild(approve);\n layout.appendChild(deny);\n return layout;\n },\n getScripts: function () { return []; },\n block: function () {},\n unblock: function () {},\n getValue: function () {},\n };\n}\n\n);\n})()\n", "css": ".decision-layout {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n width: 100%;\n}\n\n.decision-button {\n width: 100%;\n padding: 0.75rem 1rem;\n border-radius: 0.75rem;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n border: 1px solid transparent;\n}\n\n.decision-button.approve {\n background-color: #0a0a0a;\n color: #ffffff;\n}\n\n.decision-button.deny {\n background-color: #ffffff;\n color: #3f4246;\n border-color: #e5e5e5;\n}\n", "params": { "approveText": "Approve", diff --git a/packages/auth0/test/fixtures/builders.js b/packages/auth0/test/fixtures/builders.js index b2856c1..18ad014 100644 --- a/packages/auth0/test/fixtures/builders.js +++ b/packages/auth0/test/fixtures/builders.js @@ -286,7 +286,7 @@ const DELEGATE_ACTION_TYPES = ALL_ACTION_TYPES.filter( // Domain-separation tag from NEP-413 (2^31 + 413). const NEP413_PREFIX_TAG = Math.pow(2, 31) + 413; -const DEFAULT_INTENTS_RECIPIENT = "intents.near"; +const INTENTS_RECIPIENT = "intents.near"; /** * Borsh schema for the NEP-413 payload, transcribed from the NEP rather than imported from @@ -324,10 +324,10 @@ function buildIntentMessage({ * * @returns {{csv: string, bytes: Uint8Array, payload: object, message: object}} */ -function buildIntentPayload({ +function buildNep413Payload({ message, tag = NEP413_PREFIX_TAG, - recipient = DEFAULT_INTENTS_RECIPIENT, + recipient = INTENTS_RECIPIENT, nonce = SAMPLE_NONCE, callbackUrl = null, rawMessage, @@ -355,9 +355,9 @@ module.exports = { ALL_ACTION_TYPES, DELEGATE_ACTION_TYPES, NEP413_PREFIX_TAG, - DEFAULT_INTENTS_RECIPIENT, + INTENTS_RECIPIENT, NEP413_SCHEMA, SAMPLE_NONCE, buildIntentMessage, - buildIntentPayload, + buildNep413Payload, }; diff --git a/packages/auth0/test/intent-decoding.spec.js b/packages/auth0/test/intent-decoding.spec.js deleted file mode 100644 index 8b4b46c..0000000 --- a/packages/auth0/test/intent-decoding.spec.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * @jest-environment node - * - * Tests for `decodeIntent` — the validation gate for NEP-413 payloads. - * - * The guard contract never inspects the bytes it signs; it only checks that they equal the - * `fatxn` claim. So every guarantee about *what* the user approved is established here, and - * each rejection below maps to a concrete way the consent guarantee could be bypassed: - * - * - wrong/absent domain tag → bytes that could double as a NEAR transaction - * - unexpected recipient → an intent redirected to a different verifier - * - unparseable message → a payload the approval screen cannot describe - * - empty intents → an approval that shows the user nothing - */ -const { decodeIntent, NEP413_PREFIX_TAG, DEFAULT_INTENTS_RECIPIENT } = require("../src/actions/authorize-app.action.js"); -const { buildIntentPayload, buildIntentMessage, buildTransaction, toCsv } = require("./fixtures/builders.js"); - -describe("decodeIntent — happy path", () => { - test("decodes a well-formed transfer intent", () => { - const { csv, message } = buildIntentPayload(); - const { payload, message: decoded } = decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT); - - expect(payload.tag).toBe(NEP413_PREFIX_TAG); - expect(payload.recipient).toBe(DEFAULT_INTENTS_RECIPIENT); - expect(decoded).toEqual(message); - expect(decoded.intents).toHaveLength(1); - expect(decoded.intents[0].intent).toBe("transfer"); - }); - - test("round-trips the exact byte string it was given", () => { - const { csv, bytes } = buildIntentPayload(); - // The action puts `query.intent` straight into `fatxn`, so the bytes the user approves - // must be byte-identical to what was decoded for display. - expect(csv.split(",").map(Number)).toEqual(Array.from(bytes)); - expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).not.toThrow(); - }); - - test("accepts multiple intents in a single payload", () => { - const message = buildIntentMessage({ - intents: [ - { intent: "transfer", receiver_id: "a.near", tokens: { "nep141:usdc.near": "1" } }, - { intent: "transfer", receiver_id: "b.near", tokens: { "nep141:usdt.near": "2" } }, - ], - }); - const { csv } = buildIntentPayload({ message }); - const { message: decoded } = decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT); - expect(decoded.intents).toHaveLength(2); - }); - - test("accepts a custom verifier when that is what the caller expects", () => { - const { csv } = buildIntentPayload({ recipient: "intents.testnet" }); - expect(() => decodeIntent(csv, "intents.testnet")).not.toThrow(); - }); -}); - -describe("decodeIntent — domain separation", () => { - test("rejects a payload whose tag is not the NEP-413 prefix", () => { - const { csv } = buildIntentPayload({ tag: 1 }); - expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/missing the NEP-413 domain tag/); - }); - - test("rejects the delegate-action prefix reused as a tag", () => { - const { csv } = buildIntentPayload({ tag: Math.pow(2, 30) + 366 }); - expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/missing the NEP-413 domain tag/); - }); - - test("rejects transaction bytes submitted as an intent", () => { - // A real NEAR transaction must never decode into an approvable intent, whether it - // fails at borsh or at the tag check. - const { csv } = buildTransaction(); - expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(); - }); -}); - -describe("decodeIntent — recipient anchoring", () => { - test("rejects an intent aimed at a different verifier", () => { - const { csv } = buildIntentPayload({ recipient: "evil.near" }); - expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/unexpected recipient: evil\.near/); - }); - - test("rejects an empty recipient", () => { - const { csv } = buildIntentPayload({ recipient: "" }); - expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/unexpected recipient/); - }); -}); - -describe("decodeIntent — message validation", () => { - test("rejects a message that is not JSON", () => { - const { csv } = buildIntentPayload({ rawMessage: "not json at all" }); - expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/not valid JSON/); - }); - - test("rejects a message with no intents array", () => { - const { csv } = buildIntentPayload({ rawMessage: JSON.stringify({ signer_id: "trader.near" }) }); - expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/no intents to approve/); - }); - - test("rejects an empty intents array", () => { - const { csv } = buildIntentPayload({ message: buildIntentMessage({ intents: [] }) }); - expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/no intents to approve/); - }); - - test("rejects intents that is a JSON value but not an array", () => { - const { csv } = buildIntentPayload({ rawMessage: JSON.stringify({ intents: { intent: "transfer" } }) }); - expect(() => decodeIntent(csv, DEFAULT_INTENTS_RECIPIENT)).toThrow(/no intents to approve/); - }); -}); - -describe("decodeIntent — malformed input", () => { - test("rejects bytes that are not a NEP-413 payload", () => { - expect(() => decodeIntent(toCsv(Uint8Array.from([1, 2, 3, 4])), DEFAULT_INTENTS_RECIPIENT)).toThrow( - /not a valid NEP-413 message/, - ); - }); - - test("rejects an empty payload", () => { - expect(() => decodeIntent("", DEFAULT_INTENTS_RECIPIENT)).toThrow(/not a valid NEP-413 message/); - }); - - test("rejects a truncated payload", () => { - const { bytes } = buildIntentPayload(); - const truncated = bytes.slice(0, Math.floor(bytes.length / 2)); - expect(() => decodeIntent(toCsv(truncated), DEFAULT_INTENTS_RECIPIENT)).toThrow(/not a valid NEP-413 message/); - }); -}); diff --git a/packages/auth0/test/nep413-decoding.spec.js b/packages/auth0/test/nep413-decoding.spec.js new file mode 100644 index 0000000..2492edc --- /dev/null +++ b/packages/auth0/test/nep413-decoding.spec.js @@ -0,0 +1,171 @@ +/** + * @jest-environment node + * + * Tests for `decodeNep413Payload` — the validation gate for NEP-413 payloads. + * + * The guard contract never inspects the bytes it signs; it only checks that they equal the + * `fatxn` claim. So every guarantee about *what* the user approved is established here. + * + * Two checks are load-bearing, and only two: + * + * - the domain tag, without which the bytes could double as a NEAR transaction; + * - a non-empty message, without which the approval screen has nothing to show. + * + * The recipient is deliberately *not* restricted by default. Under NEP-413 it names the + * application a message is addressed to, and the protection is that the user sees it — the same + * way NEAR wallets behave. Tenants that want to serve exactly one application can opt into an + * allowlist, which is what the allowlist tests below cover. + */ +const { decodeNep413Payload, parseRecipientAllowlist, extractIntents, NEP413_PREFIX_TAG } = require("../src/actions/authorize-app.action.js"); +const { buildNep413Payload, buildIntentMessage, buildTransaction, toCsv, INTENTS_RECIPIENT } = require("./fixtures/builders.js"); + +describe("decodeNep413Payload — any valid message", () => { + test("decodes a plain-text sign-in challenge", () => { + const { csv } = buildNep413Payload({ rawMessage: "Sign in to example.com", recipient: "example.com" }); + const { payload, message } = decodeNep413Payload(csv, null); + + expect(payload.tag).toBe(NEP413_PREFIX_TAG); + expect(payload.message).toBe("Sign in to example.com"); + expect(payload.recipient).toBe("example.com"); + // Not JSON, so there is no structured message — and that is fine. + expect(message).toBeNull(); + }); + + test("decodes a NEAR Intents message and exposes its body", () => { + const { csv, message: original } = buildNep413Payload(); + const { payload, message } = decodeNep413Payload(csv, null); + + expect(payload.recipient).toBe(INTENTS_RECIPIENT); + expect(message).toEqual(original); + expect(extractIntents(message)).toHaveLength(1); + }); + + test("accepts any recipient when no allowlist is configured", () => { + for (const recipient of ["intents.near", "app.example.com", "alice.near", "some-dapp"]) { + const { csv } = buildNep413Payload({ rawMessage: "hello", recipient }); + expect(decodeNep413Payload(csv, null).payload.recipient).toBe(recipient); + } + }); + + test("accepts JSON that is not an intents body", () => { + const { csv } = buildNep413Payload({ rawMessage: JSON.stringify({ purpose: "login", session: "abc" }) }); + const { message } = decodeNep413Payload(csv, null); + + expect(message).toEqual({ purpose: "login", session: "abc" }); + expect(extractIntents(message)).toBeNull(); + }); + + test("carries the callback url through when present", () => { + const { csv } = buildNep413Payload({ rawMessage: "hello", callbackUrl: "https://example.com/cb" }); + expect(decodeNep413Payload(csv, null).payload.callbackUrl).toBe("https://example.com/cb"); + }); + + test("round-trips the exact byte string it was given", () => { + const { csv, bytes } = buildNep413Payload(); + // The action puts the query value straight into `fatxn`, so what gets approved must be + // byte-identical to what was decoded for display. + expect(csv.split(",").map(Number)).toEqual(Array.from(bytes)); + }); + + test("accepts multiple intents in a single message", () => { + const message = buildIntentMessage({ + intents: [ + { intent: "transfer", receiver_id: "a.near", tokens: { "nep141:usdc.near": "1" } }, + { intent: "transfer", receiver_id: "b.near", tokens: { "nep141:usdt.near": "2" } }, + ], + }); + const { csv } = buildNep413Payload({ message }); + expect(extractIntents(decodeNep413Payload(csv, null).message)).toHaveLength(2); + }); +}); + +describe("decodeNep413Payload — domain separation", () => { + test("rejects a payload whose tag is not the NEP-413 prefix", () => { + const { csv } = buildNep413Payload({ tag: 1 }); + expect(() => decodeNep413Payload(csv, null)).toThrow(/missing the NEP-413 domain tag/); + }); + + test("rejects the delegate-action prefix reused as a tag", () => { + const { csv } = buildNep413Payload({ tag: Math.pow(2, 30) + 366 }); + expect(() => decodeNep413Payload(csv, null)).toThrow(/missing the NEP-413 domain tag/); + }); + + test("rejects transaction bytes submitted as a message", () => { + // A real NEAR transaction must never decode into an approvable message, whether it + // fails at borsh or at the tag check. + const { csv } = buildTransaction(); + expect(() => decodeNep413Payload(csv, null)).toThrow(); + }); +}); + +describe("decodeNep413Payload — message must be showable", () => { + test("rejects an empty message", () => { + const { csv } = buildNep413Payload({ rawMessage: "" }); + expect(() => decodeNep413Payload(csv, null)).toThrow(/message is empty/); + }); + + test("accepts a message with no intents array", () => { + // Previously rejected. A message need not be a NEAR Intents body to be worth signing. + const { csv } = buildNep413Payload({ rawMessage: JSON.stringify({ signer_id: "trader.near" }) }); + expect(() => decodeNep413Payload(csv, null)).not.toThrow(); + }); + + test("accepts a message whose intents array is empty", () => { + const { csv } = buildNep413Payload({ message: buildIntentMessage({ intents: [] }) }); + const { message } = decodeNep413Payload(csv, null); + expect(extractIntents(message)).toBeNull(); + }); +}); + +describe("decodeNep413Payload — recipient allowlist", () => { + test("accepts a recipient on the allowlist", () => { + const { csv } = buildNep413Payload({ rawMessage: "hello", recipient: "intents.near" }); + expect(() => decodeNep413Payload(csv, ["intents.near"])).not.toThrow(); + }); + + test("rejects a recipient off the allowlist", () => { + const { csv } = buildNep413Payload({ rawMessage: "hello", recipient: "evil.near" }); + expect(() => decodeNep413Payload(csv, ["intents.near"])).toThrow(/unexpected recipient: evil\.near/); + }); + + test("accepts any entry of a multi-value allowlist", () => { + for (const recipient of ["intents.near", "intents.testnet"]) { + const { csv } = buildNep413Payload({ rawMessage: "hello", recipient }); + expect(() => decodeNep413Payload(csv, ["intents.near", "intents.testnet"])).not.toThrow(); + } + }); +}); + +describe("parseRecipientAllowlist", () => { + test("returns null when the secret is unset, meaning any recipient", () => { + expect(parseRecipientAllowlist(undefined)).toBeNull(); + expect(parseRecipientAllowlist("")).toBeNull(); + }); + + test("splits a comma-separated list and trims entries", () => { + expect(parseRecipientAllowlist(" intents.near , intents.testnet ")).toEqual(["intents.near", "intents.testnet"]); + }); + + test("treats a list of only separators as unset rather than as an empty allowlist", () => { + // An empty allowlist would reject everything — almost certainly a misconfiguration + // rather than an intent to disable signing entirely. + expect(parseRecipientAllowlist(" , , ")).toBeNull(); + }); +}); + +describe("decodeNep413Payload — malformed input", () => { + test("rejects bytes that are not a NEP-413 payload", () => { + expect(() => decodeNep413Payload(toCsv(Uint8Array.from([1, 2, 3, 4])), null)).toThrow(/not a valid NEP-413 message/); + }); + + test("rejects an empty payload", () => { + expect(() => decodeNep413Payload("", null)).toThrow(/not a valid NEP-413 message/); + }); + + test("rejects a truncated payload", () => { + const { bytes } = buildNep413Payload(); + expect(() => decodeNep413Payload(toCsv(bytes.slice(0, Math.floor(bytes.length / 2))), null)).toThrow( + /not a valid NEP-413 message/, + ); + }); +}); diff --git a/packages/auth0/test/intent-handlers.spec.js b/packages/auth0/test/nep413-handlers.spec.js similarity index 65% rename from packages/auth0/test/intent-handlers.spec.js rename to packages/auth0/test/nep413-handlers.spec.js index 034af3f..04619da 100644 --- a/packages/auth0/test/intent-handlers.spec.js +++ b/packages/auth0/test/nep413-handlers.spec.js @@ -6,7 +6,7 @@ * single-payload rule. */ const { onExecutePostLogin } = require("../src/actions/authorize-app.action.js"); -const { buildIntentPayload, buildIntentMessage, buildTransaction, buildDelegateAction } = require("./fixtures/builders.js"); +const { buildNep413Payload, buildIntentMessage, buildTransaction, buildDelegateAction } = require("./fixtures/builders.js"); const ONCHAIN_AUDIENCE = "https://onchain.example"; @@ -35,14 +35,14 @@ function makeApi() { return { api, calls }; } -function makeEvent({ query = {}, audience = ONCHAIN_AUDIENCE, intentsRecipient } = {}) { +function makeEvent({ query = {}, audience = ONCHAIN_AUDIENCE, allowedRecipients } = {}) { return { secrets: { ONCHAIN_AUDIENCE, TRANSACTION_FORM: "modal_tx", DELEGATE_ACTION_FORM: "modal_delegate", - INTENT_FORM: "modal_intent", - ...(intentsRecipient ? { INTENTS_RECIPIENT: intentsRecipient } : {}), + NEP413_FORM: "modal_nep413", + ...(allowedRecipients ? { NEP413_ALLOWED_RECIPIENTS: allowedRecipients } : {}), }, request: { query }, resource_server: audience == null ? undefined : { identifier: audience }, @@ -53,12 +53,12 @@ function makeEvent({ query = {}, audience = ONCHAIN_AUDIENCE, intentsRecipient } describe("onExecutePostLogin — intent dispatch", () => { test("renders the intent form with signer, verifier and deadline", async () => { const { api, calls } = makeApi(); - const { csv, message } = buildIntentPayload(); + const { csv, message } = buildNep413Payload(); - await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: csv } }), api); expect(calls.deny).toEqual([]); - expect(calls.render.modalId).toBe("modal_intent"); + expect(calls.render.modalId).toBe("modal_nep413"); expect(calls.render.opts.fields).toMatchObject({ name: "Test App", imageUrl: "https://logo.example/x.png", @@ -70,18 +70,18 @@ describe("onExecutePostLogin — intent dispatch", () => { test("hands the intents to the form as a JSON string", async () => { const { api, calls } = makeApi(); - const { csv, message } = buildIntentPayload(); + const { csv, message } = buildNep413Payload(); - await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: csv } }), api); expect(JSON.parse(calls.render.opts.fields.intents)).toEqual(message.intents); }); test("sets fatxn to the exact bytes received", async () => { const { api, calls } = makeApi(); - const { csv, bytes } = buildIntentPayload(); + const { csv, bytes } = buildNep413Payload(); - await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: csv } }), api); // The guard compares fatxn to sign_payload byte for byte; any transformation here // would make every signature fail on-chain. @@ -90,18 +90,18 @@ describe("onExecutePostLogin — intent dispatch", () => { test("strips the OIDC profile scopes like the other payload types", async () => { const { api, calls } = makeApi(); - const { csv } = buildIntentPayload(); + const { csv } = buildNep413Payload(); - await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: csv } }), api); expect(calls.removedScopes).toEqual(["profile", "email", "offline_access"]); }); test("honours a tenant-configured verifier", async () => { const { api, calls } = makeApi(); - const { csv } = buildIntentPayload({ recipient: "intents.testnet" }); + const { csv } = buildNep413Payload({ recipient: "intents.testnet" }); - await onExecutePostLogin(makeEvent({ query: { intent: csv }, intentsRecipient: "intents.testnet" }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: csv }, allowedRecipients: "intents.testnet" }), api); expect(calls.deny).toEqual([]); expect(calls.render.opts.fields.recipient).toBe("intents.testnet"); @@ -111,40 +111,50 @@ describe("onExecutePostLogin — intent dispatch", () => { describe("onExecutePostLogin — intent rejection", () => { test("denies instead of rendering when the payload cannot be decoded", async () => { const { api, calls } = makeApi(); - const { csv } = buildIntentPayload({ tag: 7 }); + const { csv } = buildNep413Payload({ tag: 7 }); - await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: csv } }), api); - expect(calls.deny).toEqual(["Intent payload is missing the NEP-413 domain tag"]); + expect(calls.deny).toEqual(["Payload is missing the NEP-413 domain tag"]); expect(calls.render).toBeNull(); expect(calls.customClaims.fatxn).toBeUndefined(); }); - test("denies an intent aimed at another verifier", async () => { + test("denies a recipient off the configured allowlist", async () => { const { api, calls } = makeApi(); - const { csv } = buildIntentPayload({ recipient: "evil.near" }); + const { csv } = buildNep413Payload({ recipient: "evil.near" }); - await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: csv }, allowedRecipients: "intents.near" }), api); - expect(calls.deny).toEqual(["Intent payload targets an unexpected recipient: evil.near"]); + expect(calls.deny).toEqual(["NEP-413 message targets an unexpected recipient: evil.near"]); expect(calls.customClaims.fatxn).toBeUndefined(); }); - test("denies an intent whose message shows the user nothing", async () => { + test("allows any recipient when no allowlist is configured", async () => { const { api, calls } = makeApi(); - const { csv } = buildIntentPayload({ message: buildIntentMessage({ intents: [] }) }); + const { csv } = buildNep413Payload({ rawMessage: "Sign in to example.com", recipient: "example.com" }); - await onExecutePostLogin(makeEvent({ query: { intent: csv } }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: csv } }), api); - expect(calls.deny).toEqual(["Intent message carries no intents to approve"]); + expect(calls.deny).toEqual([]); + expect(calls.render.opts.fields.recipient).toBe("example.com"); + }); + + test("denies a message with nothing to show the user", async () => { + const { api, calls } = makeApi(); + const { csv } = buildNep413Payload({ rawMessage: "" }); + + await onExecutePostLogin(makeEvent({ query: { nep413: csv } }), api); + + expect(calls.deny).toEqual(["NEP-413 message is empty"]); expect(calls.customClaims.fatxn).toBeUndefined(); }); - test("denies an intent sent to a non-signing audience", async () => { + test("denies a message sent to a non-signing audience", async () => { const { api, calls } = makeApi(); - const { csv } = buildIntentPayload(); + const { csv } = buildNep413Payload(); - await onExecutePostLogin(makeEvent({ query: { intent: csv }, audience: "https://other.example" }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: csv }, audience: "https://other.example" }), api); expect(calls.deny).toEqual(["Transaction payload only allowed with signing audience"]); }); @@ -153,10 +163,10 @@ describe("onExecutePostLogin — intent rejection", () => { describe("onExecutePostLogin — single payload rule", () => { test("denies when an intent arrives alongside a transaction", async () => { const { api, calls } = makeApi(); - const { csv: intentCsv } = buildIntentPayload(); + const { csv: intentCsv } = buildNep413Payload(); const { csv: txCsv } = buildTransaction(); - await onExecutePostLogin(makeEvent({ query: { intent: intentCsv, transaction: txCsv } }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: intentCsv, transaction: txCsv } }), api); // Otherwise the screen could show one payload while a different one lands in fatxn. expect(calls.deny).toEqual(["Only one signing payload may be requested at a time"]); diff --git a/packages/auth0/test/intent-helpers.spec.js b/packages/auth0/test/nep413-helpers.spec.js similarity index 69% rename from packages/auth0/test/intent-helpers.spec.js rename to packages/auth0/test/nep413-helpers.spec.js index 551ae90..7dc314f 100644 --- a/packages/auth0/test/intent-helpers.spec.js +++ b/packages/auth0/test/nep413-helpers.spec.js @@ -1,7 +1,7 @@ /** * @jest-environment jsdom * - * Tests for `renderIntentDetails` — the approval screen for NEP-413 intents. + * Tests for `renderNep413Details` — the approval screen for NEP-413 intents. * * This is the half of the security model the contract cannot enforce: the user must be able * to read what they are about to sign. These tests assert the transfer details actually reach @@ -13,10 +13,14 @@ const helpers = require("../src/forms/shared/helpers/index.js"); const TRANSFER = { intent: "transfer", receiver_id: "deposit.near", tokens: { "nep141:usdc.near": "1000000" } }; function render(intents, fields = []) { - return helpers.renderIntentDetails({ fields, intents: JSON.stringify(intents) }); + return helpers.renderNep413Details({ + fields, + message: JSON.stringify({ intents }), + intents: JSON.stringify(intents), + }); } -describe("renderIntentDetails — top-level fields", () => { +describe("renderNep413Details — top-level fields", () => { test("renders the fields it is given", () => { const box = render( [TRANSFER], @@ -43,7 +47,7 @@ describe("renderIntentDetails — top-level fields", () => { }); }); -describe("renderIntentDetails — transfer intents", () => { +describe("renderNep413Details — transfer intents", () => { test("shows the receiver and the token amount", () => { const box = render([TRANSFER]); const text = box.textContent; @@ -77,7 +81,7 @@ describe("renderIntentDetails — transfer intents", () => { }); }); -describe("renderIntentDetails — unrecognized intents", () => { +describe("renderNep413Details — unrecognized intents", () => { test("flags an unknown intent kind with a warning", () => { const box = render([{ intent: "token_diff", diff: { "nep141:usdc.near": "-1" } }]); expect(box.querySelector(".warning-icon")).not.toBeNull(); @@ -104,19 +108,37 @@ describe("renderIntentDetails — unrecognized intents", () => { }); }); -describe("renderIntentDetails — malformed payloads", () => { - test("shows a parse error instead of throwing", () => { - const box = helpers.renderIntentDetails({ fields: [], intents: "{not json" }); - expect(box.querySelector(".warning-callout").textContent).toBe("Failed to parse intents payload."); +describe("renderNep413Details — plain messages", () => { + test("shows an arbitrary message verbatim", () => { + const box = helpers.renderNep413Details({ fields: [], message: "Sign in to example.com" }); + expect(box.textContent).toContain("Sign in to example.com"); + expect(box.querySelectorAll(".accordion")).toHaveLength(0); }); - test("renders an empty section when there are no intents", () => { - const box = render([]); - expect(box.querySelectorAll(".accordion")).toHaveLength(0); + test("pretty-prints a JSON message that is not an intents body", () => { + const box = helpers.renderNep413Details({ fields: [], message: JSON.stringify({ purpose: "login" }) }); + expect(box.textContent).toContain("purpose"); + expect(box.textContent).toContain("login"); }); - test("treats a missing intents string as empty", () => { - const box = helpers.renderIntentDetails({ fields: [] }); + test("falls back to the message when the intents payload is unreadable", () => { + // An unparseable structured view must never hide what is actually being signed. + const box = helpers.renderNep413Details({ fields: [], message: "the real message", intents: "{not json" }); + expect(box.textContent).toContain("the real message"); + }); + + test("falls back to the message when the intents array is empty", () => { + const box = helpers.renderNep413Details({ fields: [], message: "nothing to break out", intents: "[]" }); + expect(box.textContent).toContain("nothing to break out"); expect(box.querySelectorAll(".accordion")).toHaveLength(0); }); + + test("shows the recipient even for a plain message", () => { + const box = helpers.renderNep413Details({ + fields: [{ label: "Recipient", value: "example.com" }], + message: "Sign in", + }); + // NEP-413 leans on the user seeing who the message is addressed to. + expect(box.textContent).toContain("example.com"); + }); }); diff --git a/packages/providers/javascript/src/nep413.ts b/packages/providers/javascript/src/nep413.ts index c10c4a2..91d64fb 100644 --- a/packages/providers/javascript/src/nep413.ts +++ b/packages/providers/javascript/src/nep413.ts @@ -31,21 +31,42 @@ export const NEP413_PAYLOAD_SCHEMA: Schema = { export type NEP413Payload = { /** - * The message to sign. For NEAR Intents this is the JSON-encoded intent body - * (`signer_id`, `deadline`, `intents`). + * The message to sign. Any string: a human-readable challenge such as "Sign in to + * example.com", or a structured body like the NEAR Intents JSON (`signer_id`, `deadline`, + * `intents`), which the approval screen renders as individual intents. */ message: string; /** - * 32-byte nonce. Generated when omitted. + * 32-byte nonce guarding against replay. Generated when omitted. */ nonce?: Uint8Array; /** - * Account the message is addressed to — the intents verifier, e.g. `intents.near`. + * Account the message is addressed to, e.g. `intents.near` or an app's own account. Under + * NEP-413 this is what stops a message from being relayed to a third party, and it is shown + * to the user on the approval screen. */ recipient: string; + /** + * Optional URL the signing result is returned to. Part of the signed payload. + */ callbackUrl?: string; }; +/** + * A NEP-413 signature result, in the shape the standard defines for `signMessage`. + * https://github.com/near/NEPs/blob/master/neps/nep-0413.md#output-interface + */ +export type NEP413SignedMessage = { + /** The signing account. */ + accountId: string; + /** Public key as `:`. */ + publicKey: string; + /** Base64-encoded signature over sha256 of the serialized payload. */ + signature: string; + /** Echo of the caller's CSRF state, when one was supplied. */ + state?: string; +}; + /** * Generate a random 32-byte NEP-413 nonce. * @returns The nonce. @@ -82,3 +103,26 @@ export function serializeNep413Payload(payload: NEP413Payload): Uint8Array { export function encodeNep413Payload(payload: NEP413Payload): number[] { return Array.from(serializeNep413Payload(payload)); } + +/** + * Assemble a NEP-413 SignedMessage from a signature produced through the FastAuth flow, so callers can hand verifiers the exact shape the standard defines. + * @param params The signing account, its public key, the raw signature and an optional state. + * @returns The signed message. + */ +export function buildNep413SignedMessage(params: { + accountId: string; + publicKey: string; + signature: Uint8Array | number[]; + state?: string; +}): NEP413SignedMessage { + const bytes = params.signature instanceof Uint8Array ? params.signature : Uint8Array.from(params.signature); + let binary = ""; + for (const byte of bytes) binary += String.fromCharCode(byte); + + return { + accountId: params.accountId, + publicKey: params.publicKey, + signature: globalThis.btoa(binary), + ...(params.state !== undefined ? { state: params.state } : {}), + }; +} diff --git a/packages/providers/javascript/src/provider.ts b/packages/providers/javascript/src/provider.ts index 3d36c1b..c7b0602 100644 --- a/packages/providers/javascript/src/provider.ts +++ b/packages/providers/javascript/src/provider.ts @@ -10,9 +10,9 @@ import { JavascriptLoginOptions, JavascriptLoginWithRedirectOptions, JavascriptLoginWithPopupOptions, - JavascriptRequestIntentSignatureOptions, - JavascriptRequestIntentSignatureWithRedirectOptions, - JavascriptRequestIntentSignatureWithPopupOptions, + JavascriptRequestMessageSignatureOptions, + JavascriptRequestMessageSignatureWithRedirectOptions, + JavascriptRequestMessageSignatureWithPopupOptions, } from "./types"; import { encodeNep413Payload } from "./nep413"; import { @@ -21,7 +21,7 @@ import { IFastAuthProvider, LoginResponse, RequestDelegateActionSignatureResponse, - RequestIntentSignatureResponse, + RequestMessageSignatureResponse, RequestTransactionSignatureResponse, User, } from "@shared/core"; @@ -282,56 +282,56 @@ export class JavascriptProvider implements IFastAuthProvider { } /** - * Request a NEP-413 intent signature with redirect. - * @param requestSignatureOptions The options for the request intent signature with redirect. + * Request a NEP-413 message signature with redirect. + * @param requestSignatureOptions The options for the request message signature with redirect. * @returns The void. */ - private async requestIntentSignatureWithRedirect( - requestSignatureOptions: JavascriptRequestIntentSignatureWithRedirectOptions, + private async requestMessageSignatureWithRedirect( + requestSignatureOptions: JavascriptRequestMessageSignatureWithRedirectOptions, ): Promise { - const { redirectUri, intent, ...opts } = requestSignatureOptions; + const { redirectUri, payload, state, ...opts } = requestSignatureOptions; await this.client.loginWithRedirect({ authorizationParams: { audience: this.options.signingAudience, scope: "transaction:sign", - intent: encodeNep413Payload(intent), + nep413: encodeNep413Payload(payload), redirect_uri: redirectUri, + ...(state !== undefined ? { state } : {}), }, ...opts, }); } /** - * Request a NEP-413 intent signature with popup. - * @param requestSignatureOptions The options for the request intent signature with popup. + * Request a NEP-413 message signature with popup. + * @param requestSignatureOptions The options for the request message signature with popup. * @returns The void. */ - private async requestIntentSignatureWithPopup( - requestSignatureOptions: JavascriptRequestIntentSignatureWithPopupOptions, + private async requestMessageSignatureWithPopup( + requestSignatureOptions: JavascriptRequestMessageSignatureWithPopupOptions, ): Promise { - const { intent, ...opts } = requestSignatureOptions; + const { payload, state, ...opts } = requestSignatureOptions; await this.client.loginWithPopup({ authorizationParams: { audience: this.options.signingAudience, scope: "transaction:sign", - intent: encodeNep413Payload(intent), + nep413: encodeNep413Payload(payload), + ...(state !== undefined ? { state } : {}), }, ...opts, }); } /** - * Request a signature over a NEP-413 off-chain message. Unlike a transaction signature, the - * signed bytes are an intent published to the solver relay, which executes it against the - * intents contract and pays the gas, so the signer needs no on-chain NEAR account or balance. - * @param options The options for the request intent signature. + * Request a signature over a NEP-413 off-chain message, covering wallet sign-in challenges, app authentication and NEAR Intents alike. The signed bytes are not a NEAR transaction, so nothing is broadcast and no gas is spent; for intents the result is published to the solver relay, which executes it and pays the gas, leaving the signer with no need for an on-chain account. + * @param options The options for the request message signature. * @returns The user. */ - async requestIntentSignature(options: JavascriptRequestIntentSignatureOptions): Promise { + async requestMessageSignature(options: JavascriptRequestMessageSignatureOptions): Promise { if ("redirectUri" in options && options.redirectUri) { - await this.requestIntentSignatureWithRedirect(options as JavascriptRequestIntentSignatureWithRedirectOptions); + await this.requestMessageSignatureWithRedirect(options as JavascriptRequestMessageSignatureWithRedirectOptions); } else { - await this.requestIntentSignatureWithPopup(options); + await this.requestMessageSignatureWithPopup(options); } return this.getUserId(); } diff --git a/packages/providers/javascript/src/types.ts b/packages/providers/javascript/src/types.ts index 7023d16..4332389 100644 --- a/packages/providers/javascript/src/types.ts +++ b/packages/providers/javascript/src/types.ts @@ -53,16 +53,24 @@ export type JavascriptRequestDelegateActionSignatureOptions = | JavascriptRequestDelegateActionSignatureWithRedirectOptions | JavascriptRequestDelegateActionSignatureWithPopupOptions; -export type JavascriptBaseRequestIntentSignatureOptions = JavascriptBaseRequestSignatureOptions & { - intent: NEP413Payload; +export type JavascriptBaseRequestMessageSignatureOptions = JavascriptBaseRequestSignatureOptions & { + /** + * The NEP-413 payload to sign. + */ + payload: NEP413Payload; + /** + * Optional CSRF state echoed back to the caller. Per NEP-413 it is not part of the signed + * bytes — it travels alongside the request and is returned with the result. + */ + state?: string; }; -export type JavascriptRequestIntentSignatureWithRedirectOptions = JavascriptBaseRequestIntentSignatureOptions & +export type JavascriptRequestMessageSignatureWithRedirectOptions = JavascriptBaseRequestMessageSignatureOptions & Omit; -export type JavascriptRequestIntentSignatureWithPopupOptions = JavascriptBaseRequestIntentSignatureOptions & +export type JavascriptRequestMessageSignatureWithPopupOptions = JavascriptBaseRequestMessageSignatureOptions & Omit; -export type JavascriptRequestIntentSignatureOptions = - | JavascriptRequestIntentSignatureWithRedirectOptions - | JavascriptRequestIntentSignatureWithPopupOptions; +export type JavascriptRequestMessageSignatureOptions = + | JavascriptRequestMessageSignatureWithRedirectOptions + | JavascriptRequestMessageSignatureWithPopupOptions; diff --git a/packages/providers/javascript/test/nep413.spec.ts b/packages/providers/javascript/test/nep413.spec.ts index 5652bc3..2980c7e 100644 --- a/packages/providers/javascript/test/nep413.spec.ts +++ b/packages/providers/javascript/test/nep413.spec.ts @@ -6,6 +6,7 @@ import { encodeNep413Payload, generateNep413Nonce, serializeNep413Payload, + buildNep413SignedMessage, } from "../src/nep413"; const FIXED_NONCE = Uint8Array.from(Array.from({ length: NEP413_NONCE_LENGTH }, (_, i) => (i * 3) % 256)); @@ -96,4 +97,60 @@ describe("encodeNep413Payload", () => { expect(encoded).toEqual(Array.from(serializeNep413Payload(params))); expect(encoded.every((value) => Number.isInteger(value) && value >= 0 && value <= 255)).toBe(true); }); + + it("encodes an arbitrary sign-in challenge, not just intents", () => { + // NEP-413 messages are arbitrary strings; the serializer must not assume JSON. + const encoded = encodeNep413Payload({ + message: "Sign in to example.com", + nonce: FIXED_NONCE, + recipient: "example.com", + }); + expect(encoded.length).toBeGreaterThan(0); + }); +}); + +describe("buildNep413SignedMessage", () => { + const SIGNATURE = Uint8Array.from(Array.from({ length: 64 }, (_, i) => i)); + + it("returns the shape NEP-413 defines for a signed message", () => { + const signed = buildNep413SignedMessage({ + accountId: "trader.near", + publicKey: "ed25519:abc", + signature: SIGNATURE, + }); + + expect(signed).toEqual({ + accountId: "trader.near", + publicKey: "ed25519:abc", + signature: expect.any(String), + }); + }); + + it("base64-encodes the signature", () => { + const signed = buildNep413SignedMessage({ accountId: "a.near", publicKey: "ed25519:k", signature: SIGNATURE }); + // Round-trip through atob to prove the bytes survive intact. + const decoded = Uint8Array.from(globalThis.atob(signed.signature), (c) => c.charCodeAt(0)); + expect(Array.from(decoded)).toEqual(Array.from(SIGNATURE)); + }); + + it("accepts a plain number array as the signature", () => { + const fromArray = buildNep413SignedMessage({ accountId: "a.near", publicKey: "ed25519:k", signature: Array.from(SIGNATURE) }); + const fromBytes = buildNep413SignedMessage({ accountId: "a.near", publicKey: "ed25519:k", signature: SIGNATURE }); + expect(fromArray.signature).toBe(fromBytes.signature); + }); + + it("echoes the state when one was supplied", () => { + const signed = buildNep413SignedMessage({ + accountId: "a.near", + publicKey: "ed25519:k", + signature: SIGNATURE, + state: "csrf-token", + }); + expect(signed.state).toBe("csrf-token"); + }); + + it("omits state entirely when none was supplied", () => { + const signed = buildNep413SignedMessage({ accountId: "a.near", publicKey: "ed25519:k", signature: SIGNATURE }); + expect("state" in signed).toBe(false); + }); }); diff --git a/packages/providers/javascript/test/provider.spec.ts b/packages/providers/javascript/test/provider.spec.ts index 37e66b5..eaeb557 100644 --- a/packages/providers/javascript/test/provider.spec.ts +++ b/packages/providers/javascript/test/provider.spec.ts @@ -679,8 +679,8 @@ describe("JavascriptProvider", () => { }); }); - describe("requestIntentSignature", () => { - const intent = { + describe("requestMessageSignature", () => { + const payload = { message: JSON.stringify({ signer_id: "trader.near", deadline: "2026-01-01T00:00:00.000Z", @@ -696,26 +696,26 @@ describe("JavascriptProvider", () => { }); describe("with redirect", () => { - it("should call loginWithRedirect with the encoded intent", async () => { + it("should call loginWithRedirect with the encoded payload", async () => { mockAuth0Client.loginWithRedirect.mockResolvedValue(undefined); - await provider.requestIntentSignature({ intent, redirectUri: "http://localhost:3000/callback" }); + await provider.requestMessageSignature({ payload, redirectUri: "http://localhost:3000/callback" }); const params = mockAuth0Client.loginWithRedirect.mock.calls[0][0].authorizationParams; expect(params.audience).toBe("auth0.jwt.fast-auth.testnet"); expect(params.scope).toBe("transaction:sign"); expect(params.redirect_uri).toBe("http://localhost:3000/callback"); - expect(Array.isArray(params.intent)).toBe(true); + expect(Array.isArray(params.nep413)).toBe(true); expect(mockAuth0Client.loginWithPopup).not.toHaveBeenCalled(); }); - it("should encode the intent with the NEP-413 domain tag first", async () => { + it("should encode the payload with the NEP-413 domain tag first", async () => { mockAuth0Client.loginWithRedirect.mockResolvedValue(undefined); - await provider.requestIntentSignature({ intent, redirectUri: "http://localhost:3000/callback" }); + await provider.requestMessageSignature({ payload, redirectUri: "http://localhost:3000/callback" }); // The tag is a little-endian u32 at offset 0 — the first thing the action checks. - const encoded: number[] = mockAuth0Client.loginWithRedirect.mock.calls[0][0].authorizationParams.intent; + const encoded: number[] = mockAuth0Client.loginWithRedirect.mock.calls[0][0].authorizationParams.nep413; const tag = encoded[0] | (encoded[1] << 8) | (encoded[2] << 16) | (encoded[3] << 24); expect(tag >>> 0).toBe(Math.pow(2, 31) + 413); }); @@ -723,7 +723,7 @@ describe("JavascriptProvider", () => { it("should propagate errors from loginWithRedirect", async () => { mockAuth0Client.loginWithRedirect.mockRejectedValue(new Error("Login redirect failed")); - await expect(provider.requestIntentSignature({ intent, redirectUri: "http://localhost:3000/callback" })).rejects.toThrow( + await expect(provider.requestMessageSignature({ payload, redirectUri: "http://localhost:3000/callback" })).rejects.toThrow( "Login redirect failed", ); }); @@ -733,26 +733,26 @@ describe("JavascriptProvider", () => { it("should call loginWithPopup when no redirectUri is provided", async () => { mockAuth0Client.loginWithPopup.mockResolvedValue(undefined); - await provider.requestIntentSignature({ intent }); + await provider.requestMessageSignature({ payload }); const params = mockAuth0Client.loginWithPopup.mock.calls[0][0].authorizationParams; expect(params.audience).toBe("auth0.jwt.fast-auth.testnet"); expect(params.scope).toBe("transaction:sign"); - expect(Array.isArray(params.intent)).toBe(true); + expect(Array.isArray(params.nep413)).toBe(true); expect(mockAuth0Client.loginWithRedirect).not.toHaveBeenCalled(); }); it("should propagate errors from loginWithPopup", async () => { mockAuth0Client.loginWithPopup.mockRejectedValue(new Error("Login popup failed")); - await expect(provider.requestIntentSignature({ intent })).rejects.toThrow("Login popup failed"); + await expect(provider.requestMessageSignature({ payload })).rejects.toThrow("Login popup failed"); }); }); it("should return the user id after signing", async () => { mockAuth0Client.loginWithPopup.mockResolvedValue(undefined); - const result = await provider.requestIntentSignature({ intent }); + const result = await provider.requestMessageSignature({ payload }); expect(result).toEqual({ userId: "test-user-id" }); }); @@ -761,7 +761,7 @@ describe("JavascriptProvider", () => { mockAuth0Client.loginWithPopup.mockResolvedValue(undefined); mockAuth0Client.getIdTokenClaims.mockResolvedValue(undefined); - await expect(provider.requestIntentSignature({ intent })).rejects.toThrow( + await expect(provider.requestMessageSignature({ payload })).rejects.toThrow( new JavascriptProviderError(JavascriptProviderErrorCodes.USER_NOT_LOGGED_IN), ); }); diff --git a/packages/sdks/browser/src/signers/signer.ts b/packages/sdks/browser/src/signers/signer.ts index 37df3fe..709a085 100644 --- a/packages/sdks/browser/src/signers/signer.ts +++ b/packages/sdks/browser/src/signers/signer.ts @@ -106,15 +106,15 @@ export class FastAuthSigner

{ } /** - * Request a signature over a NEP-413 off-chain message, used to authorize NEAR Intents without submitting a transaction. The method is optional on the provider interface, so providers that have not implemented the flow raise a clear error instead of failing on an undefined call. - * @param args The arguments to request an intent signature. - * @returns The signed intent response. + * Request a signature over a NEP-413 off-chain message, covering wallet sign-in challenges, app authentication and NEAR Intents alike. The method is optional on the provider interface, so providers that have not implemented the flow raise a clear error instead of failing on an undefined call. + * @param args The arguments to request a message signature. + * @returns The signed message response. */ - async requestIntentSignature(...args: any[]) { - if (typeof this.fastAuthProvider.requestIntentSignature !== "function") { - throw new Error("The configured FastAuth provider does not support NEP-413 intent signatures"); + async requestMessageSignature(...args: any[]) { + if (typeof this.fastAuthProvider.requestMessageSignature !== "function") { + throw new Error("The configured FastAuth provider does not support NEP-413 message signatures"); } - return await this.fastAuthProvider.requestIntentSignature(...args); + return await this.fastAuthProvider.requestMessageSignature(...args); } /** diff --git a/packages/shared/core/src/index.ts b/packages/shared/core/src/index.ts index c796ee7..d270145 100644 --- a/packages/shared/core/src/index.ts +++ b/packages/shared/core/src/index.ts @@ -24,7 +24,7 @@ export type { LoginResponse, RequestTransactionSignatureResponse, RequestDelegateActionSignatureResponse, - RequestIntentSignatureResponse, + RequestMessageSignatureResponse, GetSignatureRequestResponse, IFastAuthProvider, MPCContractAlgorithm, diff --git a/packages/shared/core/src/provider.ts b/packages/shared/core/src/provider.ts index a4ed60c..3612a67 100644 --- a/packages/shared/core/src/provider.ts +++ b/packages/shared/core/src/provider.ts @@ -21,9 +21,9 @@ export type RequestTransactionSignatureResponse = User; export type RequestDelegateActionSignatureResponse = User; /** - * Response of a NEP-413 intent signature request. + * Response of a NEP-413 message signature request. */ -export type RequestIntentSignatureResponse = User; +export type RequestMessageSignatureResponse = User; /** * Response returned after a successful signature request @@ -37,11 +37,12 @@ export interface IFastAuthProvider { requestTransactionSignature(...args: any[]): Promise; requestDelegateActionSignature(...args: any[]): Promise; /** - * Request a signature over a NEP-413 off-chain message, used to authorize NEAR Intents - * without submitting a transaction. Optional: providers that have not implemented the - * flow simply omit it, and callers must check for its presence before use. + * Request a signature over a NEP-413 off-chain message: wallet sign-in challenges, app + * authentication, or NEAR Intents authorized without submitting a transaction. Optional: + * providers that have not implemented the flow simply omit it, and callers must check for + * its presence before use. */ - requestIntentSignature?(...args: any[]): Promise; + requestMessageSignature?(...args: any[]): Promise; getSignatureRequest(): Promise; getPath(): Promise; } From 4c86d8c5065896d5111574f9144ecf1660ff688e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Rivera?= Date: Thu, 13 Aug 2026 16:53:07 +0200 Subject: [PATCH 3/6] refactor(auth0): drop the recipient allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was the last remnant of anchoring the payload to NEAR Intents — the first pass pinned `recipient` to intents.near, the second softened that to an opt-in allowlist, and neither was justified. The allowlist protects nothing. The attack NEP-413's recipient guards against is relaying: an app has you sign a message addressed to bank.near, then replays it there as you. The standard's defence is that you *see* bank.near on the approval screen while sitting in a different app — a tenant-level allowlist adds nothing to that, and the screen already renders the recipient unconditionally. It is also the wrong granularity. A recipient belongs to an application, not to a tenant serving many of them: a shared list is either permissive enough to filter nothing or breaks apps as they are added. Were per-app restriction ever wanted, client_metadata is where it belongs, not a global secret. What it did add was a secret to configure and a failure mode where a misspelled entry rejects every signature. No NEAR wallet restricts recipients for the same reasons. Removes parseRecipientAllowlist, the NEP413_ALLOWED_RECIPIENTS secret and their tests; keeps the recipient rendered on the approval screen, which is the actual protection. Co-Authored-By: Claude Opus 5 (1M context) --- .../auth0/src/actions/authorize-app.action.js | 37 ++------- packages/auth0/test/nep413-decoding.spec.js | 78 +++++-------------- packages/auth0/test/nep413-handlers.spec.js | 19 +---- 3 files changed, 31 insertions(+), 103 deletions(-) diff --git a/packages/auth0/src/actions/authorize-app.action.js b/packages/auth0/src/actions/authorize-app.action.js index 23d23a5..58fcc91 100644 --- a/packages/auth0/src/actions/authorize-app.action.js +++ b/packages/auth0/src/actions/authorize-app.action.js @@ -230,25 +230,6 @@ function decodeDelegateAction(encodedDelegateAction) { return delegateAction; } -/** - * Parse the optional recipient allowlist from tenant secrets. - * - * NEP-413 places no constraint on `recipient` — it is the application the message is addressed - * to, and the standard's protection is that the user *sees* it, not that the wallet restricts - * it. So an unset secret means "any recipient", matching how NEAR wallets behave. A tenant that - * wants to serve exactly one application can still pin it here. - * @param {string|undefined} secret Comma-separated account list, or undefined. - * @returns {string[]|null} The allowlist, or null when unrestricted. - */ -function parseRecipientAllowlist(secret) { - if (!secret) return null; - const entries = String(secret) - .split(",") - .map((value) => value.trim()) - .filter(Boolean); - return entries.length > 0 ? entries : null; -} - /** * Decode and validate a NEP-413 payload arriving on the authorize query string. * @@ -262,14 +243,15 @@ function parseRecipientAllowlist(secret) { * 2. There must be a message to show. Signing something the approval screen cannot display * would defeat the consent guarantee the whole flow rests on. * - * The recipient is only constrained when a tenant opts in via the allowlist; per the standard it - * is shown to the user rather than restricted. + * The recipient is not restricted. Under NEP-413 it names the application a message is addressed + * to, and the standard's protection is that the user sees it — which is why the approval screen + * always renders it. Restricting it here would be the wrong granularity anyway: a recipient + * belongs to an application, not to the tenant that hosts many of them. * @param {string} encodedPayload Comma-separated byte string from the query. - * @param {string[]|null} recipientAllowlist Accounts the message may target, or null for any. * @returns {{payload: object, message: object|null}} The payload, plus the message parsed as JSON when it is JSON. * @throws {Error} With a user-facing reason when any check fails. */ -function decodeNep413Payload(encodedPayload, recipientAllowlist) { +function decodeNep413Payload(encodedPayload) { const bytes = Uint8Array.from(String(encodedPayload).split(",").map((value) => Number(value))); let payload; @@ -287,10 +269,6 @@ function decodeNep413Payload(encodedPayload, recipientAllowlist) { throw new Error("NEP-413 message is empty"); } - if (recipientAllowlist && !recipientAllowlist.includes(payload.recipient)) { - throw new Error(`NEP-413 message targets an unexpected recipient: ${payload.recipient}`); - } - // A JSON message may be a structured payload the approval screen can render richly (NEAR // Intents being the case we know about). Plain-text messages are equally valid and are // shown verbatim, so failing to parse is not an error. @@ -423,11 +401,9 @@ exports.onExecutePostLogin = async (event, api) => { query.delegateAction.split(",").map((value) => Number(value)), ); } else { - const recipientAllowlist = parseRecipientAllowlist(event.secrets.NEP413_ALLOWED_RECIPIENTS); - let decoded; try { - decoded = decodeNep413Payload(query.nep413, recipientAllowlist); + decoded = decodeNep413Payload(query.nep413); } catch (error) { // A payload we cannot decode is a payload we cannot show the user. Signing it would // break the consent guarantee the whole flow rests on, so refuse instead. @@ -479,7 +455,6 @@ exports.onContinuePostLogin = async (event, api) => { exports.parseTransaction = parseTransaction; exports.decodeDelegateAction = decodeDelegateAction; exports.decodeNep413Payload = decodeNep413Payload; -exports.parseRecipientAllowlist = parseRecipientAllowlist; exports.extractIntents = extractIntents; exports.stringifyActions = stringifyActions; exports.stringifyIntents = stringifyIntents; diff --git a/packages/auth0/test/nep413-decoding.spec.js b/packages/auth0/test/nep413-decoding.spec.js index 2492edc..0ca67d8 100644 --- a/packages/auth0/test/nep413-decoding.spec.js +++ b/packages/auth0/test/nep413-decoding.spec.js @@ -11,18 +11,18 @@ * - the domain tag, without which the bytes could double as a NEAR transaction; * - a non-empty message, without which the approval screen has nothing to show. * - * The recipient is deliberately *not* restricted by default. Under NEP-413 it names the - * application a message is addressed to, and the protection is that the user sees it — the same - * way NEAR wallets behave. Tenants that want to serve exactly one application can opt into an - * allowlist, which is what the allowlist tests below cover. + * The recipient is deliberately *not* restricted. Under NEP-413 it names the application a + * message is addressed to, and the protection is that the user sees it on the approval screen — + * the same way NEAR wallets behave. Restricting it here would also be the wrong granularity: a + * recipient belongs to an application, not to a tenant that hosts many of them. */ -const { decodeNep413Payload, parseRecipientAllowlist, extractIntents, NEP413_PREFIX_TAG } = require("../src/actions/authorize-app.action.js"); +const { decodeNep413Payload, extractIntents, NEP413_PREFIX_TAG } = require("../src/actions/authorize-app.action.js"); const { buildNep413Payload, buildIntentMessage, buildTransaction, toCsv, INTENTS_RECIPIENT } = require("./fixtures/builders.js"); describe("decodeNep413Payload — any valid message", () => { test("decodes a plain-text sign-in challenge", () => { const { csv } = buildNep413Payload({ rawMessage: "Sign in to example.com", recipient: "example.com" }); - const { payload, message } = decodeNep413Payload(csv, null); + const { payload, message } = decodeNep413Payload(csv); expect(payload.tag).toBe(NEP413_PREFIX_TAG); expect(payload.message).toBe("Sign in to example.com"); @@ -33,23 +33,23 @@ describe("decodeNep413Payload — any valid message", () => { test("decodes a NEAR Intents message and exposes its body", () => { const { csv, message: original } = buildNep413Payload(); - const { payload, message } = decodeNep413Payload(csv, null); + const { payload, message } = decodeNep413Payload(csv); expect(payload.recipient).toBe(INTENTS_RECIPIENT); expect(message).toEqual(original); expect(extractIntents(message)).toHaveLength(1); }); - test("accepts any recipient when no allowlist is configured", () => { + test("accepts any recipient", () => { for (const recipient of ["intents.near", "app.example.com", "alice.near", "some-dapp"]) { const { csv } = buildNep413Payload({ rawMessage: "hello", recipient }); - expect(decodeNep413Payload(csv, null).payload.recipient).toBe(recipient); + expect(decodeNep413Payload(csv).payload.recipient).toBe(recipient); } }); test("accepts JSON that is not an intents body", () => { const { csv } = buildNep413Payload({ rawMessage: JSON.stringify({ purpose: "login", session: "abc" }) }); - const { message } = decodeNep413Payload(csv, null); + const { message } = decodeNep413Payload(csv); expect(message).toEqual({ purpose: "login", session: "abc" }); expect(extractIntents(message)).toBeNull(); @@ -57,7 +57,7 @@ describe("decodeNep413Payload — any valid message", () => { test("carries the callback url through when present", () => { const { csv } = buildNep413Payload({ rawMessage: "hello", callbackUrl: "https://example.com/cb" }); - expect(decodeNep413Payload(csv, null).payload.callbackUrl).toBe("https://example.com/cb"); + expect(decodeNep413Payload(csv).payload.callbackUrl).toBe("https://example.com/cb"); }); test("round-trips the exact byte string it was given", () => { @@ -75,96 +75,60 @@ describe("decodeNep413Payload — any valid message", () => { ], }); const { csv } = buildNep413Payload({ message }); - expect(extractIntents(decodeNep413Payload(csv, null).message)).toHaveLength(2); + expect(extractIntents(decodeNep413Payload(csv).message)).toHaveLength(2); }); }); describe("decodeNep413Payload — domain separation", () => { test("rejects a payload whose tag is not the NEP-413 prefix", () => { const { csv } = buildNep413Payload({ tag: 1 }); - expect(() => decodeNep413Payload(csv, null)).toThrow(/missing the NEP-413 domain tag/); + expect(() => decodeNep413Payload(csv)).toThrow(/missing the NEP-413 domain tag/); }); test("rejects the delegate-action prefix reused as a tag", () => { const { csv } = buildNep413Payload({ tag: Math.pow(2, 30) + 366 }); - expect(() => decodeNep413Payload(csv, null)).toThrow(/missing the NEP-413 domain tag/); + expect(() => decodeNep413Payload(csv)).toThrow(/missing the NEP-413 domain tag/); }); test("rejects transaction bytes submitted as a message", () => { // A real NEAR transaction must never decode into an approvable message, whether it // fails at borsh or at the tag check. const { csv } = buildTransaction(); - expect(() => decodeNep413Payload(csv, null)).toThrow(); + expect(() => decodeNep413Payload(csv)).toThrow(); }); }); describe("decodeNep413Payload — message must be showable", () => { test("rejects an empty message", () => { const { csv } = buildNep413Payload({ rawMessage: "" }); - expect(() => decodeNep413Payload(csv, null)).toThrow(/message is empty/); + expect(() => decodeNep413Payload(csv)).toThrow(/message is empty/); }); test("accepts a message with no intents array", () => { // Previously rejected. A message need not be a NEAR Intents body to be worth signing. const { csv } = buildNep413Payload({ rawMessage: JSON.stringify({ signer_id: "trader.near" }) }); - expect(() => decodeNep413Payload(csv, null)).not.toThrow(); + expect(() => decodeNep413Payload(csv)).not.toThrow(); }); test("accepts a message whose intents array is empty", () => { const { csv } = buildNep413Payload({ message: buildIntentMessage({ intents: [] }) }); - const { message } = decodeNep413Payload(csv, null); + const { message } = decodeNep413Payload(csv); expect(extractIntents(message)).toBeNull(); }); }); -describe("decodeNep413Payload — recipient allowlist", () => { - test("accepts a recipient on the allowlist", () => { - const { csv } = buildNep413Payload({ rawMessage: "hello", recipient: "intents.near" }); - expect(() => decodeNep413Payload(csv, ["intents.near"])).not.toThrow(); - }); - - test("rejects a recipient off the allowlist", () => { - const { csv } = buildNep413Payload({ rawMessage: "hello", recipient: "evil.near" }); - expect(() => decodeNep413Payload(csv, ["intents.near"])).toThrow(/unexpected recipient: evil\.near/); - }); - - test("accepts any entry of a multi-value allowlist", () => { - for (const recipient of ["intents.near", "intents.testnet"]) { - const { csv } = buildNep413Payload({ rawMessage: "hello", recipient }); - expect(() => decodeNep413Payload(csv, ["intents.near", "intents.testnet"])).not.toThrow(); - } - }); -}); - -describe("parseRecipientAllowlist", () => { - test("returns null when the secret is unset, meaning any recipient", () => { - expect(parseRecipientAllowlist(undefined)).toBeNull(); - expect(parseRecipientAllowlist("")).toBeNull(); - }); - - test("splits a comma-separated list and trims entries", () => { - expect(parseRecipientAllowlist(" intents.near , intents.testnet ")).toEqual(["intents.near", "intents.testnet"]); - }); - - test("treats a list of only separators as unset rather than as an empty allowlist", () => { - // An empty allowlist would reject everything — almost certainly a misconfiguration - // rather than an intent to disable signing entirely. - expect(parseRecipientAllowlist(" , , ")).toBeNull(); - }); -}); - describe("decodeNep413Payload — malformed input", () => { test("rejects bytes that are not a NEP-413 payload", () => { - expect(() => decodeNep413Payload(toCsv(Uint8Array.from([1, 2, 3, 4])), null)).toThrow(/not a valid NEP-413 message/); + expect(() => decodeNep413Payload(toCsv(Uint8Array.from([1, 2, 3, 4])))).toThrow(/not a valid NEP-413 message/); }); test("rejects an empty payload", () => { - expect(() => decodeNep413Payload("", null)).toThrow(/not a valid NEP-413 message/); + expect(() => decodeNep413Payload("")).toThrow(/not a valid NEP-413 message/); }); test("rejects a truncated payload", () => { const { bytes } = buildNep413Payload(); - expect(() => decodeNep413Payload(toCsv(bytes.slice(0, Math.floor(bytes.length / 2))), null)).toThrow( + expect(() => decodeNep413Payload(toCsv(bytes.slice(0, Math.floor(bytes.length / 2))))).toThrow( /not a valid NEP-413 message/, ); }); diff --git a/packages/auth0/test/nep413-handlers.spec.js b/packages/auth0/test/nep413-handlers.spec.js index 04619da..a20fd76 100644 --- a/packages/auth0/test/nep413-handlers.spec.js +++ b/packages/auth0/test/nep413-handlers.spec.js @@ -35,14 +35,13 @@ function makeApi() { return { api, calls }; } -function makeEvent({ query = {}, audience = ONCHAIN_AUDIENCE, allowedRecipients } = {}) { +function makeEvent({ query = {}, audience = ONCHAIN_AUDIENCE } = {}) { return { secrets: { ONCHAIN_AUDIENCE, TRANSACTION_FORM: "modal_tx", DELEGATE_ACTION_FORM: "modal_delegate", NEP413_FORM: "modal_nep413", - ...(allowedRecipients ? { NEP413_ALLOWED_RECIPIENTS: allowedRecipients } : {}), }, request: { query }, resource_server: audience == null ? undefined : { identifier: audience }, @@ -97,11 +96,11 @@ describe("onExecutePostLogin — intent dispatch", () => { expect(calls.removedScopes).toEqual(["profile", "email", "offline_access"]); }); - test("honours a tenant-configured verifier", async () => { + test("passes the recipient through unchanged whatever it is", async () => { const { api, calls } = makeApi(); const { csv } = buildNep413Payload({ recipient: "intents.testnet" }); - await onExecutePostLogin(makeEvent({ query: { nep413: csv }, allowedRecipients: "intents.testnet" }), api); + await onExecutePostLogin(makeEvent({ query: { nep413: csv } }), api); expect(calls.deny).toEqual([]); expect(calls.render.opts.fields.recipient).toBe("intents.testnet"); @@ -120,17 +119,7 @@ describe("onExecutePostLogin — intent rejection", () => { expect(calls.customClaims.fatxn).toBeUndefined(); }); - test("denies a recipient off the configured allowlist", async () => { - const { api, calls } = makeApi(); - const { csv } = buildNep413Payload({ recipient: "evil.near" }); - - await onExecutePostLogin(makeEvent({ query: { nep413: csv }, allowedRecipients: "intents.near" }), api); - - expect(calls.deny).toEqual(["NEP-413 message targets an unexpected recipient: evil.near"]); - expect(calls.customClaims.fatxn).toBeUndefined(); - }); - - test("allows any recipient when no allowlist is configured", async () => { + test("accepts any recipient and shows it to the user", async () => { const { api, calls } = makeApi(); const { csv } = buildNep413Payload({ rawMessage: "Sign in to example.com", recipient: "example.com" }); From 28360978d750b2e2a6e8b1452beeee587102963f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Rivera?= Date: Thu, 13 Aug 2026 17:03:24 +0200 Subject: [PATCH 4/6] docs(auth0): add package README, and derive implicit account ids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two gaps found while checking whether the NEP-413 support is actually usable end to end. The action's deployment was undocumented. It expects four secrets and renders forms by id, but nothing in the repo said so — build.js mentions the JSON is "ready to upload" and stops there, leaving the import steps and the secret names in the head of whoever administers the tenant. The README now covers the payload types, the secrets table, how to import a form and wire its id, and how to iterate locally against the playground. The SDK also had no way to derive a NEAR implicit account id. That is the piece the gasless path needs: an implicit account is the hex of its ed25519 public key, needs no on-chain creation, and the intents verifier accepts its own key without registration — so an app can operate without provisioning an account per user. Deriving it by hand from the raw key bytes is easy to get subtly wrong, hence getImplicitAccountId() on the signer. Also covers requestMessageSignature's delegation and its "provider does not implement this" path, which had tests in the provider package but none at the signer boundary. Co-Authored-By: Claude Opus 5 (1M context) --- packages/auth0/README.md | 99 +++++++++++++++++++ packages/sdks/browser/src/signers/signer.ts | 9 ++ .../sdks/browser/test/signers/signer.spec.ts | 45 +++++++++ 3 files changed, 153 insertions(+) create mode 100644 packages/auth0/README.md diff --git a/packages/auth0/README.md b/packages/auth0/README.md new file mode 100644 index 0000000..077eaa5 --- /dev/null +++ b/packages/auth0/README.md @@ -0,0 +1,99 @@ +# @fast-auth/auth0 + +The Auth0 half of FastAuth: a PostLogin action that turns a signing request into a user +approval, and the forms that render it. + +## How it fits together + +FastAuth's guarantee is that the MPC only ever signs bytes Auth0 certified after showing them +to the user. The on-chain guard enforces one half of that — it checks that the bytes handed to +the MPC equal the `fatxn` claim in the access token, and nothing else. It never inspects them. + +The other half lives here. The action is what decides *what* those bytes are allowed to be and +*how* they are described to the user before the token is minted. + +``` +/authorize? → action decodes + validates → form renders for approval + → approved: fatxn = the exact bytes + → denied: no token +``` + +Because the guard compares byte for byte, the action must place the payload in `fatxn` +unmodified. Any normalisation here would make every resulting signature fail on-chain. + +## Payload types + +The action accepts exactly one payload per request, on the `/authorize` query string: + +| Query key | Contents | Form rendered | +| --------------- | ------------------------------------- | ---------------------- | +| `transaction` | Borsh-encoded NEAR `Transaction` | `TRANSACTION_FORM` | +| `delegateAction`| Borsh-encoded `DelegateAction` | `DELEGATE_ACTION_FORM` | +| `nep413` | Borsh-encoded NEP-413 message payload | `NEP413_FORM` | + +Sending more than one is rejected: with several present, precedence would decide silently which +one lands in `fatxn`, and a caller could display one payload while a different one gets signed. + +A payload that fails to decode is denied rather than rendered. If the screen cannot describe it, +the user cannot meaningfully approve it. + +## Action secrets + +Configured under **Actions → Library → your action → Secrets**. Despite the name, only the +audience is remotely sensitive — the form entries are identifiers Auth0 generates on import, and +they differ per tenant, which is why they are configuration rather than constants in the code. + +| Secret | Value | +| ---------------------- | -------------------------------------------------------- | +| `ONCHAIN_AUDIENCE` | Identifier of the signing API. A request carrying a payload is only honoured for this audience, and a request *for* this audience without a payload is denied. | +| `TRANSACTION_FORM` | Form id for transaction approvals. | +| `DELEGATE_ACTION_FORM` | Form id for delegate action approvals. | +| `NEP413_FORM` | Form id for NEP-413 message approvals. | + +## Deploying a form + +Forms are built from source rather than edited in the dashboard: `build.js` inlines each +component's JS, CSS and schema into a template and emits a single importable file. + +```bash +pnpm build # writes src/forms//_form.json +``` + +Then, per form: + +1. **Auth0 Dashboard → Forms → Create Form → Import from JSON**, and upload the generated file. +2. Copy the id Auth0 assigns to the created form. +3. Paste it into the matching action secret above. + +Repeat per tenant — ids are not portable between staging and production. + +Re-importing after a code change creates a *new* form with a new id, so remember to update the +secret, or the action will keep rendering the previous version. + +## Local development + +```bash +pnpm playground # serves the forms at http://localhost:5174 +``` + +The playground renders the same components against mock payloads (`playground/payloads.js`) +with a shim standing in for the helpers that `build.js` inlines in production. It is the fastest +way to iterate on an approval screen without touching a tenant. + +```bash +pnpm test # action decoding, handler dispatch, and form rendering +``` + +The fixtures encode payloads with NEAR's production encoders, and the NEP-413 fixtures use a +borsh schema transcribed from the NEP rather than imported from the action — so a change to the +action's schema breaks the round-trip instead of silently agreeing with itself. + +## Adding a form + +1. `src/forms//_form_base.json` — the template. Custom components carry + `"$source": ""`, resolved against the form's own directory, or against the forms root + when the path contains a `/` (e.g. `shared/image`). +2. `src/forms//details/index.js` and `index.css` — the component. Rendering helpers live in + `shared/helpers` and are available as `__auth0FormHelpers`. +3. Register the form in the `FORMS` array in `build.js`. +4. Add the corresponding secret and render it from the action. diff --git a/packages/sdks/browser/src/signers/signer.ts b/packages/sdks/browser/src/signers/signer.ts index 709a085..2a8317c 100644 --- a/packages/sdks/browser/src/signers/signer.ts +++ b/packages/sdks/browser/src/signers/signer.ts @@ -169,6 +169,15 @@ export class FastAuthSigner

{ return await this.connection.provider.sendTransaction(signedTransaction); } + /** + * Derive the NEAR implicit account id for this signer: the hex encoding of its ed25519 public key. The account needs no on-chain creation to receive funds or to authorize NEP-413 messages, which is what lets an app operate without provisioning an account per user. + * @returns The implicit account id. + */ + async getImplicitAccountId(): Promise { + const publicKey = await this.getPublicKey("ed25519"); + return Buffer.from(publicKey.data).toString("hex"); + } + /** * Get the public key of the account. * @param algorithm The algorithm to use. diff --git a/packages/sdks/browser/test/signers/signer.spec.ts b/packages/sdks/browser/test/signers/signer.spec.ts index 9f254a4..1c55731 100644 --- a/packages/sdks/browser/test/signers/signer.spec.ts +++ b/packages/sdks/browser/test/signers/signer.spec.ts @@ -59,6 +59,51 @@ describe("FastAuthSigner", () => { }); }); + describe("requestMessageSignature", () => { + it("should delegate to the provider when it supports NEP-413", async () => { + const args = [{ payload: { message: "Sign in", recipient: "example.com" } }]; + (mockProvider as any).requestMessageSignature = jest.fn().mockResolvedValue({ userId: "u" }); + // @ts-ignore testing spread args passthrough + await signer.requestMessageSignature(...(args as any)); + expect((mockProvider as any).requestMessageSignature).toHaveBeenCalledWith(...args); + }); + + it("should raise a clear error when the provider does not implement it", async () => { + // The method is optional on the interface, so an unimplemented provider must fail + // with an explanation rather than "is not a function". + delete (mockProvider as any).requestMessageSignature; + await expect(signer.requestMessageSignature({} as any)).rejects.toThrow(/does not support NEP-413/); + }); + }); + + describe("getImplicitAccountId", () => { + beforeEach(async () => { + mockProvider.getPath.mockResolvedValue("jwt#path/"); + await signer.init(); + }); + + it("should hex-encode the ed25519 public key", async () => { + const data = Uint8Array.from(Array.from({ length: 32 }, (_, i) => i)); + (mockConnection.provider.query as jest.Mock).mockResolvedValue({ + result: Buffer.from(JSON.stringify("pk")), + }); + jest.spyOn(signer, "getPublicKey").mockResolvedValue({ data } as any); + + const accountId = await signer.getImplicitAccountId(); + + // A NEAR implicit account id is exactly the 64-char hex of its public key. + expect(accountId).toBe(Buffer.from(data).toString("hex")); + expect(accountId).toHaveLength(64); + expect(accountId).toMatch(/^[0-9a-f]{64}$/); + }); + + it("should always ask for the ed25519 key", async () => { + const spy = jest.spyOn(signer, "getPublicKey").mockResolvedValue({ data: new Uint8Array(32) } as any); + await signer.getImplicitAccountId(); + expect(spy).toHaveBeenCalledWith("ed25519"); + }); + }); + describe("getPublicKey (viewFunction)", () => { beforeEach(async () => { mockProvider.getPath.mockResolvedValue("jwt#path/"); From 0be2926d317f4e3a0d73d585151b41400a9d6dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Rivera?= Date: Thu, 13 Aug 2026 17:42:55 +0200 Subject: [PATCH 5/6] fix(providers): stop routing NEP-413 state through the authorization request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit requestMessageSignature accepted a `state` and forwarded it in authorizationParams, which does nothing: auth0-spa-js builds the authorize params as Object.assign(..., authorizationParams, {..., state: }), so its OAuth state always wins and the caller's value is dropped without a trace. An option that silently does nothing is worse than no option. It should never have travelled anyway. NEP-413 defines `state` as a value the caller generates, holds, and matches when the result comes back — it is not part of the signed payload and has no business reaching the authorization server, which mints its own for the OAuth exchange. Removed from the request options; buildNep413SignedMessage still takes it, which is where the standard actually wants it. Documented on the response type, and pinned with a test asserting no state reaches authorizationParams. Co-Authored-By: Claude Opus 5 (1M context) --- packages/providers/javascript/src/nep413.ts | 10 +++++++++- packages/providers/javascript/src/provider.ts | 6 ++---- packages/providers/javascript/src/types.ts | 5 ----- packages/providers/javascript/test/provider.spec.ts | 12 ++++++++++++ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/providers/javascript/src/nep413.ts b/packages/providers/javascript/src/nep413.ts index 91d64fb..3f03279 100644 --- a/packages/providers/javascript/src/nep413.ts +++ b/packages/providers/javascript/src/nep413.ts @@ -63,7 +63,15 @@ export type NEP413SignedMessage = { publicKey: string; /** Base64-encoded signature over sha256 of the serialized payload. */ signature: string; - /** Echo of the caller's CSRF state, when one was supplied. */ + /** + * Echo of the caller's CSRF state, when one was supplied. + * + * NEP-413's `state` never leaves the client: the standard defines it as a value the caller + * generates, holds, and matches when the result comes back. It is deliberately absent from + * the signed payload and from the authorization request — Auth0 mints its own `state` for + * the OAuth exchange and overwrites anything passed alongside it, so routing NEP-413's state + * through there would silently drop it. Hold it caller-side and attach it here. + */ state?: string; }; diff --git a/packages/providers/javascript/src/provider.ts b/packages/providers/javascript/src/provider.ts index c7b0602..1c2d5a9 100644 --- a/packages/providers/javascript/src/provider.ts +++ b/packages/providers/javascript/src/provider.ts @@ -289,14 +289,13 @@ export class JavascriptProvider implements IFastAuthProvider { private async requestMessageSignatureWithRedirect( requestSignatureOptions: JavascriptRequestMessageSignatureWithRedirectOptions, ): Promise { - const { redirectUri, payload, state, ...opts } = requestSignatureOptions; + const { redirectUri, payload, ...opts } = requestSignatureOptions; await this.client.loginWithRedirect({ authorizationParams: { audience: this.options.signingAudience, scope: "transaction:sign", nep413: encodeNep413Payload(payload), redirect_uri: redirectUri, - ...(state !== undefined ? { state } : {}), }, ...opts, }); @@ -310,13 +309,12 @@ export class JavascriptProvider implements IFastAuthProvider { private async requestMessageSignatureWithPopup( requestSignatureOptions: JavascriptRequestMessageSignatureWithPopupOptions, ): Promise { - const { payload, state, ...opts } = requestSignatureOptions; + const { payload, ...opts } = requestSignatureOptions; await this.client.loginWithPopup({ authorizationParams: { audience: this.options.signingAudience, scope: "transaction:sign", nep413: encodeNep413Payload(payload), - ...(state !== undefined ? { state } : {}), }, ...opts, }); diff --git a/packages/providers/javascript/src/types.ts b/packages/providers/javascript/src/types.ts index 4332389..89ef812 100644 --- a/packages/providers/javascript/src/types.ts +++ b/packages/providers/javascript/src/types.ts @@ -58,11 +58,6 @@ export type JavascriptBaseRequestMessageSignatureOptions = JavascriptBaseRequest * The NEP-413 payload to sign. */ payload: NEP413Payload; - /** - * Optional CSRF state echoed back to the caller. Per NEP-413 it is not part of the signed - * bytes — it travels alongside the request and is returned with the result. - */ - state?: string; }; export type JavascriptRequestMessageSignatureWithRedirectOptions = JavascriptBaseRequestMessageSignatureOptions & diff --git a/packages/providers/javascript/test/provider.spec.ts b/packages/providers/javascript/test/provider.spec.ts index eaeb557..83d928b 100644 --- a/packages/providers/javascript/test/provider.spec.ts +++ b/packages/providers/javascript/test/provider.spec.ts @@ -749,6 +749,18 @@ describe("JavascriptProvider", () => { }); }); + it("should not forward a caller state to Auth0", async () => { + // auth0-spa-js mints its own `state` for the OAuth exchange and overwrites anything + // passed in authorizationParams, so a NEP-413 state routed through here would be + // dropped without a trace. It is held caller-side and attached to the result instead. + mockAuth0Client.loginWithPopup.mockResolvedValue(undefined); + + await provider.requestMessageSignature({ payload, state: "csrf-token" } as any); + + const params = mockAuth0Client.loginWithPopup.mock.calls[0][0].authorizationParams; + expect(params.state).toBeUndefined(); + }); + it("should return the user id after signing", async () => { mockAuth0Client.loginWithPopup.mockResolvedValue(undefined); From b706ab0aea4ad10c98ee8388cdce553532e3f329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Rivera?= Date: Thu, 13 Aug 2026 17:58:39 +0200 Subject: [PATCH 6/6] test(auth0): cover extractIntents and stringifyIntents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both were exported for testing alongside stringifyActions, which decoding.spec already covers, but neither had a test of its own. stringifyIntents in particular serialises bigints — JSON.stringify throws on those by default, and token amounts are a plausible place for one to show up. Co-Authored-By: Claude Opus 5 (1M context) --- packages/auth0/test/nep413-decoding.spec.js | 30 ++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/auth0/test/nep413-decoding.spec.js b/packages/auth0/test/nep413-decoding.spec.js index 0ca67d8..acf4228 100644 --- a/packages/auth0/test/nep413-decoding.spec.js +++ b/packages/auth0/test/nep413-decoding.spec.js @@ -16,7 +16,7 @@ * the same way NEAR wallets behave. Restricting it here would also be the wrong granularity: a * recipient belongs to an application, not to a tenant that hosts many of them. */ -const { decodeNep413Payload, extractIntents, NEP413_PREFIX_TAG } = require("../src/actions/authorize-app.action.js"); +const { decodeNep413Payload, extractIntents, stringifyIntents, NEP413_PREFIX_TAG } = require("../src/actions/authorize-app.action.js"); const { buildNep413Payload, buildIntentMessage, buildTransaction, toCsv, INTENTS_RECIPIENT } = require("./fixtures/builders.js"); describe("decodeNep413Payload — any valid message", () => { @@ -133,3 +133,31 @@ describe("decodeNep413Payload — malformed input", () => { ); }); }); + +describe("extractIntents", () => { + test("returns the intents array of a NEAR Intents body", () => { + const message = buildIntentMessage(); + expect(extractIntents(message)).toEqual(message.intents); + }); + + test("returns null for anything that is not one", () => { + for (const value of [null, {}, { intents: [] }, { intents: "transfer" }, { intents: {} }]) { + expect(extractIntents(value)).toBeNull(); + } + }); +}); + +describe("stringifyIntents", () => { + test("pretty-prints the intents for the approval screen", () => { + const serialized = stringifyIntents(buildIntentMessage().intents); + expect(JSON.parse(serialized)).toEqual(buildIntentMessage().intents); + expect(serialized).toContain("\n"); + }); + + test("renders bigint amounts instead of throwing on them", () => { + // JSON.stringify throws on BigInt by default, and token amounts are a plausible place + // for one to arrive. + const serialized = stringifyIntents([{ intent: "transfer", tokens: { "nep141:usdc.near": BigInt("1000000") } }]); + expect(JSON.parse(serialized).tokens ?? JSON.parse(serialized)[0].tokens).toEqual({ "nep141:usdc.near": "1000000" }); + }); +});