# Global Deployer A minimal contract for managing global contract code on deterministic ([NEP-616](https://github.com/near/NEPs/blob/master/neps/nep-0616.md)) accounts. It implements the upgrade mechanism for [NEP-591 Global Contracts](https://github.com/near/NEPs/blob/master/neps/nep-0591.md). ## Two-Step Deployment Deployments are split into two steps: 1. **Approve** (`gd_approve`) — the owner (typically a DAO) votes for a specific code hash 2. **Deploy** (`gd_deploy`) — anyone can execute the deployment by submitting the matching WASM binary + storage deposit ### Why permissionless deploy? The deploy step requires attaching the full WASM binary and a storage deposit. This is error-prone (misconfigured deposit, large transaction). By separating approval from execution, the DAO only votes for a well-known code hash (e.g. from GitHub releases), and a dedicated operator or bot handles the actual deployment mechanics. ### Design philosophy The contract is intentionally slim and low-level. More sophisticated workflows can be built on top by composing utility contracts as owners. For example, only one hash can be approved at a time — each new approval erases the previous one. If consecutive multi-stage upgrades are needed, a utility contract can queue approvals and forward them one-by-one after each previous deployment completes: ```mermaid flowchart LR DAO -- "votes for H1" --> UC[Utility Contract] DAO -- "votes for H2" --> UC[Utility Contract] DAO -- ".." --> UC[Utility Contract] DAO -- "votes for HN" --> UC[Utility Contract] UC -- "queues deployments [H1, H2, .. HN]" --> GD[Global Deployer] ``` ## Contract State | Field | Type | Default | Description | |-----------------|-------------|--------------|---------------------------------------------------| | `owner_id` | `AccountId` | set at init | Account authorized to approve deployments and transfer ownership | | `code_hash` | `[u8; 32]` | `0x000...000` | SHA-256 hash of the currently deployed code | | `approved_hash` | `[u8; 32]` | `0x000...000` | SHA-256 hash of the next approved deployment | ## Public API ### `gd_approve(old_hash, new_hash)` Sets the approved hash for the next deployment. - **Access**: owner only - **Deposit**: 1 yoctoNEAR - **Params**: `old_hash` must match current `code_hash` (prevents stale approvals), `new_hash` is the SHA-256 of the WASM to deploy next - **State change**: sets `approved_hash` to `new_hash` - **Events**: `Approve { code_hash: new_hash, reason: By(caller) }` ### `gd_deploy(code)` Deploys WASM code as a global contract on this account. - **Access**: permissionless - **Deposit**: enough to cover storage delta - **Params**: `code` (raw WASM binary, passed directly without borsh length prefix) — `sha256(code)` must equal `approved_hash` - **State change**: `code_hash = sha256(code)`, `approved_hash = 0x000...000` - **Events**: [`Deploy { code_hash }`, `Approve { code_hash: 0x000...000, reason: Deploy(code_hash) }` ] - **Refund**: unused deposit is returned to the caller ### `gd_transfer_ownership(receiver_id)` Transfers contract ownership to a new account. - **Access**: owner only - **Deposit**: 1 yoctoNEAR - **Params**: `receiver_id` — must differ from current owner - **State change**: `owner_id = receiver_id`, `approved_hash = 0x000...000` - **Events**: `Transfer { old_owner_id, new_owner_id }`, then `Approve { code_hash: 0x000...000, reason: By(new_owner_id) }` ### `gd_owner_id() → AccountId` Returns the current owner's account ID. View method. ### `gd_code_hash() → hex string` Returns the SHA-256 hash of the currently deployed code (or `0x000...000` if none). View method. ### `gd_approved_hash() → hex string` Returns the currently approved hash (or `0x000...000` if none). View method. ## Events All events follow [NEP-297](https://github.com/near/NEPs/blob/master/neps/nep-0297.md) with standard `"global-deployer"` version `"1.0.0"`. | Event | Fields | Description | |------------|---------------------------------|--------------------------------------------------------------------------------------------------------------| | `Approve` | `code_hash`, `reason` | Approved hash changed | | `Deploy` | `code_hash` | Code was deployed | | `Transfer` | `old_owner_id`, `new_owner_id` | Ownership was transferred | ## Deployment Flow ```mermaid sequenceDiagram box Contracts participant Owner participant Caller participant GD as 0s1234..1234
(Global Deployer) end Note over NS: 0s1234..1234 => None box rgb(80, 120, 180) NEAR Protocol participant NS as Global Contracts
Namespace end Owner->>GD: gd_approve(old_hash, new_hash) Caller->>GD: gd_deploy(code) + deposit GD->>NS: deploy_global_contract_by_account_id Note over NS: 0s1234..1234 => code GD->>Caller: refund unused deposit ``` ## Deployment Hierarchy The examples below use generic "Global Contract 1/2" names. In practice, these can be any global contracts — e.g. Escrow Swap, Oneshot Condvar, etc. ### How Global Contracts Work [NEP-591](https://github.com/near/NEPs/blob/master/neps/nep-0591.md) introduces a protocol-level **Global Contract Namespace** — a mapping from identifiers to WASM contract code. Instead of each account storing its own copy of contract code, accounts reference global contracts via `UseGlobalContractAction`. Two deployment modes are supported: - **Deploy-by-hash** (`GlobalContractDeployMode::CodeHash`): immutable — contract code is referenced by its SHA-256 hash. Cannot be changed after deployment. - **Deploy-by-account-id** (`GlobalContractDeployMode::AccountId`): upgradeable — the owner can redeploy code. All references auto-update since they point to the account, not the hash. ### Bootstrap Process 1. Deploy GD globally **by code hash** (one-time, immutable) 2. Instantiate Controller with `StateInit` referencing GD's code hash → deterministic address 3. Controller calls `gd_approve` + `gd_deploy` of the same GD code under its own account ID 4. Controller is now a **mutable** GD instance (can upgrade GD itself) 5. Instantiate Global Contract 1 Controller referencing Controller's account ID + unique `code_hash` in `StateInit` (e.g. `0x...01`) 6. `gd_approve` + `gd_deploy` Global Contract 1 WASM on that instance 7. From Global Contract 1 Controller, create individual Global Contract 1 instances 8. Repeat for Global Contract 2 with a different `code_hash` in `StateInit` (e.g. `0x...02`) ### Hierarchy Diagram ```mermaid %%{init: {"flowchart": {"wrappingWidth": 600}}}%% flowchart TD GD["GLOBAL CONTRACT NAMESPACE
CodeHash(0x123..123) => GLOBAL DEPLOYER WASM
GlobalHash(0s..aaa) => GLOBAL DEPLOYER WASM
GlobalAccountId(0s..bbb) => GLOBAL CONTRACT 1 WASM
GlobalAccountId(0s..ccc) => GLOBAL CONTRACT 2 WASM"] C["MUTABLE CONTROLLER · 0s..aaa
ref: CodeHash(0x123..123)
state_init: {
owner: alice.near,
code_hash: 0x00..00,
approved_hash: 0x00..00
}"] C --> GC1C["GLOBAL CONTRACT 1 CONTROLLER · 0s..bbb
ref: GlobalAccountId(0s..aaa)
state_init: {
owner: alice.near,
code_hash: 0x00..01,
approved_hash: 0x00..00
}"] C --> GC2C["GLOBAL CONTRACT 2 CONTROLLER · 0s..ccc
ref: GlobalAccountId(0s..aaa)
state_init: {
owner: alice.near,
code_hash: 0x00..02,
approved_hash: 0x00..00
}"] GC1C --> GC1I1["GLOBAL CONTRACT 1 · INSTANCE 1 · 0s..ddd
ref: GlobalAccountId(0s..bbb)
state_init: {
owner: alice.near,
code_hash: 0x00..01,
approved_hash: 0x00..00
}"] GC1C --> GC1I2["GLOBAL CONTRACT 1 · INSTANCE 2 · 0s..eee
ref: GlobalAccountId(0s..bbb)
state_init: {
owner: alice.near,
code_hash: 0x00..02,
approved_hash: 0x00..00
}"] GC2C --> GC2I1["GLOBAL CONTRACT 2 · INSTANCE 1 · 0s..fff
ref: GlobalAccountId(0s..ccc)
state_init: {
owner: alice.near,
code_hash: 0x00..01,
approved_hash: 0x00..00
}"] style GD fill:#e0e0e0,stroke:#999,color:#000 style C fill:#bbdefb,stroke:#1976d2,color:#000 style GC1C fill:#c8e6c9,stroke:#388e3c,color:#000 style GC2C fill:#ffe0b2,stroke:#f57c00,color:#000 style GC1I1 fill:#c8e6c9,stroke:#388e3c,color:#000 style GC1I2 fill:#c8e6c9,stroke:#388e3c,color:#000 style GC2I1 fill:#ffe0b2,stroke:#f57c00,color:#000 ``` ### Deployed Instances The Outlayer hierarchy is bootstrapped on mainnet under an admin identity controlled by a DAO, `gdpl.near`, rather than a plain NEAR account. `gdpl.near` derives an ed25519 key via the [Chain Signatures](https://github.com/near/mpc) signer contract (`v1.signer`): ```sh near contract call-function as-read-only v1.signer derived_public_key json-args '{ "path": "global deployer admin", "predecessor": "gdpl.near", "domain_id": 1 }' network-config mainnet-fastnear now # "ed25519:HoacxHqBSbTVTKnokozyy4K6HrVz7thDRdneiBXVB3it" ``` The implicit account ID derived from that key, [`f9a9b8dfb0f2fa5033c761f6cae5fdae5ffc8c77b2463428a297cce11fc7f3d5`](https://nearblocks.io/address/f9a9b8dfb0f2fa5033c761f6cae5fdae5ffc8c77b2463428a297cce11fc7f3d5), is used as the owner/admin for every mutable contract in this hierarchy. **Why**: NEAR's MPC network ([near/mpc](https://github.com/near/mpc)) derives keys via threshold signing, so no single party — including the DAO — ever holds the private key, and the same derivation yields an identical implicit account on every chain that supports it. This gives the DAO one cross-chain admin identity with no exposed private key. #### Mainnet | Type | Address | |---|---| | Immutable Global Deployer (by hash) | `37osLHRQ8KsJx1YwXJbPcd2wfKHP5KjtKawnrfjjaD3J` | | Mutable Global Deployer (by account ID) | [`0s7876eb5ba4f1d97eb53a53903a86bd211c71b3b1`](https://nearblocks.io/address/0s7876eb5ba4f1d97eb53a53903a86bd211c71b3b1) | | Wallet (no-sign) controller | [`0sfa7a4d20c9b28a23fcd85930593ed3974c3fb38e`](https://nearblocks.io/address/0sfa7a4d20c9b28a23fcd85930593ed3974c3fb38e) | | Wallet (webauthn p256) controller | [`0saf343be226341c0eca7dba6d0b29d49bdff3ad03`](https://nearblocks.io/address/0saf343be226341c0eca7dba6d0b29d49bdff3ad03) | | Wallet (webauthn ed25519) controller | [`0sa7ed6ace79f0fd97313c465fd72a774990048501`](https://nearblocks.io/address/0sa7ed6ace79f0fd97313c465fd72a774990048501) | | Outlayer App controller | [`0sc0ec4b3e260f1bf2da6072ce6fa4493b072222dd`](https://nearblocks.io/address/0sc0ec4b3e260f1bf2da6072ce6fa4493b072222dd) | The immutable Global Deployer (by hash) is not a `StateInit`'d account — it's just the WASM referenced by its own code hash, so it has no `owner_id`. The other five entries are deterministic accounts that follow the same [Bootstrap Process](#bootstrap-process) as above, with the MPC-derived implicit account as owner throughout. The contracts above were built deterministically from repository revision [`32a7836f825e8c984c26149f4456793ec7e3d49a`](https://github.com/near/intents/commit/32a7836f825e8c984c26149f4456793ec7e3d49a). The build artifact is available from [this GitHub Actions run](https://github.com/near/intents/actions/runs/32236966092), and can be reproduced locally with `cargo near build reproducible-wasm`. #### Testnet Only the base Global Deployer instances are deployed on testnet so far: | Type | Address | |---|---| | Immutable Global Deployer (by hash) | `37osLHRQ8KsJx1YwXJbPcd2wfKHP5KjtKawnrfjjaD3J` | | Mutable Global Deployer (by account ID) | [`0s7876eb5ba4f1d97eb53a53903a86bd211c71b3b1`](https://testnet.nearblocks.io/address/0s7876eb5ba4f1d97eb53a53903a86bd211c71b3b1) | These are the same addresses as on mainnet. The immutable deployer is addressed by the WASM's hash, which doesn't depend on the network. The mutable deployer's address is derived from the referenced global contract code plus the full `StateInit` storage (`owner_id`, `code_hash`, and `approved_hash`) — for this base deployer, `code_hash` and `approved_hash` are left at their default all-zero value (`0000…0000`), and only `owner_id` is set. The deterministic `AccountId` is identical on every chain only because all of these — the referenced GD code, `owner_id`, and the zeroed `code_hash`/`approved_hash` — are the same on both networks, including the owner being the same MPC-derived implicit account. #### Deprecated These earlier deployments have been superseded by the ones above. As far as we know, they were never used by anyone.
Show deprecated addresses | Network | Type | Account / Hash | | --------- | ------ | ---------------- | | Mainnet | Immutable (by hash) | `8JK2g3kr7qCbRDBmoLx7c9Zrz9TxPdANP7ocbQGE2fqP` | | Mainnet | Mutable (by account ID) | [`0s384bfa53f1718c7f53eaaa1b43c55e2aea3ef309`](https://nearblocks.io/address/0s384bfa53f1718c7f53eaaa1b43c55e2aea3ef309) | | Testnet | Immutable (by hash) | `8JK2g3kr7qCbRDBmoLx7c9Zrz9TxPdANP7ocbQGE2fqP` | | Testnet | Mutable (by account ID) | [`0s29e346108955b88c2d180a4ba17662b1f2cc1028`](https://testnet.nearblocks.io/address/0s29e346108955b88c2d180a4ba17662b1f2cc1028) |
### Multi-Stage Deployment If consecutive upgrades are needed (e.g. H1 → H2 → H3), they can be prepared upfront. Since `gd_approve` takes the current `code_hash` as `old_hash`, each approval call simply references the code hash of the previously approved WASM binary. As long as you know the hashes of all consecutive binaries in advance, the full chain of `gd_approve` + `gd_deploy` calls can be queued and executed sequentially. ### Important Notes - A deterministic account ID is derived from `StateInit` at creation. After `gd_approve` mutates state, on-chain state diverges from what the address was derived from. - Upgrading Controller code propagates to all future instances (deploy-by-account-id). - The GD deployed by hash once is the **immutable foundation** for the whole hierarchy. ## `near gds` extension The `near-gds` command is an extension for [near-cli-rs](https://github.com/near/near-cli-rs) that computes the `StateInit` for a global-deployer contract, outputting a JSON map of base64-encoded key-value pairs. ### Install ```sh cargo install --path ./crates/global-deployer/near-gds ``` ### Usage ```sh $ near gds --help Compute StateInit for a global-deployer contract Usage: near-gds [OPTIONS] --owner-id Options: --owner-id Owner account ID -i, --index Unique index for the deployer instance. Can be used to derive multiple deployers for a single owner [default: 0] --approve Pre-approve SHA-256 code hash: first `gd_deploy()` won't require `gd_approve()`. Hash can be encoded as base58 or hex with `0x` prefix -q, --quiet Output single-line JSON with base64-encoded keys/values -h, --help Print help ``` ### Example ```sh near gds --owner-id test.near --index 1 ``` ```text // State: { "owner_id": "test.near", "code_hash": "0000000000000000000000000000000000000000000000000000000000000001", "approved_hash": "0000000000000000000000000000000000000000000000000000000000000000" } // Storage key-value pairs (as base64): {"":"CQAAAHRlc3QubmVhcgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="} ``` Use `-q` / `--quiet` to suppress stderr and emit only JSON, useful for piping into other tools like [`near-cli`](https://github.com/near/near-cli-rs): ```sh near contract state-init \ use-global-account-id 0s384bfa53f1718c7f53eaaa1b43c55e2aea3ef309 \ data-from-json "$(near gds \ --owner-id intents.sputnik-dao.near --index 42 \ --pre-approve 0x6c71114931fe91153b868f2cb29c5db70e59677d6d2e40404b3b9044d8052266 \ --quiet)" ```