{"version":3,"file":"pas.mjs","names":[],"sources":["../../../src/transaction-analyzer/move-call-handlers/pas.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { bcs } from '@mysten/sui/bcs';\nimport { deriveObjectID, normalizeStructTag, normalizeSuiAddress } from '@mysten/sui/utils';\n\nimport type { AnalyzedCommandArgument, AnalyzedMoveCallCommand } from '../rules/commands.js';\nimport type {\n\tBalanceFlowsMoveCallHandler,\n\tBalanceFlowsMoveCallHandlerContext,\n\tBalanceFlowsMoveCallHandlerFactory,\n} from '../rules/balance-flows.js';\n\nfunction readPureU64(arg: AnalyzedCommandArgument | undefined): bigint | null {\n\tif (arg?.$kind !== 'Pure') return null;\n\ttry {\n\t\treturn BigInt(bcs.u64().fromBase64(arg.bytes));\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction readPureAddress(arg: AnalyzedCommandArgument | undefined): string | null {\n\tif (arg?.$kind !== 'Pure') return null;\n\ttry {\n\t\treturn normalizeSuiAddress(bcs.Address.fromBase64(arg.bytes));\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction coinTypeOf(command: AnalyzedMoveCallCommand): string | null {\n\tconst arg = command.command.typeArguments?.[0];\n\treturn arg ? normalizeStructTag(arg) : null;\n}\n\nfunction deriveAccountAddress(owner: string, packageId: string, namespaceId: string): string {\n\tconst key = bcs.Address.serialize(owner).toBytes();\n\treturn deriveObjectID(namespaceId, `${packageId}::keys::AccountKey`, key);\n}\n\n/**\n * Create a `balanceFlows` handler factory for the Permissioned Assets\n * Standard (PAS).\n *\n * @experimental PAS is still evolving and the on-chain signatures covered\n * by this handler may change. Expect both the set of supported Move calls\n * and the shape of the delta emission to shift alongside the standard.\n */\nexport function createPASMoveCallHandler(options: {\n\tpackageId: string;\n\tnamespaceId: string;\n}): BalanceFlowsMoveCallHandlerFactory {\n\tconst packageId = normalizeSuiAddress(options.packageId);\n\tconst namespaceId = normalizeSuiAddress(options.namespaceId);\n\n\treturn (): BalanceFlowsMoveCallHandler => {\n\t\t// Fresh accounts created in-PTB: ctx key -> account object address.\n\t\tconst accountByResult = new Map<string, string>();\n\t\t// SendFunds<T> hot potatoes: ctx key -> pending credit at resolver time.\n\t\tconst sendFundsByResult = new Map<\n\t\t\tstring,\n\t\t\t{ coinType: string; amount: bigint; recipient: string | null }\n\t\t>();\n\t\t// UnlockFunds<T> / ClawbackFunds<T> hot potatoes: ctx key -> Balance<T> to\n\t\t// surface as a tracked balance at the resolver's result slot.\n\t\tconst fundsByResult = new Map<string, { coinType: string; amount: bigint }>();\n\n\t\treturn (command: AnalyzedMoveCallCommand, ctx: BalanceFlowsMoveCallHandlerContext): boolean => {\n\t\t\tif (normalizeSuiAddress(command.command.package) !== packageId) return false;\n\n\t\t\tconst mod = command.command.module;\n\t\t\tconst fn = command.command.function;\n\t\t\tconst coinType = coinTypeOf(command);\n\t\t\tconst context = `pas::${mod}::${fn} at command ${command.index}`;\n\n\t\t\tconst flagNonPureAmount = (argIndex: number) =>\n\t\t\t\tctx.addIssue({\n\t\t\t\t\tmessage: `${context} expects a pure u64 amount at argument ${argIndex}`,\n\t\t\t\t});\n\n\t\t\tconst readAccountAddress = (arg: AnalyzedCommandArgument | undefined): string | null => {\n\t\t\t\tif (arg?.$kind === 'Object') return normalizeSuiAddress(arg.object.objectId);\n\t\t\t\tconst key = arg ? ctx.keyFor(arg) : null;\n\t\t\t\treturn key ? (accountByResult.get(key) ?? null) : null;\n\t\t\t};\n\n\t\t\tif (mod === 'account') {\n\t\t\t\tswitch (fn) {\n\t\t\t\t\tcase 'create': {\n\t\t\t\t\t\tconst owner = readPureAddress(command.arguments[1]);\n\t\t\t\t\t\tif (owner) {\n\t\t\t\t\t\t\taccountByResult.set(\n\t\t\t\t\t\t\t\tctx.outputKey(0),\n\t\t\t\t\t\t\t\tderiveAccountAddress(owner, packageId, namespaceId),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\tcase 'deposit_balance': {\n\t\t\t\t\t\tconst accountAddress = readAccountAddress(command.arguments[0]);\n\t\t\t\t\t\tconst balance = ctx.getTrackedBalance(command.arguments[1]);\n\t\t\t\t\t\tif (balance) {\n\t\t\t\t\t\t\t// Always consume — the deposit debits the source regardless of\n\t\t\t\t\t\t\t// whether we can attribute it to a specific account. Leaving the\n\t\t\t\t\t\t\t// balance alive would silently credit the original owner back\n\t\t\t\t\t\t\t// via the implicit-return loop.\n\t\t\t\t\t\t\tif (accountAddress) {\n\t\t\t\t\t\t\t\tctx.recordFlow(accountAddress, balance.coinType, balance.balance);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbalance.consume();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\tcase 'unlock_balance':\n\t\t\t\t\tcase 'clawback_balance': {\n\t\t\t\t\t\tconst accountAddress = readAccountAddress(command.arguments[0]);\n\t\t\t\t\t\tconst amountArgIndex = fn === 'unlock_balance' ? 2 : 1;\n\t\t\t\t\t\tconst amount = readPureU64(command.arguments[amountArgIndex]);\n\t\t\t\t\t\tif (amount == null) {\n\t\t\t\t\t\t\tflagNonPureAmount(amountArgIndex);\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!coinType) return true;\n\t\t\t\t\t\tif (!accountAddress) {\n\t\t\t\t\t\t\tctx.addIssue({\n\t\t\t\t\t\t\t\tmessage: `${context} could not resolve the source account; debit un-attributed`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tctx.recordFlow(accountAddress, coinType, -amount);\n\t\t\t\t\t\tfundsByResult.set(ctx.outputKey(0), { coinType, amount });\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\tcase 'send_balance':\n\t\t\t\t\tcase 'unsafe_send_balance': {\n\t\t\t\t\t\tconst fromAddress = readAccountAddress(command.arguments[0]);\n\t\t\t\t\t\tlet toAddress: string | null;\n\t\t\t\t\t\tif (fn === 'send_balance') {\n\t\t\t\t\t\t\ttoAddress = readAccountAddress(command.arguments[2]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst recipient = readPureAddress(command.arguments[2]);\n\t\t\t\t\t\t\ttoAddress = recipient\n\t\t\t\t\t\t\t\t? deriveAccountAddress(recipient, packageId, namespaceId)\n\t\t\t\t\t\t\t\t: null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst amount = readPureU64(command.arguments[3]);\n\t\t\t\t\t\tif (amount == null) {\n\t\t\t\t\t\t\tflagNonPureAmount(3);\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!coinType) return true;\n\t\t\t\t\t\tif (!fromAddress) {\n\t\t\t\t\t\t\t// Source unknown: don't fabricate a credit from nothing. Flag\n\t\t\t\t\t\t\t// so the caller doesn't silently interpret \"no delta\" as\n\t\t\t\t\t\t\t// \"no value moved\".\n\t\t\t\t\t\t\tctx.addIssue({\n\t\t\t\t\t\t\t\tmessage: `${context} could not resolve the source account; debit un-attributed`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Unresolved `toAddress` is acceptable — we know what left, we\n\t\t\t\t\t\t// just can't attribute the credit. The resolver will skip the\n\t\t\t\t\t\t// credit side when `recipient` is null.\n\t\t\t\t\t\tctx.recordFlow(fromAddress, coinType, -amount);\n\t\t\t\t\t\tsendFundsByResult.set(ctx.outputKey(0), {\n\t\t\t\t\t\t\tcoinType,\n\t\t\t\t\t\t\tamount,\n\t\t\t\t\t\t\trecipient: toAddress,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\t(mod === 'unlock_funds' && (fn === 'resolve' || fn === 'resolve_unrestricted_balance')) ||\n\t\t\t\t(mod === 'clawback_funds' && fn === 'resolve')\n\t\t\t) {\n\t\t\t\tconst key = ctx.keyFor(command.arguments[0]);\n\t\t\t\tif (!key) return true;\n\t\t\t\tconst pending = fundsByResult.get(key);\n\t\t\t\tif (pending) {\n\t\t\t\t\tctx.trackBalanceResult(0, pending.coinType, pending.amount);\n\t\t\t\t\tfundsByResult.delete(key);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (mod === 'send_funds' && fn === 'resolve_balance') {\n\t\t\t\tconst key = ctx.keyFor(command.arguments[0]);\n\t\t\t\tif (!key) return true;\n\t\t\t\tconst pending = sendFundsByResult.get(key);\n\t\t\t\tif (pending && pending.recipient) {\n\t\t\t\t\tctx.recordFlow(pending.recipient, pending.coinType, pending.amount);\n\t\t\t\t}\n\t\t\t\tsendFundsByResult.delete(key);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t};\n\t};\n}\n"],"mappings":"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"}