Skip to content

Commit 82b0348

Browse files
warmidrisclaude
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 c5dd384 commit 82b0348

1 file changed

Lines changed: 32 additions & 10 deletions

File tree

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

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

308308
const state = validation.state;
309+
310+
// Build the flat Clarity-typed message matching the on-chain SIP-018 domain.
311+
// balance-1 always corresponds to principal-1 in the pipe key (canonical ordering),
312+
// regardless of which side the local agent is on.
313+
const pipeKey = state.pipeKey;
314+
const localIsPrincipal1 = pipeKey["principal-1"] === state.forPrincipal;
315+
const balance1 = localIsPrincipal1 ? state.myBalance : state.theirBalance;
316+
const balance2 = localIsPrincipal1 ? state.theirBalance : state.myBalance;
317+
318+
const message = {
319+
"principal-1": { type: "principal", value: pipeKey["principal-1"] },
320+
"principal-2": { type: "principal", value: pipeKey["principal-2"] },
321+
token:
322+
pipeKey.token == null
323+
? { type: "none" }
324+
: { type: "some", value: { type: "principal", value: String(pipeKey.token) } },
325+
"balance-1": { type: "uint", value: Number(balance1) },
326+
"balance-2": { type: "uint", value: Number(balance2) },
327+
nonce: { type: "uint", value: Number(state.nonce) },
328+
action: { type: "uint", value: Number(state.action) },
329+
actor: { type: "principal", value: state.actor },
330+
"hashed-secret":
331+
state.secret == null
332+
? { type: "none" }
333+
: { type: "some", value: { type: "buff", value: state.secret } },
334+
"valid-after":
335+
state.validAfter == null
336+
? { type: "none" }
337+
: { type: "some", value: { type: "uint", value: Number(state.validAfter) } },
338+
};
339+
309340
const mySignature = await this.signTransferMessage({
310341
contractId: state.contractId,
311-
message: {
312-
"pipe-key": state.pipeKey,
313-
"balance-1": state.myBalance,
314-
"balance-2": state.theirBalance,
315-
nonce: state.nonce,
316-
action: state.action,
317-
actor: state.actor,
318-
"hashed-secret": state.secret,
319-
"valid-after": state.validAfter,
320-
},
342+
message,
321343
walletPassword,
322344
});
323345

0 commit comments

Comments
 (0)