Skip to content

Commit 7d11d85

Browse files
warmidrisclaude
authored andcommitted
fix: use correct SIP-018 message format in acceptIncomingTransfer
Two bugs caused the agent to produce signatures incompatible with on-chain verification: 1. Message was nested (pipe-key as sub-object) with plain string values. The Clarity contract uses a flat merged tuple with typed uint/principal fields — matching what sip018_sign expects. 2. balance-1/balance-2 always used myBalance/theirBalance regardless of whether the local agent is principal-1 or principal-2. The contract's map-balances logic assigns balance-1 to principal-1's balance canonically, so we must resolve ordering from pipeKey before building the message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 946d979 commit 7d11d85

File tree

1 file changed

+32
-10
lines changed

1 file changed

+32
-10
lines changed

packages/stackflow-agent/src/agent-service.js

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -407,18 +407,40 @@ export class StackflowAgentService {
407407
}
408408

409409
const state = validation.state;
410+
411+
// Build the flat Clarity-typed message matching the on-chain SIP-018 domain.
412+
// balance-1 always corresponds to principal-1 in the pipe key (canonical ordering),
413+
// regardless of which side the local agent is on.
414+
const pipeKey = state.pipeKey;
415+
const localIsPrincipal1 = pipeKey["principal-1"] === state.forPrincipal;
416+
const balance1 = localIsPrincipal1 ? state.myBalance : state.theirBalance;
417+
const balance2 = localIsPrincipal1 ? state.theirBalance : state.myBalance;
418+
419+
const message = {
420+
"principal-1": { type: "principal", value: pipeKey["principal-1"] },
421+
"principal-2": { type: "principal", value: pipeKey["principal-2"] },
422+
token:
423+
pipeKey.token == null
424+
? { type: "none" }
425+
: { type: "some", value: { type: "principal", value: String(pipeKey.token) } },
426+
"balance-1": { type: "uint", value: Number(balance1) },
427+
"balance-2": { type: "uint", value: Number(balance2) },
428+
nonce: { type: "uint", value: Number(state.nonce) },
429+
action: { type: "uint", value: Number(state.action) },
430+
actor: { type: "principal", value: state.actor },
431+
"hashed-secret":
432+
state.secret == null
433+
? { type: "none" }
434+
: { type: "some", value: { type: "buff", value: state.secret } },
435+
"valid-after":
436+
state.validAfter == null
437+
? { type: "none" }
438+
: { type: "some", value: { type: "uint", value: Number(state.validAfter) } },
439+
};
440+
410441
const mySignature = await this.signTransferMessage({
411442
contractId: state.contractId,
412-
message: {
413-
"pipe-key": state.pipeKey,
414-
"balance-1": state.myBalance,
415-
"balance-2": state.theirBalance,
416-
nonce: state.nonce,
417-
action: state.action,
418-
actor: state.actor,
419-
"hashed-secret": state.secret,
420-
"valid-after": state.validAfter,
421-
},
443+
message,
422444
walletPassword,
423445
});
424446

0 commit comments

Comments
 (0)